Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/host fallback conditions #629

Merged
merged 10 commits into from
Jan 19, 2024
33 changes: 27 additions & 6 deletions ably/ably_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ func (pc pipeConn) Close() error {
// MessageRecorder
type MessageRecorder struct {
mu sync.Mutex
url []*url.URL
urls []*url.URL
sent []*ably.ProtocolMessage
received []*ably.ProtocolMessage
}
Expand All @@ -207,7 +207,7 @@ func NewMessageRecorder() *MessageRecorder {
// Reset resets the recorded urls, sent and received messages
func (rec *MessageRecorder) Reset() {
rec.mu.Lock()
rec.url = nil
rec.urls = nil
rec.sent = nil
rec.received = nil
rec.mu.Unlock()
Expand All @@ -216,7 +216,7 @@ func (rec *MessageRecorder) Reset() {
// Dial
func (rec *MessageRecorder) Dial(proto string, u *url.URL, timeout time.Duration) (ably.Conn, error) {
rec.mu.Lock()
rec.url = append(rec.url, u)
rec.urls = append(rec.urls, u)
rec.mu.Unlock()
conn, err := ably.DialWebsocket(proto, u, timeout)
if err != nil {
Expand All @@ -229,11 +229,11 @@ func (rec *MessageRecorder) Dial(proto string, u *url.URL, timeout time.Duration
}

// URL
func (rec *MessageRecorder) URL() []*url.URL {
func (rec *MessageRecorder) URLs() []*url.URL {
rec.mu.Lock()
defer rec.mu.Unlock()
newUrl := make([]*url.URL, len(rec.url))
copy(newUrl, rec.url)
newUrl := make([]*url.URL, len(rec.urls))
copy(newUrl, rec.urls)
return newUrl
}

Expand Down Expand Up @@ -522,3 +522,24 @@ var canceledCtx context.Context = func() context.Context {
cancel()
return ctx
}()

func assertSubset(t *testing.T, set []string, subset []string) {
t.Helper()
for _, item := range subset {
if !ablyutil.SliceContains(set, item) {
t.Errorf("expected %s got be in %s", item, set)
}
}
}

func assertUnique(t *testing.T, list []string) {
t.Helper()
hashSet := ablyutil.NewHashSet()
for _, item := range list {
if hashSet.Has(item) {
t.Errorf("duplicate item %s", item)
} else {
hashSet.Add(item)
}
}
}
2 changes: 1 addition & 1 deletion ably/auth_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,7 @@ func TestAuth_RealtimeAccessToken(t *testing.T) {
err = ablytest.FullRealtimeCloser(client).Close()
assert.NoError(t, err,
"Close()=%v", err)
recUrls := rec.URL()
recUrls := rec.URLs()
assert.NotEqual(t, 0, len(recUrls),
"want urls to be non-empty")
for _, recUrl := range recUrls {
Expand Down
39 changes: 39 additions & 0 deletions ably/error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"context"
"errors"
"fmt"
"net"
"net/http"
"net/http/httptest"
"net/url"
Expand Down Expand Up @@ -146,3 +147,41 @@ func TestIssue_154(t *testing.T) {
assert.Equal(t, http.StatusMethodNotAllowed, et.StatusCode,
"expected %d got %d: %v", http.StatusMethodNotAllowed, et.StatusCode, err)
}

func Test_DNSOrTimeoutErr(t *testing.T) {
dnsErr := net.DNSError{
Err: "Can't resolve host",
Name: "Host unresolvable",
Server: "rest.ably.com",
IsTimeout: false,
IsTemporary: false,
IsNotFound: false,
}

WrappedDNSErr := fmt.Errorf("custom error occured %w", &dnsErr)
if !ably.IsTimeoutOrDnsErr(WrappedDNSErr) {
t.Fatalf("%v is a DNS error", WrappedDNSErr)
}

urlErr := url.Error{
URL: "rest.ably.io",
Err: errors.New("URL error occured"),
Op: "IO read OP",
}

if ably.IsTimeoutOrDnsErr(&urlErr) {
t.Fatalf("%v is not a DNS or timeout error", urlErr)
}

urlErr.Err = &dnsErr

if !ably.IsTimeoutOrDnsErr(WrappedDNSErr) {
t.Fatalf("%v is a DNS error", urlErr)
}

dnsErr.IsTimeout = true

if !ably.IsTimeoutOrDnsErr(WrappedDNSErr) {
t.Fatalf("%v is a timeout error", urlErr)
}
}
4 changes: 4 additions & 0 deletions ably/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ func UnwrapStatusCode(err error) int {
return statusCode(err)
}

func IsTimeoutOrDnsErr(err error) bool {
return isTimeoutOrDnsErr(err)
}

func (a *Auth) Timestamp(ctx context.Context, query bool) (time.Time, error) {
return a.timestamp(ctx, query)
}
Expand Down
4 changes: 3 additions & 1 deletion ably/http_paginated_response_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ func TestHTTPPaginatedFallback(t *testing.T) {
app, err := ablytest.NewSandbox(nil)
assert.NoError(t, err)
defer app.Close()
opts := app.Options(ably.WithUseBinaryProtocol(false), ably.WithRESTHost("ably.invalid"), ably.WithFallbackHostsUseDefault(true))
opts := app.Options(ably.WithUseBinaryProtocol(false),
ably.WithRESTHost("ably.invalid"),
ably.WithFallbackHosts(nil))
client, err := ably.NewREST(opts...)
assert.NoError(t, err)
t.Run("request_time", func(t *testing.T) {
Expand Down
74 changes: 74 additions & 0 deletions ably/internal/ablyutil/strings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package ablyutil

import (
"math/rand"
"sort"
"strings"
"time"
)

const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

var seededRand *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano()))

func GenerateRandomString(length int) string {
b := make([]byte, length)
for i := range b {
b[i] = charset[seededRand.Intn(len(charset))]
}
return string(b)
}

type HashSet map[string]struct{} // struct {} has zero space complexity

func NewHashSet() HashSet {
return make(HashSet)
}

func (s HashSet) Add(item string) {
s[item] = struct{}{}
}

func (s HashSet) Remove(item string) {
delete(s, item)
}

func (s HashSet) Has(item string) bool {
_, ok := s[item]
return ok
}

func Copy(list []string) []string {
copiedList := make([]string, len(list))
copy(copiedList, list)
return copiedList
}

func Sort(list []string) []string {
copiedList := Copy(list)
sort.Strings(copiedList)
return copiedList
}

func Shuffle(list []string) []string {
copiedList := Copy(list)
if len(copiedList) <= 1 {
return copiedList
}
rand.Seed(time.Now().UnixNano())
rand.Shuffle(len(copiedList), func(i, j int) { copiedList[i], copiedList[j] = copiedList[j], copiedList[i] })
return copiedList
}

func SliceContains(s []string, str string) bool {
for _, v := range s {
if v == str {
return true
}
}
return false
}

func Empty(s string) bool {
return len(strings.TrimSpace(s)) == 0
}
111 changes: 111 additions & 0 deletions ably/internal/ablyutil/strings_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package ablyutil_test

import (
"testing"

"github.com/ably/ably-go/ably/internal/ablyutil"
"github.com/stretchr/testify/assert"
)

func Test_string(t *testing.T) {
t.Run("String array Shuffle", func(t *testing.T) {
t.Parallel()

strList := []string{}
shuffledList := ablyutil.Shuffle(strList)
assert.Equal(t, strList, shuffledList)

strList = []string{"a"}
shuffledList = ablyutil.Shuffle(strList)
assert.Equal(t, strList, shuffledList)

strList = []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p"}
shuffledList = ablyutil.Shuffle(strList)
assert.NotEqual(t, strList, shuffledList)
assert.Equal(t, ablyutil.Sort(strList), ablyutil.Sort(shuffledList))
})

t.Run("String array contains", func(t *testing.T) {
t.Parallel()
strarr := []string{"apple", "banana", "dragonfruit"}

if !ablyutil.SliceContains(strarr, "apple") {
t.Error("String array should contain apple")
}
if ablyutil.SliceContains(strarr, "orange") {
t.Error("String array should not contain orange")
}
})

t.Run("Empty String", func(t *testing.T) {
t.Parallel()
str := ""
if !ablyutil.Empty(str) {
t.Error("String should be empty")
}
str = " "
if !ablyutil.Empty(str) {
t.Error("String should be empty")
}
str = "ab"
if ablyutil.Empty(str) {
t.Error("String should not be empty")
}
})
}

func TestHashSet(t *testing.T) {
t.Run("Add should not duplicate entries", func(t *testing.T) {
hashSet := ablyutil.NewHashSet()
hashSet.Add("apple")
hashSet.Add("apple")
assert.Len(t, hashSet, 1)

hashSet.Add("banana")
assert.Len(t, hashSet, 2)

hashSet.Add("orange")
assert.Len(t, hashSet, 3)

hashSet.Add("banana")
hashSet.Add("apple")
hashSet.Add("orange")
hashSet.Add("orange")

assert.Len(t, hashSet, 3)
})

t.Run("Should check if item is present", func(t *testing.T) {
hashSet := ablyutil.NewHashSet()
hashSet.Add("apple")
hashSet.Add("orange")
if !hashSet.Has("apple") {
t.Fatalf("Set should contain apple")
}
if hashSet.Has("banana") {
t.Fatalf("Set shouldm't contain banana")
}
if !hashSet.Has("orange") {
t.Fatalf("Set should contain orange")
}
})

t.Run("Should remove element", func(t *testing.T) {
hashSet := ablyutil.NewHashSet()
hashSet.Add("apple")
assert.Len(t, hashSet, 1)

hashSet.Add("orange")
assert.Len(t, hashSet, 2)

hashSet.Remove("apple")
assert.Len(t, hashSet, 1)

if hashSet.Has("apple") {
t.Fatalf("Set shouldm't contain apple")
}
hashSet.Remove("orange")
assert.Len(t, hashSet, 0)

})
}
3 changes: 2 additions & 1 deletion ably/realtime_channel_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"time"

"github.com/ably/ably-go/ably"
"github.com/ably/ably-go/ably/internal/ablyutil"
"github.com/ably/ably-go/ablytest"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -320,7 +321,7 @@ func TestRealtimeChannel_ShouldReturnErrorIfReadLimitExceeded(t *testing.T) {
assert.NoError(t, err, "client2:.Subscribe(context.Background())=%v", err)
defer unsub2()

messageWith2MbSize := ablytest.GenerateRandomString(2048)
messageWith2MbSize := ablyutil.GenerateRandomString(2048)
err = channel1.Publish(context.Background(), "hello", messageWith2MbSize)
assert.NoError(t, err, "client1: Publish()=%v", err)

Expand Down
Loading
Loading