Skip to content

Commit

Permalink
Merge pull request #629 from ably/fix/host-fallback-conditions
Browse files Browse the repository at this point in the history
Fix/host fallback conditions
  • Loading branch information
sacOO7 authored Jan 19, 2024
2 parents b8e6391 + e43bb34 commit 81e8b29
Show file tree
Hide file tree
Showing 11 changed files with 425 additions and 101 deletions.
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

0 comments on commit 81e8b29

Please sign in to comment.