Files
ResolveSpec/pkg/mqttspec/client_test.go
2025-12-30 14:12:36 +02:00

257 lines
5.5 KiB
Go

package mqttspec
import (
"context"
"sync"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewClient(t *testing.T) {
client := NewClient("client-123", "user@example.com", nil)
assert.Equal(t, "client-123", client.ID)
assert.Equal(t, "user@example.com", client.Username)
assert.NotNil(t, client.subscriptions)
assert.NotNil(t, client.metadata)
assert.NotNil(t, client.ctx)
assert.NotNil(t, client.cancel)
}
func TestClient_Metadata(t *testing.T) {
client := NewClient("client-123", "user", nil)
// Set metadata
client.SetMetadata("user_id", 456)
client.SetMetadata("tenant_id", "tenant-abc")
client.SetMetadata("roles", []string{"admin", "user"})
// Get metadata
userID, exists := client.GetMetadata("user_id")
assert.True(t, exists)
assert.Equal(t, 456, userID)
tenantID, exists := client.GetMetadata("tenant_id")
assert.True(t, exists)
assert.Equal(t, "tenant-abc", tenantID)
roles, exists := client.GetMetadata("roles")
assert.True(t, exists)
assert.Equal(t, []string{"admin", "user"}, roles)
// Non-existent key
_, exists = client.GetMetadata("nonexistent")
assert.False(t, exists)
}
func TestClient_Subscriptions(t *testing.T) {
client := NewClient("client-123", "user", nil)
// Create mock subscription
sub := &Subscription{
ID: "sub-1",
ConnectionID: "client-123",
Schema: "public",
Entity: "users",
Active: true,
}
// Add subscription
client.AddSubscription(sub)
// Get subscription
retrieved, exists := client.GetSubscription("sub-1")
assert.True(t, exists)
assert.Equal(t, "sub-1", retrieved.ID)
// Remove subscription
client.RemoveSubscription("sub-1")
// Verify removed
_, exists = client.GetSubscription("sub-1")
assert.False(t, exists)
}
func TestClient_Close(t *testing.T) {
client := NewClient("client-123", "user", nil)
// Add some subscriptions
client.AddSubscription(&Subscription{ID: "sub-1"})
client.AddSubscription(&Subscription{ID: "sub-2"})
// Close client
client.Close()
// Verify subscriptions cleared
client.subMu.RLock()
assert.Empty(t, client.subscriptions)
client.subMu.RUnlock()
// Verify context cancelled
select {
case <-client.ctx.Done():
// Context was cancelled
default:
t.Fatal("Context should be cancelled after Close()")
}
}
func TestNewClientManager(t *testing.T) {
cm := NewClientManager(context.Background())
assert.NotNil(t, cm)
assert.NotNil(t, cm.clients)
assert.Equal(t, 0, cm.Count())
}
func TestClientManager_Register(t *testing.T) {
cm := NewClientManager(context.Background())
defer cm.Shutdown()
client := cm.Register("client-1", "user@example.com", nil)
assert.NotNil(t, client)
assert.Equal(t, "client-1", client.ID)
assert.Equal(t, "user@example.com", client.Username)
assert.Equal(t, 1, cm.Count())
}
func TestClientManager_Unregister(t *testing.T) {
cm := NewClientManager(context.Background())
defer cm.Shutdown()
cm.Register("client-1", "user1", nil)
assert.Equal(t, 1, cm.Count())
cm.Unregister("client-1")
assert.Equal(t, 0, cm.Count())
}
func TestClientManager_GetClient(t *testing.T) {
cm := NewClientManager(context.Background())
defer cm.Shutdown()
cm.Register("client-1", "user1", nil)
// Get existing client
client, exists := cm.GetClient("client-1")
assert.True(t, exists)
assert.NotNil(t, client)
assert.Equal(t, "client-1", client.ID)
// Get non-existent client
_, exists = cm.GetClient("nonexistent")
assert.False(t, exists)
}
func TestClientManager_MultipleClients(t *testing.T) {
cm := NewClientManager(context.Background())
defer cm.Shutdown()
cm.Register("client-1", "user1", nil)
cm.Register("client-2", "user2", nil)
cm.Register("client-3", "user3", nil)
assert.Equal(t, 3, cm.Count())
cm.Unregister("client-2")
assert.Equal(t, 2, cm.Count())
// Verify correct client was removed
_, exists := cm.GetClient("client-2")
assert.False(t, exists)
_, exists = cm.GetClient("client-1")
assert.True(t, exists)
_, exists = cm.GetClient("client-3")
assert.True(t, exists)
}
func TestClientManager_Shutdown(t *testing.T) {
cm := NewClientManager(context.Background())
cm.Register("client-1", "user1", nil)
cm.Register("client-2", "user2", nil)
assert.Equal(t, 2, cm.Count())
cm.Shutdown()
// All clients should be removed
assert.Equal(t, 0, cm.Count())
// Context should be cancelled
select {
case <-cm.ctx.Done():
// Context was cancelled
default:
t.Fatal("Context should be cancelled after Shutdown()")
}
}
func TestClientManager_ConcurrentOperations(t *testing.T) {
cm := NewClientManager(context.Background())
defer cm.Shutdown()
// This test verifies that concurrent operations don't cause race conditions
// Run with: go test -race
var wg sync.WaitGroup
// Goroutine 1: Register clients
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 100; i++ {
cm.Register("client-"+string(rune(i)), "user", nil)
}
}()
// Goroutine 2: Get clients
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 100; i++ {
cm.GetClient("client-" + string(rune(i)))
}
}()
// Goroutine 3: Count
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 100; i++ {
cm.Count()
}
}()
wg.Wait()
}
func TestClient_ConcurrentMetadata(t *testing.T) {
client := NewClient("client-123", "user", nil)
var wg sync.WaitGroup
// Concurrent writes
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 100; i++ {
client.SetMetadata("key1", i)
}
}()
// Concurrent reads
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 100; i++ {
client.GetMetadata("key1")
}
}()
wg.Wait()
}