Repository: ably/ably-go
Pull request #201 (2020-10-07 11:27:24)

Remove things from public API that aren't in the spec
Picking some low-hanging fruit for sticking to the spec.


Patch:
diff --git a/ably/auth.go b/ably/auth.go
index 18b94d6..72576b7 100644
--- a/ably/auth.go
+++ b/ably/auth.go
@@ -180,7 +180,7 @@ func (a *Auth) RequestToken(params *TokenParams, opts AuthOptions) (*TokenDetail
 }
 
 func (a *Auth) requestToken(params *TokenParams, opts *authOptions) (tok *TokenDetails, tokReqClientID string, err error) {
-	log := a.logger().Sugar()
+	log := a.logger().sugar()
 	switch {
 	case opts != nil && opts.Token != "":
 		log.Verbose("Auth: found token in AuthOptions")
@@ -246,7 +246,7 @@ func (a *Auth) requestToken(params *TokenParams, opts *authOptions) (tok *TokenD
 		tokReq = req
 	}
 	tok = &TokenDetails{}
-	r := &Request{
+	r := &request{
 		Method: "POST",
 		Path:   "/keys/" + tokReq.KeyName + "/requestToken",
 		In:     tokReq,
@@ -286,7 +286,7 @@ func (a *Auth) Authorize(params *TokenParams, setOpts AuthOptions) (*TokenDetail
 }
 
 func (a *Auth) authorize(params *TokenParams, opts *authOptions, force bool) (*TokenDetails, error) {
-	log := a.logger().Sugar()
+	log := a.logger().sugar()
 	switch tok := a.token(); {
 	case tok != nil && !force && (tok.Expires == 0 || !tok.expired(a.opts().Now())):
 		return tok, nil
@@ -323,7 +323,7 @@ func (a *Auth) authorize(params *TokenParams, opts *authOptions, force bool) (*T
 func (a *Auth) reauthorize() (*TokenDetails, error) {
 	a.mtx.Lock()
 	defer a.mtx.Unlock()
-	a.logger().Sugar().Info("Auth: reauthorize")
+	a.logger().sugar().Info("Auth: reauthorize")
 	return a.authorize(a.params, nil, true)
 }
 
@@ -340,8 +340,8 @@ func (a *Auth) setDefaults(opts *authOptions, req *TokenRequest) error {
 	if req.Nonce == "" {
 		req.Nonce = randomString(32)
 	}
-	if req.RawCapability == "" {
-		req.RawCapability = (Capability{"*": {"*"}}).Encode()
+	if req.Capability == "" {
+		req.Capability = `{"*":["*"]}`
 	}
 	if req.TTL == 0 {
 		req.TTL = 60 * 60 * 1000
diff --git a/ably/http_paginated_response.go b/ably/http_paginated_response.go
index 47277d1..8463a91 100644
--- a/ably/http_paginated_response.go
+++ b/ably/http_paginated_response.go
@@ -27,7 +27,7 @@ func decodeHTTPPaginatedResult(opts *proto.ChannelOptions, typ reflect.Type, res
 }
 
 func newHTTPPaginatedResult(path string, params *PaginateParams,
-	query QueryFunc, log *LoggerOptions) (*HTTPPaginatedResponse, error) {
+	query queryFunc, log *LoggerOptions) (*HTTPPaginatedResponse, error) {
 	p, err := newPaginatedResult(nil, paginatedRequest{typ: arrayTyp, path: path, params: params, query: query, logger: log, respCheck: func(_ *http.Response) error {
 		return nil
 	}, decoder: decodeHTTPPaginatedResult})
diff --git a/ably/logger.go b/ably/logger.go
index 3f96aa5..cbe7ce6 100644
--- a/ably/logger.go
+++ b/ably/logger.go
@@ -61,8 +61,8 @@ func (l LoggerOptions) GetLogger() Logger {
 	return defaultLog.Logger
 }
 
-func (l LoggerOptions) Sugar() *SugaredLogger {
-	return &SugaredLogger{LoggerOptions: l}
+func (l LoggerOptions) sugar() *sugaredLogger {
+	return &sugaredLogger{LoggerOptions: l}
 }
 
 // Logger is an interface for ably loggers.
@@ -87,46 +87,46 @@ func (s *StdLogger) Print(level LogLevel, v ...interface{}) {
 	}
 }
 
-type SugaredLogger struct {
+type sugaredLogger struct {
 	LoggerOptions
 }
 
-func (s SugaredLogger) Error(v ...interface{}) {
+func (s sugaredLogger) Error(v ...interface{}) {
 	s.Print(LogError, v...)
 }
 
-func (s SugaredLogger) Errorf(fmt string, v ...interface{}) {
+func (s sugaredLogger) Errorf(fmt string, v ...interface{}) {
 	s.Printf(LogError, fmt, v...)
 }
 
-func (s SugaredLogger) Warn(v ...interface{}) {
+func (s sugaredLogger) Warn(v ...interface{}) {
 	s.Print(LogWarning, v...)
 }
 
-func (s SugaredLogger) Warnf(fmt string, v ...interface{}) {
+func (s sugaredLogger) Warnf(fmt string, v ...interface{}) {
 	s.Printf(LogWarning, fmt, v...)
 }
 
-func (s SugaredLogger) Info(v ...interface{}) {
+func (s sugaredLogger) Info(v ...interface{}) {
 	s.Print(LogInfo, v...)
 }
 
-func (s SugaredLogger) Infof(fmt string, v ...interface{}) {
+func (s sugaredLogger) Infof(fmt string, v ...interface{}) {
 	s.Printf(LogInfo, fmt, v...)
 }
 
-func (s SugaredLogger) Verbose(v ...interface{}) {
+func (s sugaredLogger) Verbose(v ...interface{}) {
 	s.Print(LogVerbose, v...)
 }
 
-func (s SugaredLogger) Verbosef(fmt string, v ...interface{}) {
+func (s sugaredLogger) Verbosef(fmt string, v ...interface{}) {
 	s.Printf(LogVerbose, fmt, v...)
 }
 
-func (s SugaredLogger) Debugf(fmt string, v ...interface{}) {
+func (s sugaredLogger) Debugf(fmt string, v ...interface{}) {
 	s.Printf(LogDebug, fmt, v...)
 }
 
-func (s SugaredLogger) Debug(v ...interface{}) {
+func (s sugaredLogger) Debug(v ...interface{}) {
 	s.Print(LogDebug, v...)
 }
diff --git a/ably/options.go b/ably/options.go
index f7b55e3..c0b9b7b 100644
--- a/ably/options.go
+++ b/ably/options.go
@@ -19,12 +19,12 @@ const (
 	protocolJSON    = "application/json"
 	protocolMsgPack = "application/x-msgpack"
 
-	// RestHost is the primary ably host .
-	RestHost = "rest.ably.io"
+	// restHost is the primary ably host .
+	restHost = "rest.ably.io"
 )
 
 var defaultOptions = clientOptions{
-	RestHost:                 RestHost,
+	RestHost:                 restHost,
 	HTTPMaxRetryCount:        3,
 	HTTPRequestTimeout:       10 * time.Second,
 	RealtimeHost:             "realtime.ably.io",
@@ -40,7 +40,7 @@ var defaultOptions = clientOptions{
 	After:                    ablyutil.After,
 }
 
-func DefaultFallbackHosts() []string {
+func defaultFallbackHosts() []string {
 	return []string{
 		"a.ably-realtime.com",
 		"b.ably-realtime.com",
diff --git a/ably/paginated_result.go b/ably/paginated_result.go
index 8579ef4..ee5f0c3 100644
--- a/ably/paginated_result.go
+++ b/ably/paginated_result.go
@@ -26,9 +26,9 @@ func (err errInvalidType) Error() string {
 	return "requested value of incompatible type: " + err.typ.String()
 }
 
-// QueryFunc queries the given URL and gives non-nil HTTP response if no error
+// queryFunc queries the given URL and gives non-nil HTTP response if no error
 // occurred.
-type QueryFunc func(url string) (*http.Response, error)
+type queryFunc func(url string) (*http.Response, error)
 
 // PaginatedResult represents a single page coming back from the REST API.
 // Any call to create a new page will generate a new instance.
@@ -52,7 +52,7 @@ type paginatedRequest struct {
 	typ       reflect.Type
 	path      string
 	params    *PaginateParams
-	query     QueryFunc
+	query     queryFunc
 	logger    *LoggerOptions
 	respCheck func(*http.Response) error
 	decoder   func(*proto.ChannelOptions, reflect.Type, *http.Response) (interface{}, error)
@@ -132,7 +132,7 @@ func newPaginatedResult(opts *proto.ChannelOptions, req paginatedRequest) (*Pagi
 	p.statusCode = resp.StatusCode
 	p.success = 200 <= p.statusCode && p.statusCode < 300
 	copyHeader(p.respHeaders, resp.Header)
-	if h := p.respHeaders.Get(AblyErrorCodeHeader); h != "" {
+	if h := p.respHeaders.Get(proto.AblyErrorCodeHeader); h != "" {
 		i, err := strconv.Atoi(h)
 		if err != nil {
 			return nil, err
@@ -141,7 +141,7 @@ func newPaginatedResult(opts *proto.ChannelOptions, req paginatedRequest) (*Pagi
 	} else if !p.success {
 		return nil, malformedPaginatedResponseError(resp)
 	}
-	if h := p.respHeaders.Get(AblyErrormessageHeader); h != "" {
+	if h := p.respHeaders.Get(proto.AblyErrormessageHeader); h != "" {
 		p.errorMessage = h
 	} else if !p.success {
 		return nil, malformedPaginatedResponseError(resp)
diff --git a/ably/proto/http.go b/ably/proto/http.go
new file mode 100644
index 0000000..02c858f
--- /dev/null
+++ b/ably/proto/http.go
@@ -0,0 +1,15 @@
+package proto
+
+// constants for rsc7
+const (
+	AblyVersionHeader      = "X-Ably-Version"
+	AblyLibHeader          = "X-Ably-Lib"
+	AblyErrorCodeHeader    = "X-Ably-Errorcode"
+	AblyErrormessageHeader = "X-Ably-Errormessage"
+	LibraryVersion         = "1.1.5"
+	LibraryName            = "ably-go"
+	LibraryString          = LibraryName + "-" + LibraryVersion
+	AblyVersion            = "1.0"
+	AblyClientIDHeader     = "X-Ably-ClientId"
+	HostHeader             = "Host"
+)
diff --git a/ably/rest_client.go b/ably/rest_client.go
index d9eee65..3e80e5a 100644
--- a/ably/rest_client.go
+++ b/ably/rest_client.go
@@ -30,22 +30,7 @@ var (
 	arrayTyp    = reflect.TypeOf((*[]interface{})(nil)).Elem()
 )
 
-// constants for rsc7
-const (
-	AblyVersionHeader      = "X-Ably-Version"
-	AblyLibHeader          = "X-Ably-Lib"
-	AblyErrorCodeHeader    = "X-Ably-Errorcode"
-	AblyErrormessageHeader = "X-Ably-Errormessage"
-	LibraryVersion         = "1.1.5"
-	LibraryName            = "ably-go"
-	LibraryString          = LibraryName + "-" + LibraryVersion
-	AblyVersion            = "1.0"
-	AblyClientIDHeader     = "X-Ably-ClientId"
-)
-
-const HostHeader = "Host"
-
-func query(fn func(string, interface{}) (*http.Response, error)) QueryFunc {
+func query(fn func(string, interface{}) (*http.Response, error)) queryFunc {
 	return func(path string) (*http.Response, error) {
 		return fn(path, nil)
 	}
@@ -161,7 +146,7 @@ func NewREST(options ClientOptions) (*REST, error) {
 
 func (c *REST) Time() (time.Time, error) {
 	var times []int64
-	r := &Request{
+	r := &request{
 		Method: "GET",
 		Path:   "/time",
 		Out:    &times,
@@ -184,9 +169,9 @@ func (c *REST) Stats(params *PaginateParams) (*PaginatedResult, error) {
 	return newPaginatedResult(nil, paginatedRequest{typ: statType, path: "/stats", params: params, query: query(c.get), logger: c.logger(), respCheck: checkValidHTTPResponse})
 }
 
-// Request this contains fields necessary to compose http request that will be
+// request this contains fields necessary to compose http request that will be
 // sent ably endpoints.
-type Request struct {
+type request struct {
 	Method string
 	Path   string
 	In     interface{} // value to be encoded and sent with request body
@@ -207,7 +192,7 @@ func (c *REST) Request(method string, path string, params *PaginateParams, body
 	switch method {
 	case "GET", "POST", "PUT", "PATCH", "DELETE": // spec RSC19a
 		return newHTTPPaginatedResult(path, params, func(p string) (*http.Response, error) {
-			req := &Request{
+			req := &request{
 				Method: method,
 				Path:   p,
 				In:     body,
@@ -227,7 +212,7 @@ func (c *REST) Request(method string, path string, params *PaginateParams, body
 }
 
 func (c *REST) get(path string, out interface{}) (*http.Response, error) {
-	r := &Request{
+	r := &request{
 		Method: "GET",
 		Path:   path,
 		Out:    out,
@@ -236,7 +221,7 @@ func (c *REST) get(path string, out interface{}) (*http.Response, error) {
 }
 
 func (c *REST) post(path string, in, out interface{}) (*http.Response, error) {
-	r := &Request{
+	r := &request{
 		Method: "POST",
 		Path:   path,
 		In:     in,
@@ -245,7 +230,7 @@ func (c *REST) post(path string, in, out interface{}) (*http.Response, error) {
 	return c.do(r)
 }
 
-func (c *REST) do(r *Request) (*http.Response, error) {
+func (c *REST) do(r *request) (*http.Response, error) {
 	return c.doWithHandle(r, c.handleResponse)
 }
 
@@ -312,15 +297,15 @@ func (f *fallbackCache) put(host string) {
 	}
 }
 
-func (c *REST) doWithHandle(r *Request, handle func(*http.Response, interface{}) (*http.Response, error)) (*http.Response, error) {
-	log := c.opts.Logger.Sugar()
+func (c *REST) doWithHandle(r *request, handle func(*http.Response, interface{}) (*http.Response, error)) (*http.Response, error) {
+	log := c.opts.Logger.sugar()
 	if c.successFallbackHost == nil {
 		c.successFallbackHost = &fallbackCache{
 			duration: c.opts.fallbackRetryTimeout(),
 		}
 		log.Verbosef("RestClient: setup fallback duration to %v", c.successFallbackHost.duration)
 	}
-	req, err := c.NewHTTPRequest(r)
+	req, err := c.newHTTPRequest(r)
 	if err != nil {
 		return nil, err
 	}
@@ -362,7 +347,7 @@ func (c *REST) doWithHandle(r *Request, handle func(*http.Response, interface{})
 			if canFallBack(e.StatusCode) &&
 				(strings.HasPrefix(req.URL.Host, defaultOptions.RestHost) ||
 					c.opts.FallbackHosts != nil) {
-				fallback := DefaultFallbackHosts()
+				fallback := defaultFallbackHosts()
 				if c.opts.FallbackHosts != nil {
 					fallback = c.opts.FallbackHosts
 				}
@@ -394,14 +379,14 @@ func (c *REST) doWithHandle(r *Request, handle func(*http.Response, interface{})
 							}
 						}
 						left = n
-						req, err := c.NewHTTPRequest(r)
+						req, err := c.newHTTPRequest(r)
 						if err != nil {
 							return nil, err
 						}
 						log.Infof("RestClient:  chose fallback host=%q ", h)
 						req.URL.Host = h
 						req.Host = ""
-						req.Header.Set(HostHeader, h)
+						req.Header.Set(proto.HostHeader, h)
 						if log.Is(LogVerbose) {
 							b, err := httputil.DumpRequest(req, true)
 							if err != nil {
@@ -466,13 +451,13 @@ func canFallBack(code int) bool {
 		code <= http.StatusGatewayTimeout
 }
 
-// NewHTTPRequest creates a new http.Request that can be sent to ably endpoints.
+// newHTTPRequest creates a new http.Request that can be sent to ably endpoints.
 // This makes sure necessary headers are set.
-func (c *REST) NewHTTPRequest(r *Request) (*http.Request, error) {
+func (c *REST) newHTTPRequest(r *request) (*http.Request, error) {
 	var body io.Reader
-	var proto = c.opts.protocol()
+	var protocol = c.opts.protocol()
 	if r.In != nil {
-		p, err := encode(proto, r.In)
+		p, err := encode(protocol, r.In)
 		if err != nil {
 			return nil, newError(ErrProtocolError, err)
 		}
@@ -484,18 +469,18 @@ func (c *REST) NewHTTPRequest(r *Request) (*http.Request, error) {
 		return nil, newError(ErrInternalError, err)
 	}
 	if body != nil {
-		req.Header.Set("Content-Type", proto) //spec RSC19c
+		req.Header.Set("Content-Type", protocol) //spec RSC19c
 	}
 	if r.header != nil {
 		copyHeader(req.Header, r.header)
 	}
-	req.Header.Set("Accept", proto) //spec RSC19c
-	req.Header.Set(AblyVersionHeader, AblyVersion)
-	req.Header.Set(AblyLibHeader, LibraryString)
+	req.Header.Set("Accept", protocol) //spec RSC19c
+	req.Header.Set(proto.AblyVersionHeader, proto.AblyVersion)
+	req.Header.Set(proto.AblyLibHeader, proto.LibraryString)
 	if c.opts.ClientID != "" && c.Auth.method == authBasic {
 		// References RSA7e2
 		h := base64.StdEncoding.EncodeToString([]byte(c.opts.ClientID))
-		req.Header.Set(AblyClientIDHeader, h)
+		req.Header.Set(proto.AblyClientIDHeader, h)
 	}
 	if !r.NoAuth {
 		//spec RSC19b
@@ -507,7 +492,7 @@ func (c *REST) NewHTTPRequest(r *Request) (*http.Request, error) {
 }
 
 func (c *REST) handleResponse(resp *http.Response, out interface{}) (*http.Response, error) {
-	log := c.opts.Logger.Sugar()
+	log := c.opts.Logger.sugar()
 	log.Info("RestClient:checking valid http response")
 	if err := checkValidHTTPResponse(resp); err != nil {
 		log.Error("RestClient: failed to check valid http response ", err)
diff --git a/ably/token.go b/ably/token.go
index 7e4b906..3e9458c 100644
--- a/ably/token.go
+++ b/ably/token.go
@@ -4,35 +4,12 @@ import (
 	"crypto/hmac"
 	"crypto/sha256"
 	"encoding/base64"
-	"encoding/json"
 	"fmt"
 	"net/url"
 	"strconv"
 	"time"
 )
 
-// Capability
-type Capability map[string][]string
-
-// ParseCapability
-func ParseCapability(capability string) (c Capability, err error) {
-	c = make(Capability)
-	err = json.Unmarshal([]byte(capability), &c)
-	return
-}
-
-// Encode
-func (c Capability) Encode() string {
-	if len(c) == 0 {
-		return ""
-	}
-	p, err := json.Marshal((map[string][]string)(c))
-	if err != nil {
-		panic(err)
-	}
-	return string(p)
-}
-
 // TokenParams
 type TokenParams struct {
 	// TTL is a requested time to live for the token. If the token request
@@ -41,8 +18,8 @@ type TokenParams struct {
 	// of the issuing key.
 	TTL int64 `json:"ttl,omitempty" codec:"ttl,omitempty"`
 
-	// RawCapability represents encoded access rights of the token.
-	RawCapability string `json:"capability,omitempty" codec:"capability,omitempty"`
+	// Capability represents encoded access rights of the token.
+	Capability string `json:"capability,omitempty" codec:"capability,omitempty"`
 
 	// ClientID represents a client, whom the token is generated for.
 	ClientID string `json:"clientId,omitempty" codec:"clientId,omitempty"`
@@ -52,12 +29,6 @@ type TokenParams struct {
 	Timestamp int64 `json:"timestamp,omitempty" codec:"timestamp,omitempty"`
 }
 
-// Capability
-func (params *TokenParams) Capability() Capability {
-	c, _ := ParseCapability(params.RawCapability)
-	return c
-}
-
 // Query encodes the params to query params value. If a field of params is
 // a zero-value, it's omitted. If params is zero-value, nil is returned.
 func (params *TokenParams) Query() url.Values {
@@ -68,8 +39,8 @@ func (params *TokenParams) Query() url.Values {
 	if params.TTL != 0 {
 		q.Set("ttl", strconv.FormatInt(params.TTL, 10))
 	}
-	if params.RawCapability != "" {
-		q.Set("capability", params.RawCapability)
+	if params.Capability != "" {
+		q.Set("capability", params.Capability)
 	}
 	if params.ClientID != "" {
 		q.Set("clientId", params.ClientID)
@@ -96,7 +67,7 @@ func (req *TokenRequest) sign(secret []byte) {
 	mac := hmac.New(sha256.New, secret)
 	fmt.Fprintln(mac, req.KeyName)
 	fmt.Fprintln(mac, req.TTL)
-	fmt.Fprintln(mac, req.RawCapability)
+	fmt.Fprintln(mac, req.Capability)
 	fmt.Fprintln(mac, req.ClientID)
 	fmt.Fprintln(mac, req.Timestamp)
 	fmt.Fprintln(mac, req.Nonce)
@@ -120,19 +91,13 @@ type TokenDetails struct {
 	// Issued
 	Issued int64 `json:"issued,omitempty" codec:"issued,omitempty"`
 
-	// RawCapability
-	RawCapability string `json:"capability,omitempty" codec:"capability,omitempty"`
+	// Capability
+	Capability string `json:"capability,omitempty" codec:"capability,omitempty"`
 }
 
 func (TokenDetails) IsTokener() {}
 func (TokenDetails) isTokener() {}
 
-// Capability
-func (tok *TokenDetails) Capability() Capability {
-	c, _ := ParseCapability(tok.RawCapability)
-	return c
-}
-
 func (tok *TokenDetails) expired(now time.Time) bool {
 	return tok.Expires != 0 && tok.Expires <= unixMilli(now)
 }


Test patch:
diff --git a/ably/ablytest/proxies.go b/ably/ablytest/proxies.go
index 24b610b..43a5460 100644
--- a/ably/ablytest/proxies.go
+++ b/ably/ablytest/proxies.go
@@ -31,7 +31,7 @@ func NewTokenParams(query url.Values) *ably.TokenParams {
 		params.TTL = n
 	}
 	if s := query.Get("capability"); s != "" {
-		params.RawCapability = s
+		params.Capability = s
 	}
 	if s := query.Get("clientId"); s != "" {
 		params.ClientID = s
diff --git a/ably/ablytest/sandbox.go b/ably/ablytest/sandbox.go
index d664a70..982df71 100644
--- a/ably/ablytest/sandbox.go
+++ b/ably/ablytest/sandbox.go
@@ -17,21 +17,16 @@ import (
 )
 
 type Key struct {
-	ID            string `json:"id,omitempty"`
-	ScopeID       string `json:"scopeId,omitempty"`
-	Status        int    `json:"status,omitempty"`
-	Type          int    `json:"type,omitempty"`
-	Value         string `json:"value,omitempty"`
-	Created       int    `json:"created,omitempty"`
-	Modified      int    `json:"modified,omitempty"`
-	RawCapability string `json:"capability,omitempty"`
-	Expires       int    `json:"expired,omitempty"`
-	Privileged    bool   `json:"privileged,omitempty"`
-}
-
-func (k *Key) Capability() ably.Capability {
-	c, _ := ably.ParseCapability(k.RawCapability)
-	return c
+	ID         string `json:"id,omitempty"`
+	ScopeID    string `json:"scopeId,omitempty"`
+	Status     int    `json:"status,omitempty"`
+	Type       int    `json:"type,omitempty"`
+	Value      string `json:"value,omitempty"`
+	Created    int    `json:"created,omitempty"`
+	Modified   int    `json:"modified,omitempty"`
+	Capability string `json:"capability,omitempty"`
+	Expires    int    `json:"expired,omitempty"`
+	Privileged bool   `json:"privileged,omitempty"`
 }
 
 type Namespace struct {
diff --git a/ably/auth_test.go b/ably/auth_test.go
index a94187f..de72731 100644
--- a/ably/auth_test.go
+++ b/ably/auth_test.go
@@ -134,8 +134,8 @@ func TestAuth_TokenAuth(t *testing.T) {
 	if n := rec.Len(); n != 1 {
 		t.Fatalf("Authorize() did not return new token; want rec.Len()=1; %d", n)
 	}
-	if defaultCap := (ably.Capability{"*": {"*"}}); tok.RawCapability != defaultCap.Encode() {
-		t.Fatalf("want tok.Capability=%v; got %v", defaultCap, tok.Capability())
+	if defaultCap := `{"*":["*"]}`; tok.Capability != defaultCap {
+		t.Fatalf("want tok.Capability=%v; got %v", defaultCap, tok.Capability)
 	}
 	now := time.Now().Add(time.Second)
 	if err := timeWithin(tok.IssueTime(), beforeAuth, now); err != nil {
@@ -716,8 +716,8 @@ func TestAuth_CreateTokenRequest(t *testing.T) {
 		QueryTime(true).
 		Key(app.Key())
 	params := &ably.TokenParams{
-		TTL:           (5 * time.Second).Milliseconds(),
-		RawCapability: (ably.Capability{"presence": {"read", "write"}}).Encode(),
+		TTL:        (5 * time.Second).Milliseconds(),
+		Capability: `{"presence":["read", "write"]}`,
 	}
 	t.Run("RSA9h", func(ts *testing.T) {
 		ts.Run("parameters are optional", func(ts *testing.T) {
diff --git a/ably/export_test.go b/ably/export_test.go
index f5456f6..7b1c0de 100644
--- a/ably/export_test.go
+++ b/ably/export_test.go
@@ -128,3 +128,7 @@ func (os ClientOptions) ApplyWithDefaults() *clientOptions {
 type ConnStateChanges = connStateChanges
 
 type ChannelStateChanges = channelStateChanges
+
+func DefaultFallbackHosts() []string {
+	return defaultFallbackHosts()
+}
diff --git a/ably/rest_client_test.go b/ably/rest_client_test.go
index 1151927..d72c396 100644
--- a/ably/rest_client_test.go
+++ b/ably/rest_client_test.go
@@ -230,35 +230,44 @@ func TestRestClient(t *testing.T) {
 	})
 }
 
+type httpRoundTripperFunc func(*http.Request) (*http.Response, error)
+
+func (f httpRoundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
+	return f(req)
+}
+
 func TestRSC7(t *testing.T) {
 	t.Parallel()
-	app, err := ablytest.NewSandbox(nil)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer app.Close()
-	c, err := ably.NewREST(app.Options(nil))
+
+	client := &http.Client{}
+	requests := make(chan *http.Request, 1)
+	client.Transport = httpRoundTripperFunc(func(req *http.Request) (*http.Response, error) {
+		requests <- req
+		return nil, errors.New("fake round tripper")
+	})
+
+	c, err := ably.NewREST(ably.ClientOptions{}.
+		Key("fake:key").
+		HTTPClient(client))
 	if err != nil {
 		t.Fatal(err)
 	}
+
+	_, _ = c.Request("POST", "/foo", nil, nil, nil)
+
+	var req *http.Request
+	ablytest.Instantly.Recv(t, &req, requests, t.Fatalf)
+
 	t.Run("must set version header", func(ts *testing.T) {
-		req, err := c.NewHTTPRequest(&ably.Request{})
-		if err != nil {
-			ts.Fatal(err)
-		}
-		h := req.Header.Get(ably.AblyVersionHeader)
-		if h != ably.AblyVersion {
-			t.Errorf("expected %s got %s", ably.AblyVersion, h)
+		h := req.Header.Get(proto.AblyVersionHeader)
+		if h != proto.AblyVersion {
+			t.Errorf("expected %s got %s", proto.AblyVersion, h)
 		}
 	})
 	t.Run("must set lib header", func(ts *testing.T) {
-		req, err := c.NewHTTPRequest(&ably.Request{})
-		if err != nil {
-			ts.Fatal(err)
-		}
-		h := req.Header.Get(ably.AblyLibHeader)
-		if h != ably.LibraryString {
-			t.Errorf("expected %s got %s", ably.LibraryString, h)
+		h := req.Header.Get(proto.AblyLibHeader)
+		if h != proto.LibraryString {
+			t.Errorf("expected %s got %s", proto.LibraryString, h)
 		}
 	})
 }