diff --git a/ably/auth_integration_test.go b/ably/auth_integration_test.go index 29af315a..7dd4e1c4 100644 --- a/ably/auth_integration_test.go +++ b/ably/auth_integration_test.go @@ -21,6 +21,7 @@ import ( "github.com/ably/ably-go/internal/ablytest" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func single() *ably.PaginateParams { @@ -134,7 +135,7 @@ func TestAuth_TokenAuth(t *testing.T) { "want url.Scheme=http; got %s", requestUrl.Scheme) rec.Reset() tok, err := client.Auth.Authorize(context.Background(), nil) - assert.NoError(t, err, + require.NoError(t, err, "Authorize()=%v", err) // Call to Authorize should always refresh the token. assert.Equal(t, 1, rec.Len(), @@ -162,7 +163,7 @@ func TestAuth_TokenAuth_Renew(t *testing.T) { TTL: time.Second.Milliseconds(), } tok, err := client.Auth.Authorize(context.Background(), params) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 1, rec.Len(), "want rec.Len()=1; got %d", rec.Len()) ttl := tok.ExpireTime().Sub(tok.IssueTime()) @@ -197,7 +198,7 @@ func TestAuth_TokenAuth_Renew(t *testing.T) { opts = app.Options(opts...) opts = append(opts, ably.WithKey(""), ably.WithTokenDetails(tok)) client, err = ably.NewREST(opts...) - assert.NoError(t, err, + require.NoError(t, err, "NewREST()=%v", err) _, err = client.Stats().Pages(context.Background()) assert.Error(t, err) @@ -250,7 +251,7 @@ func TestAuth_RequestToken(t *testing.T) { ably.AuthWithCallback(server.Callback(callback)), } tokCallback, err := client.Auth.RequestToken(context.Background(), nil, authOpts...) - assert.NoError(t, err, + require.NoError(t, err, "RequestToken()=%v (callback=%s)", err, callback) // Ensure no requests to Ably servers were made. assert.Equal(t, 0, rec.Len(), @@ -268,7 +269,7 @@ func TestAuth_RequestToken(t *testing.T) { ably.AuthWithCallback(server.Callback("request")), } tokCallback, err := client.Auth.RequestToken(context.Background(), nil, authOpts...) - assert.NoError(t, err, + require.NoError(t, err, "RequestToken()=%v", err) assert.Equal(t, 1, rec.Len(), "want rec.Len()=1; got %d", rec.Len()) @@ -301,7 +302,7 @@ func TestAuth_RequestToken(t *testing.T) { } tokURL, err := client.Auth.RequestToken(context.Background(), params, authOpts...) - assert.NoError(t, err, + require.NoError(t, err, "RequestToken()=%v (method=%s)", err, method) assert.NotEqual(t, tokURL.Token, token2.Token, "want tokURL.Token != token2.Token: %s (method=%s)", tokURL.Token, method) @@ -333,7 +334,7 @@ func TestAuth_RequestToken(t *testing.T) { ably.WithToken(tokURL.Token), ) c, err := ably.NewREST(optsURL...) - assert.NoError(t, err, + require.NoError(t, err, "NewREST()=%v", err) _, err = c.Stats().Pages(context.Background()) assert.NoError(t, err, @@ -381,11 +382,10 @@ func TestAuth_JWT_Token_RSA8c(t *testing.T) { assert.True(t, strings.HasPrefix(jwt, "ey")) rec, optn := ablytest.NewHttpRecorder() - rest, err := ably.NewREST( + rest, err := ably.NewREST(app.Options( ably.WithToken(jwt), - ably.WithEndpoint(app.Endpoint), optn[0], - ) + )...) assert.NoError(t, err, "rest()=%v", err) _, err = rest.Stats().Pages(context.Background()) @@ -404,12 +404,11 @@ func TestAuth_JWT_Token_RSA8c(t *testing.T) { app := ablytest.MustSandbox() rec, optn := ablytest.NewHttpRecorder() - rest, err := ably.NewREST( + rest, err := ably.NewREST(app.Options( ably.WithAuthURL(ablytest.CREATE_JWT_URL), ably.WithAuthParams(app.GetJwtAuthParams(30*time.Second, false)), - ably.WithEndpoint(app.Endpoint), optn[0], - ) + )...) assert.NoError(t, err, "rest()=%v", err) _, err = rest.Stats().Pages(context.Background()) @@ -448,11 +447,10 @@ func TestAuth_JWT_Token_RSA8c(t *testing.T) { }) rec, optn := ablytest.NewHttpRecorder() - rest, err := ably.NewREST( - ably.WithEndpoint(app.Endpoint), + rest, err := ably.NewREST(app.Options( authCallback, optn[0], - ) + )...) assert.NoError(t, err) _, err = rest.Stats().Pages(context.Background()) @@ -473,12 +471,11 @@ func TestAuth_JWT_Token_RSA8c(t *testing.T) { app := ablytest.MustSandbox() rec, optn := ablytest.NewHttpRecorder() - rest, err := ably.NewREST( + rest, err := ably.NewREST(app.Options( ably.WithAuthURL(ablytest.CREATE_JWT_URL), ably.WithAuthParams(app.GetJwtAuthParams(30*time.Second, true)), - ably.WithEndpoint(app.Endpoint), optn[0], - ) + )...) assert.NoError(t, err, "rest()=%v", err) _, err = rest.Stats().Pages(context.Background()) @@ -502,14 +499,14 @@ func TestAuth_ReuseClientID(t *testing.T) { ClientID: "reuse-me", } tok, err := client.Auth.Authorize(context.Background(), params) - assert.NoError(t, err, + require.NoError(t, err, "Authorize()=%v", err) assert.Equal(t, params.ClientID, tok.ClientID, "want ClientID=%q; got %q", params.ClientID, tok.ClientID) assert.Equal(t, params.ClientID, client.Auth.ClientID(), "want ClientID=%q; got %q", params.ClientID, client.Auth.ClientID()) tok2, err := client.Auth.Authorize(context.Background(), nil) - assert.NoError(t, err, + require.NoError(t, err, "Authorize()=%v", err) assert.Equal(t, params.ClientID, tok2.ClientID, "want ClientID=%q; got %q", params.ClientID, tok2.ClientID) @@ -532,7 +529,7 @@ func TestAuth_RequestToken_PublishClientID(t *testing.T) { for i, cas := range cases { rclient, err := ably.NewREST(app.Options()...) - assert.NoError(t, err) + require.NoError(t, err) params := &ably.TokenParams{ ClientID: cas.authAs, } @@ -718,13 +715,13 @@ func TestAuth_CreateTokenRequest(t *testing.T) { }) t.Run("RSA9c must generate a unique 16+ character nonce", func(t *testing.T) { req, err := client.Auth.CreateTokenRequest(params, opts...) - assert.NoError(t, err) + require.NoError(t, err) assert.GreaterOrEqual(t, len(req.Nonce), 16, "want len(nonce)>=16; got %d", len(req.Nonce)) }) t.Run("RSA9g generate a signed request", func(t *testing.T) { req, err := client.Auth.CreateTokenRequest(nil) - assert.NoError(t, err, + require.NoError(t, err, "CreateTokenRequest()=%v", err) assert.NotEqual(t, "", req.MAC, "want mac to be not empty") @@ -780,7 +777,6 @@ func TestAuth_IgnoreTimestamp_QueryTime(t *testing.T) { }{ "Should not save query time and timestamp when WithQueryTime is false and token params has no timestamp": { opt: []ably.ClientOption{ - ably.WithTLS(true), ably.WithUseTokenAuth(true), ably.WithQueryTime(false), }, @@ -794,7 +790,6 @@ func TestAuth_IgnoreTimestamp_QueryTime(t *testing.T) { }, "Should not save query time and timestamp when WithQueryTime is true and token params has no timestamp": { opt: []ably.ClientOption{ - ably.WithTLS(true), ably.WithUseTokenAuth(true), ably.WithQueryTime(true), }, @@ -808,7 +803,6 @@ func TestAuth_IgnoreTimestamp_QueryTime(t *testing.T) { }, "Should not save query time and timestamp when WithQueryTime is true and token params has a timestamp": { opt: []ably.ClientOption{ - ably.WithTLS(true), ably.WithUseTokenAuth(true), ably.WithQueryTime(true), }, @@ -822,7 +816,6 @@ func TestAuth_IgnoreTimestamp_QueryTime(t *testing.T) { }, "Should not save query time and timestamp when WithQueryTime is false and token params has a timestamp": { opt: []ably.ClientOption{ - ably.WithTLS(true), ably.WithUseTokenAuth(true), ably.WithQueryTime(false), }, diff --git a/ably/http_paginated_response_integration_test.go b/ably/http_paginated_response_integration_test.go index f41b64c3..4bbfdd0a 100644 --- a/ably/http_paginated_response_integration_test.go +++ b/ably/http_paginated_response_integration_test.go @@ -14,16 +14,17 @@ import ( "github.com/ably/ably-go/internal/ablytest" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestHTTPPaginatedFallback(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) opts := app.Options(ably.WithUseBinaryProtocol(false), ably.WithEndpoint("ably.invalid"), ably.WithFallbackHosts(nil)) client, err := ably.NewREST(opts...) - assert.NoError(t, err) + require.NoError(t, err) t.Run("request_time", func(t *testing.T) { _, err := client.Request("get", "/time").Pages(context.Background()) assert.Error(t, err) @@ -32,12 +33,12 @@ func TestHTTPPaginatedFallback(t *testing.T) { func TestHTTPPaginatedResponse(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) client, err := ably.NewREST(app.Options()...) - assert.NoError(t, err) + require.NoError(t, err) t.Run("request_time", func(t *testing.T) { res, err := client.Request("get", "/time").Pages(context.Background()) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, http.StatusOK, res.StatusCode(), "expected %d got %d", http.StatusOK, res.StatusCode()) assert.True(t, res.Success(), "expected success to be true") @@ -51,7 +52,7 @@ func TestHTTPPaginatedResponse(t *testing.T) { t.Run("request_404", func(t *testing.T) { res, err := client.Request("get", "/keys/ablyjs.test/requestToken").Pages(context.Background()) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, http.StatusNotFound, res.StatusCode(), "expected %d got %d", http.StatusNotFound, res.StatusCode()) assert.Equal(t, ably.ErrNotFound, res.ErrorCode(), @@ -73,7 +74,7 @@ func TestHTTPPaginatedResponse(t *testing.T) { t.Run("post", func(t *testing.T) { for _, message := range msgs { res, err := client.Request("POST", channelPath, ably.RequestWithBody(message)).Pages(context.Background()) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, http.StatusCreated, res.StatusCode(), "expected %d got %d", http.StatusCreated, res.StatusCode()) assert.True(t, res.Success(), diff --git a/ably/proto_message_integration_test.go b/ably/proto_message_integration_test.go index d2b28122..ec0ead23 100644 --- a/ably/proto_message_integration_test.go +++ b/ably/proto_message_integration_test.go @@ -11,6 +11,7 @@ import ( "github.com/ably/ably-go/internal/ablytest" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestMessage_CryptoDataFixtures_RSL6a1_RSL5b_RSL5c(t *testing.T) { @@ -47,14 +48,14 @@ func TestMessage_CryptoDataFixtures_RSL6a1_RSL5b_RSL5c(t *testing.T) { assert.NoError(t, err) encoded, err = ably.MessageWithDecodedData(encoded, cipher) - assert.NoError(t, err) + require.NoError(t, err) var encrypted ably.Message err = json.Unmarshal(item.Encoded, &encrypted) assert.NoError(t, err) encrypted, err = ably.MessageWithDecodedData(encrypted, cipher) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, encoded.Name, encrypted.Name, "expected %s got %s", encoded.Name, encrypted.Name) assert.Equal(t, encoded.Data, encrypted.Data, @@ -98,14 +99,14 @@ func TestMessage_CryptoDataFixtures_RSL6a1_RSL5b_RSL5c_TM3(t *testing.T) { assert.NoError(t, err) encoded, err = ably.MessageWithDecodedData(encoded, cipher) - assert.NoError(t, err) + require.NoError(t, err) var encrypted ably.Message err = json.Unmarshal(item.Encoded, &encrypted) assert.NoError(t, err) encrypted, err = ably.MessageWithDecodedData(encrypted, cipher) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, encoded.Name, encrypted.Name) assert.Equal(t, encoded.Data, encrypted.Data) } diff --git a/ably/realtime_channel_integration_test.go b/ably/realtime_channel_integration_test.go index c5a411e2..9e96b8e8 100644 --- a/ably/realtime_channel_integration_test.go +++ b/ably/realtime_channel_integration_test.go @@ -17,6 +17,7 @@ import ( "github.com/ably/ably-go/internal/ablytest" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func expectMsg(ch <-chan *ably.Message, name string, data interface{}, t time.Duration, received bool) error { @@ -111,7 +112,7 @@ func TestRealtimeChannel_Subscribe(t *testing.T) { func TestRealtimeChannel_SubscriptionFilters(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) options := app.Options() restClient, err := ably.NewREST(options...) assert.NoError(t, err) @@ -173,7 +174,7 @@ func TestRealtimeChannel_SubscriptionFilters(t *testing.T) { unsub, err := rtDerivedChannel.SubscribeAll(ctx, func(msg *ably.Message) { filteredMessages <- msg }) - assert.NoError(t, err) + require.NoError(t, err) defer unsub() unfilteredMessages := make(chan *ably.Message, 10) diff --git a/ably/realtime_channel_spec_integration_test.go b/ably/realtime_channel_spec_integration_test.go index 8e60bac6..d0d129ee 100644 --- a/ably/realtime_channel_spec_integration_test.go +++ b/ably/realtime_channel_spec_integration_test.go @@ -17,6 +17,7 @@ import ( "github.com/ably/ably-go/internal/ablytest" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestRealtimeChannel_RTL2_ChannelEventForStateChange(t *testing.T) { @@ -214,7 +215,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { t.Run("RTL4b: If connection state is INITIALIZED, CLOSING, CLOSED returns error", func(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) recorder := NewMessageRecorder() @@ -288,7 +289,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { t.Run("RTL4b: If connection state is FAILED, returns error", func(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) recorder := NewMessageRecorder() @@ -329,7 +330,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { t.Run("RTL4b: If connection state is SUSPENDED, returns error", func(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) recorder := NewMessageRecorder() @@ -429,7 +430,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { t.Run("RTL4d : should return error on FAILED while attaching channel", func(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) recorder := NewMessageRecorder() c, closer := TransitionConn(t, recorder.Dial, app.Options()...) @@ -478,7 +479,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { t.Run("RTL4d : should return error on DETACHED while attaching channel", func(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) recorder := NewMessageRecorder() c, closer := TransitionConn(t, recorder.Dial, app.Options()...) @@ -528,7 +529,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { t.Run("RTL4d : should return error on SUSPENDED while attaching channel", func(t *testing.T) { t.Skip("Channel SUSPENDED not implemented yet") app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) recorder := NewMessageRecorder() c, closer := TransitionConn(t, recorder.Dial, app.Options()...) @@ -578,7 +579,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { t.Run("RTL4e: Transition to failed if no attach permission", func(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) rest, _ := ably.NewREST(app.Options()...) var params ably.TokenParams @@ -645,7 +646,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { t.Run("RTL4g: If channel in FAILED state, set err to null and proceed with attach", func(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) recorder := NewMessageRecorder() c, closer := TransitionConn(t, recorder.Dial, app.Options()...) @@ -715,7 +716,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { t.Run("RTL4h: If channel is ATTACHING, listen to the attach event and don't send attach event", func(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) recorder := NewMessageRecorder() c, closer := TransitionConn(t, recorder.Dial, app.Options()...) @@ -779,7 +780,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { t.Run("RTL4h: If channel is DETACHING, do attach after completion of request", func(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) recorder := NewMessageRecorder() c, closer := TransitionConn(t, recorder.Dial, app.Options()...) @@ -862,7 +863,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { t.Run("RTL4i : If connection state is CONNECTING, do ATTACH after CONNECTED", func(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) recorder := NewMessageRecorder() c, closer := TransitionConn(t, recorder.Dial, app.Options()...) @@ -917,7 +918,7 @@ func TestRealtimeChannel_RTL4_Attach(t *testing.T) { t.Run("RTL4i : If connection state is DISCONNECTED, do ATTACH after CONNECTED", func(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) recorder := NewMessageRecorder() c, closer := TransitionConn(t, recorder.Dial, app.Options()...) @@ -1317,7 +1318,7 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { t.Run("RTL5a: If channel is INITIALIZED or DETACHED, do nothing", func(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) recorder := NewMessageRecorder() c, closer := TransitionConn(t, recorder.Dial, app.Options()...) @@ -1392,7 +1393,7 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { t.Run("RTL5b: If channel state is FAILED, return error", func(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) recorder := NewMessageRecorder() c, closer := TransitionConn(t, recorder.Dial, app.Options()...) @@ -1509,7 +1510,7 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { t.Run("RTL5e: return error if channel detach fails", func(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) recorder := NewMessageRecorder() c, closer := TransitionConn(t, recorder.Dial, app.Options()...) @@ -1613,7 +1614,7 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { t.Run("RTL5g: If connection state CLOSING or FAILED, should return error", func(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) recorder := NewMessageRecorder() c, closer := TransitionConn(t, recorder.Dial, app.Options()...) @@ -1677,7 +1678,7 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { t.Run("RTL5h : If Connection state CONNECTING, queue the DETACH message and send on CONNECTED", func(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) recorder := NewMessageRecorder() c, closer := TransitionConn(t, recorder.Dial, app.Options()...) @@ -1747,7 +1748,7 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { t.Run("RTL5h, RTN19b: If Connection state DISCONNECTED, queue the DETACH message and send on CONNECTED", func(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) recorder := NewMessageRecorder() c, closer := TransitionConn(t, recorder.Dial, app.Options()...) @@ -1816,7 +1817,7 @@ func TestRealtimeChannel_RTL5_Detach(t *testing.T) { t.Run("RTL5i: If channel in DETACHING or ATTACHING state, do detach after completion of operation", func(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) recorder := NewMessageRecorder() c, closer := TransitionConn(t, recorder.Dial, app.Options()...) @@ -1966,7 +1967,7 @@ func TestRealtimeChannel_RTL6c1_PublishNow(t *testing.T) { t.Run(fmt.Sprintf("when %s", state), func(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) c, closer := TransitionConn(t, nil, app.Options()...) defer safeclose(t, closer) @@ -1985,7 +1986,7 @@ func TestRealtimeChannel_RTL6c1_PublishNow(t *testing.T) { // published without interferring with the first client's state. subClient, err := ably.NewRealtime(app.Options()...) - assert.NoError(t, err) + require.NoError(t, err) defer safeclose(t, ablytest.FullRealtimeCloser(subClient)) err = ablytest.Wait(ablytest.ConnWaiter(subClient, subClient.Connect, ably.ConnectionEventConnected), nil) assert.NoError(t, err) @@ -2083,7 +2084,7 @@ func TestRealtimeChannel_RTL6c2_PublishEnqueue(t *testing.T) { t.Run(fmt.Sprintf("when connection is %v, channel is %v", connTarget, chanTarget), func(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) recorder := NewMessageRecorder() @@ -2181,7 +2182,7 @@ func TestRealtimeChannel_RTL6c4_PublishFail(t *testing.T) { t.Run(fmt.Sprintf("when connection is %v, channel is %v", connTarget, chanTarget), func(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) recorder := NewMessageRecorder() diff --git a/ably/realtime_client_integration_test.go b/ably/realtime_client_integration_test.go index c224bf1e..5fff5801 100644 --- a/ably/realtime_client_integration_test.go +++ b/ably/realtime_client_integration_test.go @@ -85,7 +85,7 @@ func TestRealtime_RSC7_AblyAgent(t *testing.T) { })) defer server.Close() serverURL, err := url.Parse(server.URL) - assert.NoError(t, err) + require.NoError(t, err) client, err := ably.NewRealtime( ably.WithEndpoint(serverURL.Host), @@ -109,7 +109,7 @@ func TestRealtime_RSC7_AblyAgent(t *testing.T) { })) defer server.Close() serverURL, err := url.Parse(server.URL) - assert.NoError(t, err) + require.NoError(t, err) client, err := ably.NewRealtime( ably.WithEndpoint(serverURL.Host), @@ -137,7 +137,7 @@ func TestRealtime_RSC7_AblyAgent(t *testing.T) { })) defer server.Close() serverURL, err := url.Parse(server.URL) - assert.NoError(t, err) + require.NoError(t, err) client, err := ably.NewRealtime( ably.WithEndpoint(serverURL.Host), @@ -167,7 +167,7 @@ func TestRealtime_RSC7_AblyAgent(t *testing.T) { })) defer server.Close() serverURL, err := url.Parse(server.URL) - assert.NoError(t, err) + require.NoError(t, err) client, err := ably.NewRealtime( ably.WithTLS(false), @@ -191,7 +191,7 @@ func TestRealtime_RSC7_AblyAgent(t *testing.T) { })) defer server.Close() serverURL, err := url.Parse(server.URL) - assert.NoError(t, err) + require.NoError(t, err) client, err := ably.NewRealtime( ably.WithTLS(false), @@ -219,7 +219,7 @@ func TestRealtime_RSC7_AblyAgent(t *testing.T) { })) defer server.Close() serverURL, err := url.Parse(server.URL) - assert.NoError(t, err) + require.NoError(t, err) client, err := ably.NewRealtime( ably.WithTLS(false), @@ -493,7 +493,7 @@ func TestRealtime_multiple(t *testing.T) { var all ablytest.ResultGroup var wg sync.WaitGroup app, err := ablytest.NewSandbox() - assert.NoError(t, err, + require.NoError(t, err, "NewSandbox()=%v", err) wg.Add(N) idch := make(chan string, N) diff --git a/ably/realtime_conn_integration_test.go b/ably/realtime_conn_integration_test.go index 02cc4489..384f166d 100644 --- a/ably/realtime_conn_integration_test.go +++ b/ably/realtime_conn_integration_test.go @@ -7,6 +7,7 @@ import ( "context" "errors" "net/url" + "sync" "testing" "time" @@ -14,6 +15,7 @@ import ( "github.com/ably/ably-go/internal/ablytest" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var connTransitions = []ably.ConnectionState{ @@ -91,7 +93,7 @@ func TestRealtimeConn_AuthError(t *testing.T) { ably.WithAutoConnect(false), } client, err := ably.NewRealtime(opts...) - assert.NoError(t, err, + require.NoError(t, err, "NewRealtime()=%v", err) err = ablytest.Wait(ablytest.ConnWaiter(client, client.Connect, ably.ConnectionEventConnected), nil) @@ -267,8 +269,24 @@ func TestRealtimeConn_ReconnectFromSuspendedState(t *testing.T) { dialErr := make(chan error, 1) msgReceiveErr := make(chan error, 1) + // lastDialErr sticks the most recently injected dial outcome. A dial made + // when the test hasn't queued a new one reuses it instead of blocking on an + // empty channel: a real dial always returns within its timeout, and a dial + // wedged here would strand the connection in CONNECTING, where the + // DISCONNECTED->SUSPENDED timer does not run — so it could never reach + // SUSPENDED and the test would hang on the harness timeout rather than + // exercising suspended-state reconnection. The mutex keeps it race-free even + // if a reconnect attempt overlaps a test-driven Connect. + var dialMu sync.Mutex + var lastDialErr error dial := DialFunc(func(p string, url *url.URL, timeout time.Duration) (ably.Conn, error) { - err := <-dialErr + dialMu.Lock() + select { + case lastDialErr = <-dialErr: + default: + } + err := lastDialErr + dialMu.Unlock() if err != nil { return nil, err } diff --git a/ably/realtime_conn_spec_integration_test.go b/ably/realtime_conn_spec_integration_test.go index 54115eaa..91b360a7 100644 --- a/ably/realtime_conn_spec_integration_test.go +++ b/ably/realtime_conn_spec_integration_test.go @@ -18,6 +18,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/ably/ably-go/ably" "github.com/ably/ably-go/internal/ablytest" @@ -227,7 +228,7 @@ func Test_RTN4a_ConnectionEventForStateChange(t *testing.T) { } realtime, err := ably.NewRealtime(options...) - assert.NoError(t, err, + require.NoError(t, err, "unexpected err: %s", err) changes := make(chan ably.ConnectionStateChange) @@ -750,7 +751,7 @@ func TestRealtimeConn_RTN15a_ReconnectOnEOF(t *testing.T) { // succeeds, we should then receive it without reattaching. rest, err := ably.NewREST(app.Options()...) - assert.NoError(t, err) + require.NoError(t, err) err = rest.Channels.Get(ablytest.UniqueChannelName(t, "channel")).Publish(context.Background(), "name", "data") assert.NoError(t, err) @@ -884,7 +885,7 @@ func TestRealtimeConn_RTN15b(t *testing.T) { // succeeds, we should then receive it without reattaching. rest, err := ably.NewREST(app.Options()...) - assert.NoError(t, err) + require.NoError(t, err) goOn := <-gotDial err = rest.Channels.Get(ablytest.UniqueChannelName(t, "channel")).Publish(context.Background(), "name", "data") assert.NoError(t, err) @@ -985,7 +986,7 @@ func TestRealtimeConn_RTN15c6(t *testing.T) { "expected transition to %v, got %v", ably.ConnectionStateDisconnected, connState.Current) rest, err := ably.NewREST(app.Options()...) - assert.NoError(t, err) + require.NoError(t, err) err = rest.Channels.Get(ablytest.UniqueChannelName(t, "channel")).Publish(context.Background(), "name", "data") assert.NoError(t, err) @@ -1097,7 +1098,7 @@ func TestRealtimeConn_RTN15c7_attached(t *testing.T) { "expected transition to %v, got %v", ably.ConnectionStateDisconnected, connState.Current) rest, err := ably.NewREST(app.Options()...) - assert.NoError(t, err) + require.NoError(t, err) err = rest.Channels.Get(ablytest.UniqueChannelName(t, "channel")).Publish(context.Background(), "name", "data") assert.NoError(t, err) @@ -1165,7 +1166,7 @@ func TestRealtimeConn_RTN15d_MessageRecovery(t *testing.T) { // still be attached and the messages will arrive. rest, err := ably.NewREST(app.Options()...) - assert.NoError(t, err) + require.NoError(t, err) for i := 0; i < 3; i++ { err := rest.Channels.Get(ablytest.UniqueChannelName(t, "test")).Publish(context.Background(), "test", fmt.Sprintf("msg %d", i)) assert.NoError(t, err, @@ -1718,7 +1719,7 @@ func TestRealtimeConn_RTN15h3_Success(t *testing.T) { func TestRealtimeConn_RTN22a_RTN15h2_Integration_ServerInitiatedAuth(t *testing.T) { t.Parallel() - _, restClient := ablytest.NewREST() + app, restClient := ablytest.NewREST() recorder := NewMessageRecorder() authCallbackTokens := []string{} @@ -1730,11 +1731,15 @@ func TestRealtimeConn_RTN22a_RTN15h2_Integration_ServerInitiatedAuth(t *testing. return token, err } - realtime, err := ably.NewRealtime( + // Build from app.Options so the client uses the provisioned app's transport + // (endpoint/port/TLS). A bare WithEndpoint dials the default TLS port, which a + // plaintext local server doesn't serve — the connect would then never succeed + // and the test would hang, a harness artifact rather than a server result. + realtime, err := ably.NewRealtime(app.Options( ably.WithAutoConnect(false), ably.WithDial(recorder.Dial), - ably.WithEndpoint(ablytest.Endpoint), - ably.WithAuthCallback(authCallback)) + ably.WithAuthCallback(authCallback), + )...) assert.NoError(t, err) defer realtime.Close() @@ -1777,7 +1782,7 @@ func TestRealtimeConn_RTN22a_RTN15h2_Integration_ServerInitiatedAuth(t *testing. } func TestRealtimeConn_RTN22_RTC8_Integration_ServerInitiatedAuth(t *testing.T) { - _, restClient := ablytest.NewREST() + app, restClient := ablytest.NewREST() recorder := NewMessageRecorder() authCallbackTokens := []string{} @@ -1791,12 +1796,16 @@ func TestRealtimeConn_RTN22_RTC8_Integration_ServerInitiatedAuth(t *testing.T) { return token, err } - realtime, err := ably.NewRealtime( + // Build from app.Options so the client uses the provisioned app's transport + // (endpoint/port/TLS). A bare WithEndpoint dials the default TLS port, which a + // plaintext local server doesn't serve — the connect would then never succeed + // and the test would hang, a harness artifact rather than a server result. + realtime, err := ably.NewRealtime(app.Options( ably.WithAutoConnect(false), ably.WithDial(recorder.Dial), ably.WithUseBinaryProtocol(false), - ably.WithEndpoint(ablytest.Endpoint), - ably.WithAuthCallback(authCallback)) + ably.WithAuthCallback(authCallback), + )...) assert.NoError(t, err) defer realtime.Close() @@ -3031,11 +3040,15 @@ func TestRealtimeConn_RTC8a_ExplicitAuthorizeWhileConnected(t *testing.T) { } realtimeMsgRecorder := NewMessageRecorder() - realtime, err := ably.NewRealtime( + // Build from app.Options so the client uses the provisioned app's + // transport (endpoint/port/TLS) rather than dialing the default TLS port, + // which a plaintext local server doesn't serve — see the note on the + // RTN22 tests above. + realtime, err := ably.NewRealtime(app.Options( ably.WithAutoConnect(false), - ably.WithEndpoint(ablytest.Endpoint), ably.WithDial(realtimeMsgRecorder.Dial), - ably.WithAuthCallback(authCallback)) + ably.WithAuthCallback(authCallback), + )...) assert.NoError(t, err) defer realtime.Close() diff --git a/ably/rest_channel_integration_test.go b/ably/rest_channel_integration_test.go index 5f60680b..964d94a1 100644 --- a/ably/rest_channel_integration_test.go +++ b/ably/rest_channel_integration_test.go @@ -22,14 +22,15 @@ import ( "github.com/ably/ably-go/internal/ablytest" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestRESTChannel(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) options := app.Options() client, err := ably.NewREST(options...) - assert.NoError(t, err) + require.NoError(t, err) t.Run("Publish", func(t *testing.T) { channel := client.Channels.Get("test_publish_channel") @@ -142,10 +143,10 @@ func TestRESTChannel(t *testing.T) { func TestIdempotentPublishing(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) options := app.Options(ably.WithIdempotentRESTPublishing(true)) client, err := ably.NewREST(options...) - assert.NoError(t, err) + require.NoError(t, err) randomStr, err := ablyutil.BaseID() assert.NoError(t, err) t.Run("when ID is not included (#RSL1k2)", func(t *testing.T) { @@ -309,7 +310,6 @@ func TestIdempotent_retry(t *testing.T) { // failing all others via the test server fallbackHosts := []string{"fallback0", "fallback1", "fallback2"} nopts := []ably.ClientOption{ - ably.WithEndpoint(ablytest.Endpoint), ably.WithTLS(false), ably.WithFallbackHosts(fallbackHosts), ably.WithIdempotentRESTPublishing(true), @@ -317,7 +317,12 @@ func TestIdempotent_retry(t *testing.T) { } serverURL, _ := url.Parse(server.URL) - defaultURL, _ := url.Parse(ably.ApplyOptionsWithDefaults(nopts...).RestURL()) + // Resolve the real destination from app.Options, not nopts alone, so the + // proxy forwards token requests and successful retries to the provisioned + // app's endpoint/port. Against a per-test local child that address is not + // derivable from the endpoint name, so a bare nopts URL would send those + // requests to the wrong server (which 404s the app id). + defaultURL, _ := url.Parse(ably.ApplyOptionsWithDefaults(app.Options(nopts...)...).RestURL()) proxy := func(r *http.Request) (*url.URL, error) { if !strings.HasPrefix(r.URL.Path, "/channels/") { // this is to handle token requests @@ -343,7 +348,7 @@ func TestIdempotent_retry(t *testing.T) { })) client, err := ably.NewREST(app.Options(nopts...)...) - assert.NoError(t, err) + require.NoError(t, err) t.Run("two REST publish retries result in only one message being published'", func(t *testing.T) { channel := client.Channels.Get("idempotent_test_fallback") diff --git a/ably/rest_channel_spec_integration_test.go b/ably/rest_channel_spec_integration_test.go index 96203a13..46eab736 100644 --- a/ably/rest_channel_spec_integration_test.go +++ b/ably/rest_channel_spec_integration_test.go @@ -13,16 +13,17 @@ import ( "github.com/ably/ably-go/internal/ablytest" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestRSL1f1(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) opts := app.Options() // RSL1f opts = append(opts, ably.WithUseTokenAuth(false)) client, err := ably.NewREST(opts...) - assert.NoError(t, err) + require.NoError(t, err) channel := client.Channels.Get("RSL1f") var msgs []*ably.Message size := 10 @@ -47,13 +48,13 @@ func TestRSL1f1(t *testing.T) { func TestRSL1g(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) opts := append(app.Options(), ably.WithUseTokenAuth(true), ) opts = append(opts, ably.WithClientID("some_client_id")) client, err := ably.NewREST(opts...) - assert.NoError(t, err) + require.NoError(t, err) t.Run("RSL1g1b", func(t *testing.T) { channel := client.Channels.Get("RSL1g1b") err := channel.PublishMultiple(context.Background(), []*ably.Message{ @@ -167,7 +168,7 @@ func TestGetChannelLifecycleStatus_RSL8(t *testing.T) { err := channel.Publish(ctx, "event", "data") assert.NoError(t, err) status, err := channel.Status(ctx) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, status.ChannelId) assert.True(t, status.Status.IsActive) assert.Equal(t, "lifecycle:test", status.ChannelId) diff --git a/ably/rest_client_integration_test.go b/ably/rest_client_integration_test.go index 7a7acda5..62d048fd 100644 --- a/ably/rest_client_integration_test.go +++ b/ably/rest_client_integration_test.go @@ -27,6 +27,7 @@ import ( "github.com/ably/ably-go/internal/ablytest" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func newHTTPClientMock(srv *httptest.Server) *http.Client { @@ -63,7 +64,7 @@ func TestRestClient(t *testing.T) { } client, err := ably.NewREST(app.Options(options...)...) - assert.NoError(t, err) + require.NoError(t, err) err = client.Channels.Get(ablytest.UniqueChannelName(t, "test")).Publish(context.Background(), "ping", "pong") assert.NoError(t, err) var anyJson []map[string]interface{} @@ -90,7 +91,7 @@ func TestRestClient(t *testing.T) { } client, err := ably.NewREST(app.Options(options...)...) - assert.NoError(t, err) + require.NoError(t, err) err = client.Channels.Get(ablytest.UniqueChannelName(t, "test")).Publish(context.Background(), "ping", "pong") assert.NoError(t, err) var anyMsgPack []map[string]interface{} @@ -107,9 +108,9 @@ func TestRestClient(t *testing.T) { t.Run("Time", func(t *testing.T) { client, err := ably.NewREST(app.Options()...) - assert.NoError(t, err) + require.NoError(t, err) ti, err := client.Time(context.Background()) - assert.NoError(t, err) + require.NoError(t, err) before := time.Now().Add(2 * time.Second).Unix() after := time.Now().Add(-2 * time.Second).Unix() n := ti.Unix() @@ -153,7 +154,7 @@ func TestRestClient(t *testing.T) { stats[2].IntervalID = intervalFormatFor(lastInterval.Add(-1*time.Minute), ably.StatGranularityMinute) res, err := client.Post(context.Background(), "/stats", &stats, nil) - assert.NoError(t, err) + require.NoError(t, err) res.Body.Close() statsCh := make(chan []*ably.Stats, 1) @@ -246,7 +247,7 @@ func TestRest_RSC7_AblyAgent(t *testing.T) { })) defer server.Close() serverURL, err := url.Parse(server.URL) - assert.NoError(t, err) + require.NoError(t, err) opts := []ably.ClientOption{ ably.WithEndpoint(serverURL.Host), @@ -255,7 +256,7 @@ func TestRest_RSC7_AblyAgent(t *testing.T) { } client, err := ably.NewREST(opts...) - assert.NoError(t, err) + require.NoError(t, err) expectedAgentHeaderValue := ably.AblySDKIdentifier + " " + ably.GoRuntimeIdentifier + " " + ably.GoOSIdentifier() client.Time(context.Background()) @@ -270,7 +271,7 @@ func TestRest_RSC7_AblyAgent(t *testing.T) { })) defer server.Close() serverURL, err := url.Parse(server.URL) - assert.NoError(t, err) + require.NoError(t, err) opts := []ably.ClientOption{ ably.WithEndpoint(serverURL.Host), @@ -282,7 +283,7 @@ func TestRest_RSC7_AblyAgent(t *testing.T) { } client, err := ably.NewREST(opts...) - assert.NoError(t, err) + require.NoError(t, err) expectedAgentHeaderValue := ably.AblySDKIdentifier + " " + ably.GoRuntimeIdentifier + " " + ably.GoOSIdentifier() + " foo/1.2.3" client.Time(context.Background()) @@ -297,7 +298,7 @@ func TestRest_RSC7_AblyAgent(t *testing.T) { })) defer server.Close() serverURL, err := url.Parse(server.URL) - assert.NoError(t, err) + require.NoError(t, err) opts := []ably.ClientOption{ ably.WithEndpoint(serverURL.Host), @@ -309,7 +310,7 @@ func TestRest_RSC7_AblyAgent(t *testing.T) { } client, err := ably.NewREST(opts...) - assert.NoError(t, err) + require.NoError(t, err) expectedAgentHeaderValue := ably.AblySDKIdentifier + " " + ably.GoRuntimeIdentifier + " " + ably.GoOSIdentifier() + " bar" client.Time(context.Background()) @@ -331,7 +332,7 @@ func TestRest_RSC15_HostFallback(t *testing.T) { })) defer server.Close() client, err := ably.NewREST(app.Options(append(options, ably.WithHTTPClient(newHTTPClientMock(server)))...)...) - assert.NoError(t, err) + require.NoError(t, err) err = client.Channels.Get(ablytest.UniqueChannelName(t, "test")).Publish(context.Background(), "ping", "pong") assert.Error(t, err, "expected an error") return retryCount, hosts @@ -375,7 +376,7 @@ func TestRest_RSC15_HostFallback(t *testing.T) { }, } client, err := ably.NewREST(app.Options(append(options, ably.WithHTTPClient(httpClientMock))...)...) - assert.NoError(t, err) + require.NoError(t, err) err = client.Channels.Get(ablytest.UniqueChannelName(t, "test")).Publish(context.Background(), "ping", "pong") <-allHostsTried assert.Contains(t, err.Error(), "context deadline exceeded (Client.Timeout exceeded while awaiting headers)") @@ -409,7 +410,7 @@ func TestRest_RSC15_HostFallback(t *testing.T) { ably.WithUseTokenAuth(true), } client, err := ably.NewREST(app.Options(options...)...) - assert.NoError(t, err) + require.NoError(t, err) tm, err := client.Time(context.Background()) assert.Nil(t, err) assert.NotNil(t, tm) @@ -511,16 +512,18 @@ func TestRest_rememberHostFallback(t *testing.T) { defer server.Close() nopts = []ably.ClientOption{ - ably.WithEndpoint(ablytest.Endpoint), ably.WithTLS(false), ably.WithFallbackHosts([]string{"fallback0", "fallback1", "fallback2"}), ably.WithUseTokenAuth(true), } // set up the proxy to forward all requests except a specific fallback to the server, - // whilst that fallback goes to the regular endpoint + // whilst that fallback goes to the regular endpoint. Resolve that endpoint + // from app.Options (not nopts alone) so it is the provisioned app's + // host/port — for a per-test local child that isn't derivable from the + // endpoint name, and a bare nopts URL would 404 the app id. serverURL, _ := url.Parse(server.URL) - defaultURL, _ := url.Parse(ably.ApplyOptionsWithDefaults(nopts...).RestURL()) + defaultURL, _ := url.Parse(ably.ApplyOptionsWithDefaults(app.Options(nopts...)...).RestURL()) proxy := func(r *http.Request) (*url.URL, error) { if r.URL.Hostname() == "fallback2" { @@ -539,7 +542,7 @@ func TestRest_rememberHostFallback(t *testing.T) { })) client, err := ably.NewREST(app.Options(nopts...)...) - assert.NoError(t, err) + require.NoError(t, err) channel := client.Channels.Get("remember_fallback_host") err = channel.Publish(context.Background(), "ping", "pong") assert.NoError(t, err) @@ -561,9 +564,9 @@ func TestRest_rememberHostFallback(t *testing.T) { func TestRESTChannels_RSN1(t *testing.T) { app, err := ablytest.NewSandbox() - assert.NoError(t, err) + require.NoError(t, err) client, err := ably.NewREST(app.Options()...) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, client.Channels, "expected Channels to be initialized") sample := []struct { @@ -615,7 +618,7 @@ func TestFixConnLeak_ISSUE89(t *testing.T) { opts := app.Options(ably.WithHTTPClient(httpClient)) client, err := ably.NewREST(opts...) - assert.NoError(t, err) + require.NoError(t, err) channel := client.Channels.Get("issue89") for i := 0; i < 10; i++ { err := channel.Publish(context.Background(), fmt.Sprintf("msg_%d", i), fmt.Sprint(i)) @@ -758,8 +761,7 @@ func TestStats_Unit_RSC6b4(t *testing.T) { got = append(got, pages.Items()...) } assert.NoError(t, pages.Err()) - assert.Equal(t, 1, len(got), - "expected: 1; got: %v", got) + require.Equal(t, 1, len(got), "expected: 1; got: %v", got) stats := got[0] assert.Equal(t, "month", stats.Unit, diff --git a/internal/ablytest/ablytest.go b/internal/ablytest/ablytest.go index d88c4d76..b1373247 100644 --- a/internal/ablytest/ablytest.go +++ b/internal/ablytest/ablytest.go @@ -69,6 +69,14 @@ func UniqueChannelName(t *testing.T, base string) string { return ns + name + "-" + channelNameSanitizer.Replace(t.Name()) } +// LocalSandboxURL, when set (via ABLY_LOCAL_SANDBOX_URL, e.g. "http://localhost:9010"), +// points app provisioning at a local sandbox instead of the cloud sandbox. +// Each NewSandbox then POSTs the appspec to /apps, which boots an +// isolated ably-server child and returns that child's keys, endpoint and port; +// clients are configured to talk to that child (see provisionSandbox / Options). +// This is the hook used by the ably-server compatibility harness. +var LocalSandboxURL string + func nonil(err ...error) error { for _, err := range err { if err != nil { @@ -93,6 +101,9 @@ func init() { if s := os.Getenv("ABLY_ENDPOINT"); s != "" { Endpoint = s } + if s := os.Getenv("ABLY_LOCAL_SANDBOX_URL"); s != "" { + LocalSandboxURL = strings.TrimRight(s, "/") + } } func MergeOptions(opts ...[]ably.ClientOption) []ably.ClientOption { diff --git a/internal/ablytest/sandbox.go b/internal/ablytest/sandbox.go index 46da86ec..6c19d870 100644 --- a/internal/ablytest/sandbox.go +++ b/internal/ablytest/sandbox.go @@ -38,6 +38,14 @@ type Key struct { type Config struct { AppID string `json:"appId,omitempty"` Keys []Key `json:"keys"` + + // LocalEndpoint/LocalPort/LocalTLS are populated only when provisioning + // against a local sandbox (see LocalSandboxURL), which runs each app on its + // own child server and returns how to reach it. They are absent (zero) for + // the cloud sandbox, where routing is derived from Sandbox.Endpoint instead. + LocalEndpoint string `json:"endpoint,omitempty"` + LocalPort int `json:"port,omitempty"` + LocalTLS bool `json:"tls,omitempty"` } // Presence describes a presence fixture member provisioned on the @@ -131,6 +139,11 @@ type Sandbox struct { Config *Config Endpoint string client *http.Client + + // local is set when this app was provisioned against a local sandbox + // (see LocalSandboxURL) rather than the cloud sandbox; it selects the local + // routing in Options and the unauthenticated teardown in delete. + local bool } func NewRealtime(opts ...ably.ClientOption) (*Sandbox, *ably.Realtime) { @@ -198,7 +211,9 @@ func provisionSandbox(endpoint string) (*Sandbox, error) { Config: &Config{}, Endpoint: endpoint, client: NewHTTPClient(), + local: LocalSandboxURL != "", } + p := []byte(loadAppSetup().PostApps) const RetryCount = 4 @@ -271,21 +286,18 @@ func (app *Sandbox) NewRealtime(opts ...ably.ClientOption) *ably.Realtime { return client } -// wildcardCapability is the all-resources, all-operations capability. The shared -// appspec provisions several keys with differing capabilities; the tests expect -// a single all-powerful key, so KeyParts selects the one carrying this -// capability (in particular [*]* is required for qualified/derived channels, -// which the default capability does not grant). -const wildcardCapability = `{"[*]*":["*"]}` - func (app *Sandbox) KeyParts() (name, secret string) { key := app.wildcardKey() return app.Config.AppID + "." + key.ID, key.Value } +// wildcardKey returns the all-powerful key. The shared appspec provisions +// several keys with differing capabilities; the tests expect a single key that +// grants every operation on every resource, including the qualified/derived +// namespace ("[*]*") that the plain "*" resource does not cover. func (app *Sandbox) wildcardKey() Key { for _, k := range app.Config.Keys { - if k.Capability == wildcardCapability { + if grantsWildcard(k.Capability) { return k } } @@ -294,6 +306,22 @@ func (app *Sandbox) wildcardKey() Key { return app.Config.Keys[0] } +// grantsWildcard reports whether a capability grants all operations ("*") on +// the all-resources qualifier ("[*]*"). It parses the capability rather than +// string-matching so it is insensitive to JSON whitespace and key ordering. +func grantsWildcard(capability string) bool { + var caps map[string][]string + if json.Unmarshal([]byte(capability), &caps) != nil { + return false + } + for _, op := range caps["[*]*"] { + if op == "*" { + return true + } + } + return false +} + func (app *Sandbox) Key() string { name, secret := app.KeyParts() return name + ":" + secret @@ -312,6 +340,20 @@ func (app *Sandbox) Options(opts ...ably.ClientOption) []ably.ClientOption { ably.WithLogLevel(DefaultLogLevel), } + // local sandbox: route to the app's child server (its own host/port, + // plain ws/http), overriding the cloud endpoint set above. Basic auth is + // allowed without TLS since the child terminates plaintext. + if app.local { + appOpts = append(appOpts, + ably.WithEndpoint(app.Config.LocalEndpoint), + ably.WithTLS(app.Config.LocalTLS), + ably.WithPort(app.Config.LocalPort), + ) + if !app.Config.LocalTLS { + appOpts = append(appOpts, ably.WithInsecureAllowBasicAuthWithoutTLS()) + } + } + // If opts want to record round trips inject the recording transport // via TransportHijacker interface. if httpClient := ClientOptionsInspector.HTTPClient(opts); httpClient != nil { @@ -326,6 +368,9 @@ func (app *Sandbox) Options(opts ...ably.ClientOption) []ably.ClientOption { } func (app *Sandbox) URL(paths ...string) string { + if app.local { + return LocalSandboxURL + "/" + path.Join(paths...) + } if strings.HasPrefix(app.Endpoint, "nonprod:") { namespace := strings.TrimPrefix(app.Endpoint, "nonprod:") return fmt.Sprintf("https://%s.realtime.ably-nonprod.net/%s", namespace, path.Join(paths...))