diff --git a/ably/export_test.go b/ably/export_test.go index c23640d7..1b006789 100644 --- a/ably/export_test.go +++ b/ably/export_test.go @@ -16,6 +16,14 @@ func GetEnvFallbackHosts(env string) []string { return getEnvFallbackHosts(env) } +func GetEndpointFallbackHosts(endpoint string) []string { + return getEndpointFallbackHosts(endpoint) +} + +func (opts *clientOptions) GetEndpoint() string { + return opts.getEndpoint() +} + func (opts *clientOptions) GetRestHost() string { return opts.getRestHost() } diff --git a/ably/options.go b/ably/options.go index 1bf26783..7ec7dff6 100644 --- a/ably/options.go +++ b/ably/options.go @@ -12,6 +12,7 @@ import ( "net/http/httptrace" "net/url" "os" + "regexp" "strconv" "strings" "time" @@ -23,6 +24,9 @@ const ( protocolJSON = "application/json" protocolMsgPack = "application/x-msgpack" + // endpoint is the default routing policy used to connect to Ably + endpoint = "main" + // restHost is the primary ably host. restHost = "rest.ably.io" // realtimeHost is the primary ably host. @@ -37,6 +41,7 @@ const ( ) var defaultOptions = clientOptions{ + Endpoint: endpoint, RESTHost: restHost, FallbackHosts: defaultFallbackHosts(), HTTPMaxRetryCount: 3, @@ -59,13 +64,24 @@ var defaultOptions = clientOptions{ } func defaultFallbackHosts() []string { - return []string{ - "a.ably-realtime.com", - "b.ably-realtime.com", - "c.ably-realtime.com", - "d.ably-realtime.com", - "e.ably-realtime.com", + return endpointFallbacks("main", "ably-realtime.com") +} + +func getEndpointFallbackHosts(endpoint string) []string { + if match := regexp.MustCompile(`^nonprod:(.*)$`).FindStringSubmatch(endpoint); match != nil { + namespace := match[1] + return endpointFallbacks(namespace, "ably-realtime-nonprod.com") + } + + return endpointFallbacks(endpoint, "ably-realtime.com") +} + +func endpointFallbacks(namespace, root string) []string { + var fallbacks []string + for _, id := range []string{"a", "b", "c", "d", "e"} { + fallbacks = append(fallbacks, fmt.Sprintf("%s.%s.fallback.%s", namespace, id, root)) } + return fallbacks } func getEnvFallbackHosts(env string) []string { @@ -244,8 +260,18 @@ type clientOptions struct { // authOptions Embedded an [ably.authOptions] object (TO3j). authOptions - // RESTHost enables a non-default Ably host to be specified. For development environments only. - // The default value is rest.ably.io (RSC12, TO3k2). + // TODO: update docs with guidance + // Endpoint specifies the domain used to connect to Ably. It has the following properties: + // If the endpoint option is a production routing policy name of the form [name] then the primary domain is [name].realtime.ably.net (REC1b4). + // If the endpoint option is a non-production routing policy name of the form nonprod:[name] then the primary domain is [name].realtime.ably-nonprod.net (REC1b3). + // If the endpoint option is a domain name, determined by it containing at least one period (.) then the primary domain is the value of the endpoint option (REC1b2). + // If any one of the deprecated options environment, restHost, realtimeHost are also specified then the options as a set are invalid (REC1b1). + Endpoint string + + // Deprecated: this property is deprecated and will be removed in a future version. + // RESTHost enables a non-default Ably host to be specified. For + // development environments only. The default value is rest.ably.io (RSC12, + // TO3k2). RESTHost string // Deprecated: this property is deprecated and will be removed in a future version. @@ -257,10 +283,12 @@ type clientOptions struct { // please specify them here (RSC15b, RSC15a, TO3k6). FallbackHosts []string + // Deprecated: this property is deprecated and will be removed in a future version. // RealtimeHost enables a non-default Ably host to be specified for realtime connections. // For development environments only. The default value is realtime.ably.io (RTC1d, TO3k3). RealtimeHost string + // Deprecated: this property is deprecated and will be removed in a future version. // Environment enables a custom environment to be used with the Ably service. // Optional: prefixes both hostname with the environment string (RSC15b, TO3k1). Environment string @@ -410,7 +438,15 @@ type clientOptions struct { LogHandler Logger } +// TODO: update for endpoint func (opts *clientOptions) validate() error { + if !empty(opts.Endpoint) && (!empty(opts.Environment) || !empty(opts.RealtimeHost) || !empty(opts.RESTHost)) { + err := errors.New("invalid client option: cannot use endpoint with any of environment, realtimeHost or restHost") + logger := opts.LogHandler + logger.Printf(LogError, "Invalid client options : %v", err.Error()) + return err + } + _, err := opts.getFallbackHosts() if err != nil { logger := opts.LogHandler @@ -446,7 +482,33 @@ func (opts *clientOptions) activePort() (port int, isDefault bool) { return } +func (opts *clientOptions) usingLegacyOpts() bool { + return empty(opts.Endpoint) && (!empty(opts.Environment) || !empty(opts.RESTHost) || !empty(opts.RealtimeHost)) +} + +func (opts *clientOptions) getEndpoint() string { + endpoint := opts.Endpoint + if empty(endpoint) { + endpoint = defaultOptions.Endpoint + } + + if strings.Contains(endpoint, ".") { + return endpoint + } + + if match := regexp.MustCompile(`^nonprod:(.*)$`).FindStringSubmatch(endpoint); match != nil { + namespace := match[1] + return fmt.Sprintf("%s.realtime.ably-nonprod.net", namespace) + } + + return fmt.Sprintf("%s.realtime.ably.net", endpoint) +} + func (opts *clientOptions) getRestHost() string { + if !opts.usingLegacyOpts() { + return opts.getEndpoint() + } + if !empty(opts.RESTHost) { return opts.RESTHost } @@ -457,6 +519,10 @@ func (opts *clientOptions) getRestHost() string { } func (opts *clientOptions) getRealtimeHost() string { + if !opts.usingLegacyOpts() { + return opts.getEndpoint() + } + if !empty(opts.RealtimeHost) { return opts.RealtimeHost } @@ -475,6 +541,7 @@ func empty(s string) bool { return len(strings.TrimSpace(s)) == 0 } +// TODO: update for endpoint func (opts *clientOptions) restURL() (restUrl string) { baseUrl := opts.getRestHost() _, _, err := net.SplitHostPort(baseUrl) @@ -501,9 +568,11 @@ func (opts *clientOptions) realtimeURL(realtimeHost string) (realtimeUrl string) return "wss://" + baseUrl } +// TODO: update for endpoint func (opts *clientOptions) getFallbackHosts() ([]string, error) { logger := opts.LogHandler _, isDefaultPort := opts.activePort() + if opts.FallbackHostsUseDefault { if opts.FallbackHosts != nil { return nil, errors.New("fallbackHosts and fallbackHostsUseDefault cannot both be set") @@ -517,12 +586,21 @@ func (opts *clientOptions) getFallbackHosts() ([]string, error) { logger.Printf(LogWarning, "Deprecated fallbackHostsUseDefault : using default fallbackhosts") return defaultOptions.FallbackHosts, nil } + + if opts.FallbackHosts == nil && !empty(opts.Endpoint) { + if strings.Contains(opts.Endpoint, ".") { + return nil, errors.New("fallbackHosts must be set is endpoint is fully qualified") + } + return getEndpointFallbackHosts(opts.Endpoint), nil + } + if opts.FallbackHosts == nil && empty(opts.RESTHost) && empty(opts.RealtimeHost) && isDefaultPort { if opts.isProductionEnvironment() { return defaultOptions.FallbackHosts, nil } return getEnvFallbackHosts(opts.Environment), nil } + return opts.FallbackHosts, nil } @@ -1066,6 +1144,18 @@ func WithEchoMessages(echo bool) ClientOption { } } +// WithEndpoint is used for setting Endpoint using [ably.ClientOption]. +// Endpoint specifies the domain used to connect to Ably. It has the following properties: +// If the endpoint option is a production routing policy name of the form [name] then the primary domain is [name].realtime.ably.net (REC1b4). +// If the endpoint option is a non-production routing policy name of the form nonprod:[name] then the primary domain is [name].realtime.ably-nonprod.net (REC1b3). +// If the endpoint option is a domain name, determined by it containing at least one period (.) then the primary domain is the value of the endpoint option (REC1b2). +// If any one of the deprecated options environment, restHost, realtimeHost are also specified then the options as a set are invalid (REC1b1). +func WithEndpoint(env string) ClientOption { + return func(os *clientOptions) { + os.Endpoint = env + } +} + // WithEnvironment is used for setting Environment using [ably.ClientOption]. // Environment enables a custom environment to be used with the Ably service. // Optional: prefixes both hostname with the environment string (RSC15b, TO3k1). @@ -1319,6 +1409,7 @@ func WithDial(dial func(protocol string, u *url.URL, timeout time.Duration) (con func applyOptionsWithDefaults(opts ...ClientOption) *clientOptions { to := defaultOptions // No need to set hosts by default + to.Endpoint = "" to.RESTHost = "" to.RealtimeHost = "" to.FallbackHosts = nil diff --git a/ably/options_test.go b/ably/options_test.go index 855076bd..b53899bd 100644 --- a/ably/options_test.go +++ b/ably/options_test.go @@ -15,16 +15,42 @@ import ( func TestDefaultFallbacks_RSC15h(t *testing.T) { expectedFallBackHosts := []string{ - "a.ably-realtime.com", - "b.ably-realtime.com", - "c.ably-realtime.com", - "d.ably-realtime.com", - "e.ably-realtime.com", + "main.a.fallback.ably-realtime.com", + "main.b.fallback.ably-realtime.com", + "main.c.fallback.ably-realtime.com", + "main.d.fallback.ably-realtime.com", + "main.e.fallback.ably-realtime.com", } hosts := ably.DefaultFallbackHosts() assert.Equal(t, expectedFallBackHosts, hosts) } +func TestEndpointFallbacks(t *testing.T) { + t.Run("standard endpoint", func(t *testing.T) { + expectedFallBackHosts := []string{ + "acme.a.fallback.ably-realtime.com", + "acme.b.fallback.ably-realtime.com", + "acme.c.fallback.ably-realtime.com", + "acme.d.fallback.ably-realtime.com", + "acme.e.fallback.ably-realtime.com", + } + hosts := ably.GetEndpointFallbackHosts("acme") + assert.Equal(t, expectedFallBackHosts, hosts) + }) + + t.Run("nonprod endpoint", func(t *testing.T) { + expectedFallBackHosts := []string{ + "acme.a.fallback.ably-realtime-nonprod.com", + "acme.b.fallback.ably-realtime-nonprod.com", + "acme.c.fallback.ably-realtime-nonprod.com", + "acme.d.fallback.ably-realtime-nonprod.com", + "acme.e.fallback.ably-realtime-nonprod.com", + } + hosts := ably.GetEndpointFallbackHosts("nonprod:acme") + assert.Equal(t, expectedFallBackHosts, hosts) + }) +} + func TestEnvFallbackHosts_RSC15i(t *testing.T) { expectedFallBackHosts := []string{ "sandbox-a-fallback.ably-realtime.com", @@ -42,11 +68,11 @@ func TestInternetConnectionCheck_RTN17c(t *testing.T) { assert.True(t, clientOptions.HasActiveInternetConnection()) } -func TestFallbackHosts_RSC15b(t *testing.T) { - t.Run("RSC15e RSC15g3 with default options", func(t *testing.T) { +func TestHosts_RSC15b(t *testing.T) { + t.Run("with default options", func(t *testing.T) { clientOptions := ably.NewClientOptions() - assert.Equal(t, "realtime.ably.io", clientOptions.GetRealtimeHost()) - assert.Equal(t, "rest.ably.io", clientOptions.GetRestHost()) + assert.Equal(t, "main.realtime.ably.net", clientOptions.GetRealtimeHost()) + assert.Equal(t, "main.realtime.ably.net", clientOptions.GetRestHost()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -55,101 +81,152 @@ func TestFallbackHosts_RSC15b(t *testing.T) { assert.Equal(t, ably.DefaultFallbackHosts(), fallbackHosts) }) - t.Run("RSC15h with production environment", func(t *testing.T) { - clientOptions := ably.NewClientOptions(ably.WithEnvironment("production")) - assert.Equal(t, "realtime.ably.io", clientOptions.GetRealtimeHost()) - assert.Equal(t, "rest.ably.io", clientOptions.GetRestHost()) + t.Run("with endpoint as a custom routing policy name", func(t *testing.T) { + clientOptions := ably.NewClientOptions(ably.WithEndpoint("acme")) + assert.Equal(t, "acme.realtime.ably.net", clientOptions.GetRestHost()) + assert.Equal(t, "acme.realtime.ably.net", clientOptions.GetRealtimeHost()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) assert.True(t, isDefaultPort) fallbackHosts, _ := clientOptions.GetFallbackHosts() - assert.Equal(t, ably.DefaultFallbackHosts(), fallbackHosts) + assert.Equal(t, ably.GetEndpointFallbackHosts("acme"), fallbackHosts) }) - t.Run("RSC15g2 RTC1e with custom environment", func(t *testing.T) { - clientOptions := ably.NewClientOptions(ably.WithEnvironment("sandbox")) - assert.Equal(t, "sandbox-realtime.ably.io", clientOptions.GetRealtimeHost()) - assert.Equal(t, "sandbox-rest.ably.io", clientOptions.GetRestHost()) + t.Run("with endpoint as a nonprod routing policy name", func(t *testing.T) { + clientOptions := ably.NewClientOptions(ably.WithEndpoint("nonprod:acme")) + assert.Equal(t, "acme.realtime.ably-nonprod.net", clientOptions.GetRestHost()) + assert.Equal(t, "acme.realtime.ably-nonprod.net", clientOptions.GetRealtimeHost()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) assert.True(t, isDefaultPort) fallbackHosts, _ := clientOptions.GetFallbackHosts() - assert.Equal(t, ably.GetEnvFallbackHosts("sandbox"), fallbackHosts) - }) - - t.Run("RSC15g4 RTC1e with custom environment and fallbackHostUseDefault", func(t *testing.T) { - clientOptions := ably.NewClientOptions(ably.WithEnvironment("sandbox"), ably.WithFallbackHostsUseDefault(true)) - assert.Equal(t, "sandbox-realtime.ably.io", clientOptions.GetRealtimeHost()) - assert.Equal(t, "sandbox-rest.ably.io", clientOptions.GetRestHost()) - assert.False(t, clientOptions.NoTLS) - port, isDefaultPort := clientOptions.ActivePort() - assert.Equal(t, 443, port) - assert.True(t, isDefaultPort) - fallbackHosts, _ := clientOptions.GetFallbackHosts() - assert.Equal(t, ably.DefaultFallbackHosts(), fallbackHosts) - }) - - t.Run("RSC11b RTN17b RTC1e with custom environment and non default ports", func(t *testing.T) { - clientOptions := ably.NewClientOptions( - ably.WithEnvironment("local"), - ably.WithPort(8080), - ably.WithTLSPort(8081), - ) - assert.Equal(t, "local-realtime.ably.io", clientOptions.GetRealtimeHost()) - assert.Equal(t, "local-rest.ably.io", clientOptions.GetRestHost()) - assert.False(t, clientOptions.NoTLS) - port, isDefaultPort := clientOptions.ActivePort() - assert.Equal(t, 8081, port) - assert.False(t, isDefaultPort) - fallbackHosts, _ := clientOptions.GetFallbackHosts() - assert.Nil(t, fallbackHosts) + assert.Equal(t, ably.GetEndpointFallbackHosts("nonprod:acme"), fallbackHosts) }) - t.Run("RSC11 with custom rest host", func(t *testing.T) { - clientOptions := ably.NewClientOptions(ably.WithRESTHost("test.org")) - assert.Equal(t, "test.org", clientOptions.GetRealtimeHost()) - assert.Equal(t, "test.org", clientOptions.GetRestHost()) + t.Run("with endpoint as a fqdn with no fallbackHosts specified", func(t *testing.T) { + clientOptions := ably.NewClientOptions(ably.WithEndpoint("foo.example.com")) + assert.Equal(t, "foo.example.com", clientOptions.GetRestHost()) + assert.Equal(t, "foo.example.com", clientOptions.GetRealtimeHost()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) assert.True(t, isDefaultPort) - fallbackHosts, _ := clientOptions.GetFallbackHosts() - assert.Nil(t, fallbackHosts) + _, err := clientOptions.GetFallbackHosts() + assert.Error(t, err) }) - t.Run("RSC11 with custom rest host and realtime host", func(t *testing.T) { - clientOptions := ably.NewClientOptions(ably.WithRealtimeHost("ws.test.org"), ably.WithRESTHost("test.org")) - assert.Equal(t, "ws.test.org", clientOptions.GetRealtimeHost()) - assert.Equal(t, "test.org", clientOptions.GetRestHost()) + t.Run("with endpoint as a fqdn with fallbackHosts specified", func(t *testing.T) { + clientOptions := ably.NewClientOptions(ably.WithEndpoint("foo.example.com"), ably.WithFallbackHosts([]string{"fallback.foo.example.com"})) + assert.Equal(t, "foo.example.com", clientOptions.GetRestHost()) + assert.Equal(t, "foo.example.com", clientOptions.GetRealtimeHost()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) assert.True(t, isDefaultPort) - fallbackHosts, _ := clientOptions.GetFallbackHosts() - assert.Nil(t, fallbackHosts) + fallbackHosts, err := clientOptions.GetFallbackHosts() + assert.NoError(t, err) + assert.Equal(t, []string{"fallback.foo.example.com"}, fallbackHosts) }) - t.Run("RSC15b with custom rest host and realtime host and fallbackHostsUseDefault", func(t *testing.T) { - clientOptions := ably.NewClientOptions( - ably.WithRealtimeHost("ws.test.org"), - ably.WithRESTHost("test.org"), - ably.WithFallbackHostsUseDefault(true)) - assert.Equal(t, "ws.test.org", clientOptions.GetRealtimeHost()) - assert.Equal(t, "test.org", clientOptions.GetRestHost()) - assert.False(t, clientOptions.NoTLS) - port, isDefaultPort := clientOptions.ActivePort() - assert.Equal(t, 443, port) - assert.True(t, isDefaultPort) - fallbackHosts, _ := clientOptions.GetFallbackHosts() - assert.Equal(t, ably.DefaultFallbackHosts(), fallbackHosts) + t.Run("legacy support", func(t *testing.T) { + t.Run("RSC15h with production environment", func(t *testing.T) { + clientOptions := ably.NewClientOptions(ably.WithEnvironment("production")) + assert.Equal(t, "realtime.ably.io", clientOptions.GetRealtimeHost()) + assert.Equal(t, "rest.ably.io", clientOptions.GetRestHost()) + assert.False(t, clientOptions.NoTLS) + port, isDefaultPort := clientOptions.ActivePort() + assert.Equal(t, 443, port) + assert.True(t, isDefaultPort) + fallbackHosts, _ := clientOptions.GetFallbackHosts() + assert.Equal(t, ably.DefaultFallbackHosts(), fallbackHosts) + }) + + t.Run("RSC15g2 RTC1e with custom environment", func(t *testing.T) { + clientOptions := ably.NewClientOptions(ably.WithEnvironment("sandbox")) + assert.Equal(t, "sandbox-realtime.ably.io", clientOptions.GetRealtimeHost()) + assert.Equal(t, "sandbox-rest.ably.io", clientOptions.GetRestHost()) + assert.False(t, clientOptions.NoTLS) + port, isDefaultPort := clientOptions.ActivePort() + assert.Equal(t, 443, port) + assert.True(t, isDefaultPort) + fallbackHosts, _ := clientOptions.GetFallbackHosts() + assert.Equal(t, ably.GetEnvFallbackHosts("sandbox"), fallbackHosts) + }) + + t.Run("RSC15g4 RTC1e with custom environment and fallbackHostUseDefault", func(t *testing.T) { + clientOptions := ably.NewClientOptions(ably.WithEnvironment("sandbox"), ably.WithFallbackHostsUseDefault(true)) + assert.Equal(t, "sandbox-realtime.ably.io", clientOptions.GetRealtimeHost()) + assert.Equal(t, "sandbox-rest.ably.io", clientOptions.GetRestHost()) + assert.False(t, clientOptions.NoTLS) + port, isDefaultPort := clientOptions.ActivePort() + assert.Equal(t, 443, port) + assert.True(t, isDefaultPort) + fallbackHosts, _ := clientOptions.GetFallbackHosts() + assert.Equal(t, ably.DefaultFallbackHosts(), fallbackHosts) + }) + + t.Run("RSC11b RTN17b RTC1e with legacy custom environment and non default ports", func(t *testing.T) { + clientOptions := ably.NewClientOptions( + ably.WithEnvironment("local"), + ably.WithPort(8080), + ably.WithTLSPort(8081), + ) + assert.Equal(t, "local-realtime.ably.io", clientOptions.GetRealtimeHost()) + assert.Equal(t, "local-rest.ably.io", clientOptions.GetRestHost()) + assert.False(t, clientOptions.NoTLS) + port, isDefaultPort := clientOptions.ActivePort() + assert.Equal(t, 8081, port) + assert.False(t, isDefaultPort) + fallbackHosts, _ := clientOptions.GetFallbackHosts() + assert.Nil(t, fallbackHosts) + }) + + t.Run("RSC11 with custom rest host", func(t *testing.T) { + clientOptions := ably.NewClientOptions(ably.WithRESTHost("test.org")) + assert.Equal(t, "test.org", clientOptions.GetRealtimeHost()) + assert.Equal(t, "test.org", clientOptions.GetRestHost()) + assert.False(t, clientOptions.NoTLS) + port, isDefaultPort := clientOptions.ActivePort() + assert.Equal(t, 443, port) + assert.True(t, isDefaultPort) + fallbackHosts, _ := clientOptions.GetFallbackHosts() + assert.Nil(t, fallbackHosts) + }) + + t.Run("RSC11 with custom rest host and realtime host", func(t *testing.T) { + clientOptions := ably.NewClientOptions(ably.WithRealtimeHost("ws.test.org"), ably.WithRESTHost("test.org")) + assert.Equal(t, "ws.test.org", clientOptions.GetRealtimeHost()) + assert.Equal(t, "test.org", clientOptions.GetRestHost()) + assert.False(t, clientOptions.NoTLS) + port, isDefaultPort := clientOptions.ActivePort() + assert.Equal(t, 443, port) + assert.True(t, isDefaultPort) + fallbackHosts, _ := clientOptions.GetFallbackHosts() + assert.Nil(t, fallbackHosts) + }) + + t.Run("RSC15b with custom rest host and realtime host and fallbackHostsUseDefault", func(t *testing.T) { + clientOptions := ably.NewClientOptions( + ably.WithRealtimeHost("ws.test.org"), + ably.WithRESTHost("test.org"), + ably.WithFallbackHostsUseDefault(true)) + assert.Equal(t, "ws.test.org", clientOptions.GetRealtimeHost()) + assert.Equal(t, "test.org", clientOptions.GetRestHost()) + assert.False(t, clientOptions.NoTLS) + port, isDefaultPort := clientOptions.ActivePort() + assert.Equal(t, 443, port) + assert.True(t, isDefaultPort) + fallbackHosts, _ := clientOptions.GetFallbackHosts() + assert.Equal(t, ably.DefaultFallbackHosts(), fallbackHosts) + }) }) t.Run("RSC15g1 with fallbackHosts", func(t *testing.T) { clientOptions := ably.NewClientOptions(ably.WithFallbackHosts([]string{"a.example.com", "b.example.com"})) - assert.Equal(t, "realtime.ably.io", clientOptions.GetRealtimeHost()) - assert.Equal(t, "rest.ably.io", clientOptions.GetRestHost()) + assert.Equal(t, "main.realtime.ably.net", clientOptions.GetRealtimeHost()) + assert.Equal(t, "main.realtime.ably.net", clientOptions.GetRestHost()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -204,6 +281,19 @@ func TestClientOptions(t *testing.T) { assert.Error(t, err, "expected an error") }) + t.Run("must return error on invalid combinations", func(t *testing.T) { + _, err := ably.NewREST([]ably.ClientOption{ably.WithEndpoint("acme"), ably.WithEnvironment("acme")}...) + assert.Error(t, err, + "expected an error") + + _, err = ably.NewREST([]ably.ClientOption{ably.WithEndpoint("acme"), ably.WithRealtimeHost("foo.example.com")}...) + assert.Error(t, err, + "expected an error") + + _, err = ably.NewREST([]ably.ClientOption{ably.WithEndpoint("acme"), ably.WithRESTHost("foo.example.com")}...) + assert.Error(t, err, + "expected an error") + }) } func TestScopeParams(t *testing.T) { diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index 284ee0bf..91a2bfda 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -388,6 +388,7 @@ func (c *Connection) connectWith(arg connArgs) (result, error) { var conn conn primaryHost := c.opts.getRealtimeHost() + hosts := []string{primaryHost} fallbackHosts, err := c.opts.getFallbackHosts() if err != nil { diff --git a/ablytest/ablytest.go b/ablytest/ablytest.go index fc7b8363..7a69f844 100644 --- a/ablytest/ablytest.go +++ b/ablytest/ablytest.go @@ -18,6 +18,7 @@ var Timeout = 30 * time.Second var NoBinaryProtocol bool var DefaultLogLevel = ably.LogNone var Environment = "sandbox" +var Endpoint = "nonprod:sandbox" func nonil(err ...error) error { for _, err := range err {