diff --git a/client/serverlist.go b/client/serverlist.go index 6342846..5843e5c 100644 --- a/client/serverlist.go +++ b/client/serverlist.go @@ -14,9 +14,12 @@ type ServerList struct { // FilterByIdxs returns a filtered server list filtered according to an index list func (sl *ServerList) FilterByIdxs(MessageServerIdxs []int) (filtered *ServerList, err error) { - filtered.Servers = []*Server{} + filtered = &ServerList{ + Name: sl.Name, + Servers: []*Server{}, + } for _, i := range MessageServerIdxs { - if i > len(sl.Servers)-1 { + if i < 0 || i > len(sl.Servers)-1 { return nil, errors.New("requested server out of range of defined message servers") } } @@ -28,7 +31,7 @@ func (sl *ServerList) FilterByIdxs(MessageServerIdxs []int) (filtered *ServerLis // GetServerByIdx returns a server from it's index func (sl *ServerList) GetServerByIdx(idx int) (server *Server, err error) { - if idx > len(sl.Servers)-1 { + if idx < 0 || idx > len(sl.Servers)-1 { return nil, errors.New("requested server out of range of defined message servers") } return sl.Servers[idx], nil diff --git a/client/serverlist_test.go b/client/serverlist_test.go new file mode 100644 index 0000000..372db94 --- /dev/null +++ b/client/serverlist_test.go @@ -0,0 +1,346 @@ +package client + +import ( + "testing" + + "forge.redroom.link/yves/meowlib" + "github.com/stretchr/testify/assert" +) + +// Helper function to create a test server list with sample servers +func createTestServerList(t *testing.T) *ServerList { + sl := &ServerList{ + Name: "TestServerList", + Servers: []*Server{}, + } + + // Create servers with different public keys + for i := 0; i < 5; i++ { + srv, err := CreateServerFromUrl("https://server" + string(rune('0'+i)) + ".example.com/meow") + if err != nil { + t.Fatalf("Failed to create server: %v", err) + } + + // Generate unique public keys for testing + kp, err := meowlib.NewKeyPair() + if err != nil { + t.Fatalf("Failed to create keypair: %v", err) + } + srv.PublicKey = kp.Public + srv.Name = "Server" + string(rune('0'+i)) + + sl.Servers = append(sl.Servers, srv) + } + + return sl +} + +func TestServerList_FilterByIdxs(t *testing.T) { + sl := createTestServerList(t) + + t.Run("Filter with valid indices", func(t *testing.T) { + indices := []int{0, 2, 4} + filtered, err := sl.FilterByIdxs(indices) + + assert.NoError(t, err, "FilterByIdxs should not return error for valid indices") + assert.NotNil(t, filtered, "Filtered list should not be nil") + assert.Equal(t, 3, len(filtered.Servers), "Should have 3 servers after filtering") + + // Verify the filtered servers are correct + assert.Equal(t, sl.Servers[0], filtered.Servers[0]) + assert.Equal(t, sl.Servers[2], filtered.Servers[1]) + assert.Equal(t, sl.Servers[4], filtered.Servers[2]) + }) + + t.Run("Filter with single index", func(t *testing.T) { + indices := []int{1} + filtered, err := sl.FilterByIdxs(indices) + + assert.NoError(t, err, "FilterByIdxs should not return error for valid single index") + assert.NotNil(t, filtered, "Filtered list should not be nil") + assert.Equal(t, 1, len(filtered.Servers), "Should have 1 server after filtering") + assert.Equal(t, sl.Servers[1], filtered.Servers[0]) + }) + + t.Run("Filter with empty indices", func(t *testing.T) { + indices := []int{} + filtered, err := sl.FilterByIdxs(indices) + + assert.NoError(t, err, "FilterByIdxs should not return error for empty indices") + assert.NotNil(t, filtered, "Filtered list should not be nil") + assert.Equal(t, 0, len(filtered.Servers), "Should have 0 servers after filtering with empty indices") + }) + + t.Run("Filter with out of range index", func(t *testing.T) { + indices := []int{0, 10} + filtered, err := sl.FilterByIdxs(indices) + + assert.Error(t, err, "FilterByIdxs should return error for out of range index") + assert.Nil(t, filtered, "Filtered list should be nil on error") + assert.Contains(t, err.Error(), "out of range", "Error message should mention out of range") + }) + + t.Run("Filter with negative index", func(t *testing.T) { + indices := []int{-1} + filtered, err := sl.FilterByIdxs(indices) + + assert.Error(t, err, "FilterByIdxs should return error for negative index") + assert.Nil(t, filtered, "Filtered list should be nil on error") + }) + + t.Run("Filter with duplicate indices", func(t *testing.T) { + indices := []int{1, 1, 2} + filtered, err := sl.FilterByIdxs(indices) + + assert.NoError(t, err, "FilterByIdxs should not return error for duplicate indices") + assert.NotNil(t, filtered, "Filtered list should not be nil") + // Note: duplicates will result in duplicate servers in the list + assert.Equal(t, 3, len(filtered.Servers), "Should have 3 servers (including duplicate)") + }) +} + +func TestServerList_FilterByIdxs_EmptyList(t *testing.T) { + sl := &ServerList{ + Name: "EmptyList", + Servers: []*Server{}, + } + + t.Run("Filter empty list with indices", func(t *testing.T) { + indices := []int{0} + filtered, err := sl.FilterByIdxs(indices) + + assert.Error(t, err, "FilterByIdxs should return error when trying to filter empty list") + assert.Nil(t, filtered, "Filtered list should be nil on error") + }) + + t.Run("Filter empty list with empty indices", func(t *testing.T) { + indices := []int{} + filtered, err := sl.FilterByIdxs(indices) + + assert.NoError(t, err, "FilterByIdxs should not return error for empty indices on empty list") + assert.NotNil(t, filtered, "Filtered list should not be nil") + assert.Equal(t, 0, len(filtered.Servers), "Should have 0 servers") + }) +} + +func TestServerList_GetServerByIdx(t *testing.T) { + sl := createTestServerList(t) + + t.Run("Get server with valid index", func(t *testing.T) { + server, err := sl.GetServerByIdx(2) + + assert.NoError(t, err, "GetServerByIdx should not return error for valid index") + assert.NotNil(t, server, "Server should not be nil") + assert.Equal(t, sl.Servers[2], server, "Should return the correct server") + }) + + t.Run("Get first server", func(t *testing.T) { + server, err := sl.GetServerByIdx(0) + + assert.NoError(t, err, "GetServerByIdx should not return error for index 0") + assert.NotNil(t, server, "Server should not be nil") + assert.Equal(t, sl.Servers[0], server, "Should return the first server") + }) + + t.Run("Get last server", func(t *testing.T) { + lastIdx := len(sl.Servers) - 1 + server, err := sl.GetServerByIdx(lastIdx) + + assert.NoError(t, err, "GetServerByIdx should not return error for last index") + assert.NotNil(t, server, "Server should not be nil") + assert.Equal(t, sl.Servers[lastIdx], server, "Should return the last server") + }) + + t.Run("Get server with out of range index", func(t *testing.T) { + server, err := sl.GetServerByIdx(100) + + assert.Error(t, err, "GetServerByIdx should return error for out of range index") + assert.Nil(t, server, "Server should be nil on error") + assert.Contains(t, err.Error(), "out of range", "Error message should mention out of range") + }) + + t.Run("Get server with negative index", func(t *testing.T) { + server, err := sl.GetServerByIdx(-1) + + assert.Error(t, err, "GetServerByIdx should return error for negative index") + assert.Nil(t, server, "Server should be nil on error") + }) +} + +func TestServerList_GetServerByIdx_EmptyList(t *testing.T) { + sl := &ServerList{ + Name: "EmptyList", + Servers: []*Server{}, + } + + t.Run("Get server from empty list", func(t *testing.T) { + server, err := sl.GetServerByIdx(0) + + assert.Error(t, err, "GetServerByIdx should return error on empty list") + assert.Nil(t, server, "Server should be nil on error") + }) +} + +func TestServerList_GetServerByPubkey(t *testing.T) { + sl := createTestServerList(t) + + t.Run("Get server with existing public key", func(t *testing.T) { + // Use the public key from the second server + targetPubkey := sl.Servers[1].PublicKey + + server := sl.GetServerByPubkey(targetPubkey) + + assert.NotNil(t, server, "Server should not be nil when public key exists") + assert.Equal(t, targetPubkey, server.PublicKey, "Should return server with matching public key") + assert.Equal(t, sl.Servers[1], server, "Should return the correct server") + }) + + t.Run("Get server with non-existent public key", func(t *testing.T) { + server := sl.GetServerByPubkey("nonexistent-pubkey-12345") + + assert.Nil(t, server, "Server should be nil when public key doesn't exist") + }) + + t.Run("Get server with empty public key", func(t *testing.T) { + server := sl.GetServerByPubkey("") + + assert.Nil(t, server, "Server should be nil when searching for empty public key") + }) + + t.Run("Get first matching server", func(t *testing.T) { + // Add a duplicate public key + duplicatePubkey := sl.Servers[0].PublicKey + newServer, err := CreateServerFromUrl("https://duplicate.example.com/meow") + if err != nil { + t.Fatalf("Failed to create server: %v", err) + } + newServer.PublicKey = duplicatePubkey + sl.Servers = append(sl.Servers, newServer) + + server := sl.GetServerByPubkey(duplicatePubkey) + + assert.NotNil(t, server, "Server should not be nil") + assert.Equal(t, duplicatePubkey, server.PublicKey) + // Should return the first match + assert.Equal(t, sl.Servers[0], server, "Should return the first matching server") + }) +} + +func TestServerList_GetServerByPubkey_EmptyList(t *testing.T) { + sl := &ServerList{ + Name: "EmptyList", + Servers: []*Server{}, + } + + t.Run("Get server from empty list", func(t *testing.T) { + server := sl.GetServerByPubkey("any-pubkey") + + assert.Nil(t, server, "Server should be nil when searching in empty list") + }) +} + +func TestServerList_AddUrls(t *testing.T) { + t.Run("Add valid URLs", func(t *testing.T) { + sl := &ServerList{ + Name: "TestList", + Servers: []*Server{}, + } + + urls := []string{ + "https://server1.example.com/meow", + "https://server2.example.com/meow", + "https://server3.example.com/meow", + } + + err := sl.AddUrls(urls) + + assert.NoError(t, err, "AddUrls should not return error for valid URLs") + assert.Equal(t, 3, len(sl.Servers), "Should have 3 servers after adding URLs") + + // Verify each server was created correctly + for i, url := range urls { + assert.Equal(t, url, sl.Servers[i].Url, "Server URL should match") + assert.NotNil(t, sl.Servers[i].UserKp, "Server should have UserKp") + } + }) + + t.Run("Add empty URLs list", func(t *testing.T) { + sl := &ServerList{ + Name: "TestList", + Servers: []*Server{}, + } + + urls := []string{} + err := sl.AddUrls(urls) + + assert.NoError(t, err, "AddUrls should not return error for empty list") + assert.Equal(t, 0, len(sl.Servers), "Should have 0 servers after adding empty list") + }) + + t.Run("Add URLs to existing list", func(t *testing.T) { + sl := createTestServerList(t) + initialCount := len(sl.Servers) + + urls := []string{ + "https://newserver1.example.com/meow", + "https://newserver2.example.com/meow", + } + + err := sl.AddUrls(urls) + + assert.NoError(t, err, "AddUrls should not return error") + assert.Equal(t, initialCount+2, len(sl.Servers), "Should have added 2 servers to existing list") + }) + + t.Run("Add single URL", func(t *testing.T) { + sl := &ServerList{ + Name: "TestList", + Servers: []*Server{}, + } + + urls := []string{"https://single.example.com/meow"} + err := sl.AddUrls(urls) + + assert.NoError(t, err, "AddUrls should not return error for single URL") + assert.Equal(t, 1, len(sl.Servers), "Should have 1 server after adding single URL") + }) +} + +func TestServerList_AddUrls_NilList(t *testing.T) { + t.Run("Add URLs to nil server list", func(t *testing.T) { + sl := &ServerList{ + Name: "TestList", + Servers: nil, // Explicitly nil + } + + urls := []string{"https://server.example.com/meow"} + err := sl.AddUrls(urls) + + assert.NoError(t, err, "AddUrls should not return error even with nil Servers slice") + assert.NotNil(t, sl.Servers, "Servers slice should be initialized") + assert.Equal(t, 1, len(sl.Servers), "Should have 1 server after adding URL") + }) +} + +// Test the ServerList struct itself +func TestServerList_Structure(t *testing.T) { + t.Run("Create empty server list", func(t *testing.T) { + sl := &ServerList{ + Name: "TestList", + Servers: []*Server{}, + } + + assert.NotNil(t, sl, "ServerList should not be nil") + assert.Equal(t, "TestList", sl.Name, "Name should be set correctly") + assert.NotNil(t, sl.Servers, "Servers slice should not be nil") + assert.Equal(t, 0, len(sl.Servers), "Servers slice should be empty") + }) + + t.Run("Create server list with name", func(t *testing.T) { + sl := &ServerList{ + Name: "MyMessageServers", + } + + assert.Equal(t, "MyMessageServers", sl.Name) + }) +}