Files
ResolveSpec/pkg/websocketspec/subscription_test.go
2025-12-23 17:27:29 +02:00

435 lines
11 KiB
Go

package websocketspec
import (
"testing"
"github.com/bitechdev/ResolveSpec/pkg/common"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewSubscriptionManager(t *testing.T) {
sm := NewSubscriptionManager()
assert.NotNil(t, sm)
assert.NotNil(t, sm.subscriptions)
assert.NotNil(t, sm.entitySubscriptions)
assert.Equal(t, 0, sm.Count())
}
func TestSubscriptionManager_Subscribe(t *testing.T) {
sm := NewSubscriptionManager()
// Create a subscription
sub := sm.Subscribe("sub-1", "conn-1", "public", "users", nil)
assert.NotNil(t, sub)
assert.Equal(t, "sub-1", sub.ID)
assert.Equal(t, "conn-1", sub.ConnectionID)
assert.Equal(t, "public", sub.Schema)
assert.Equal(t, "users", sub.Entity)
assert.True(t, sub.Active)
assert.Equal(t, 1, sm.Count())
}
func TestSubscriptionManager_Subscribe_WithOptions(t *testing.T) {
sm := NewSubscriptionManager()
options := &common.RequestOptions{
Filters: []common.FilterOption{
{Column: "status", Operator: "eq", Value: "active"},
},
}
sub := sm.Subscribe("sub-1", "conn-1", "public", "users", options)
assert.NotNil(t, sub)
assert.NotNil(t, sub.Options)
assert.Len(t, sub.Options.Filters, 1)
assert.Equal(t, "status", sub.Options.Filters[0].Column)
}
func TestSubscriptionManager_Subscribe_MultipleSubscriptions(t *testing.T) {
sm := NewSubscriptionManager()
sub1 := sm.Subscribe("sub-1", "conn-1", "public", "users", nil)
sub2 := sm.Subscribe("sub-2", "conn-1", "public", "posts", nil)
sub3 := sm.Subscribe("sub-3", "conn-2", "public", "users", nil)
assert.NotNil(t, sub1)
assert.NotNil(t, sub2)
assert.NotNil(t, sub3)
assert.Equal(t, 3, sm.Count())
}
func TestSubscriptionManager_Unsubscribe(t *testing.T) {
sm := NewSubscriptionManager()
sm.Subscribe("sub-1", "conn-1", "public", "users", nil)
assert.Equal(t, 1, sm.Count())
// Unsubscribe
ok := sm.Unsubscribe("sub-1")
assert.True(t, ok)
assert.Equal(t, 0, sm.Count())
}
func TestSubscriptionManager_Unsubscribe_NonExistent(t *testing.T) {
sm := NewSubscriptionManager()
ok := sm.Unsubscribe("non-existent")
assert.False(t, ok)
}
func TestSubscriptionManager_Unsubscribe_MultipleSubscriptions(t *testing.T) {
sm := NewSubscriptionManager()
sm.Subscribe("sub-1", "conn-1", "public", "users", nil)
sm.Subscribe("sub-2", "conn-1", "public", "posts", nil)
sm.Subscribe("sub-3", "conn-2", "public", "users", nil)
assert.Equal(t, 3, sm.Count())
// Unsubscribe one
ok := sm.Unsubscribe("sub-2")
assert.True(t, ok)
assert.Equal(t, 2, sm.Count())
// Verify the right subscription was removed
_, exists := sm.GetSubscription("sub-2")
assert.False(t, exists)
_, exists = sm.GetSubscription("sub-1")
assert.True(t, exists)
_, exists = sm.GetSubscription("sub-3")
assert.True(t, exists)
}
func TestSubscriptionManager_GetSubscription(t *testing.T) {
sm := NewSubscriptionManager()
sm.Subscribe("sub-1", "conn-1", "public", "users", nil)
// Get existing subscription
sub, exists := sm.GetSubscription("sub-1")
assert.True(t, exists)
assert.NotNil(t, sub)
assert.Equal(t, "sub-1", sub.ID)
}
func TestSubscriptionManager_GetSubscription_NonExistent(t *testing.T) {
sm := NewSubscriptionManager()
sub, exists := sm.GetSubscription("non-existent")
assert.False(t, exists)
assert.Nil(t, sub)
}
func TestSubscriptionManager_GetSubscriptionsByEntity(t *testing.T) {
sm := NewSubscriptionManager()
sm.Subscribe("sub-1", "conn-1", "public", "users", nil)
sm.Subscribe("sub-2", "conn-2", "public", "users", nil)
sm.Subscribe("sub-3", "conn-1", "public", "posts", nil)
// Get subscriptions for users entity
subs := sm.GetSubscriptionsByEntity("public", "users")
assert.Len(t, subs, 2)
// Verify subscription IDs
ids := make([]string, len(subs))
for i, sub := range subs {
ids[i] = sub.ID
}
assert.Contains(t, ids, "sub-1")
assert.Contains(t, ids, "sub-2")
}
func TestSubscriptionManager_GetSubscriptionsByEntity_NoSchema(t *testing.T) {
sm := NewSubscriptionManager()
sm.Subscribe("sub-1", "conn-1", "", "users", nil)
sm.Subscribe("sub-2", "conn-2", "", "users", nil)
// Get subscriptions without schema
subs := sm.GetSubscriptionsByEntity("", "users")
assert.Len(t, subs, 2)
}
func TestSubscriptionManager_GetSubscriptionsByEntity_NoResults(t *testing.T) {
sm := NewSubscriptionManager()
sm.Subscribe("sub-1", "conn-1", "public", "users", nil)
// Get subscriptions for non-existent entity
subs := sm.GetSubscriptionsByEntity("public", "posts")
assert.Nil(t, subs)
}
func TestSubscriptionManager_GetSubscriptionsByConnection(t *testing.T) {
sm := NewSubscriptionManager()
sm.Subscribe("sub-1", "conn-1", "public", "users", nil)
sm.Subscribe("sub-2", "conn-1", "public", "posts", nil)
sm.Subscribe("sub-3", "conn-2", "public", "users", nil)
// Get subscriptions for connection 1
subs := sm.GetSubscriptionsByConnection("conn-1")
assert.Len(t, subs, 2)
// Verify subscription IDs
ids := make([]string, len(subs))
for i, sub := range subs {
ids[i] = sub.ID
}
assert.Contains(t, ids, "sub-1")
assert.Contains(t, ids, "sub-2")
}
func TestSubscriptionManager_GetSubscriptionsByConnection_NoResults(t *testing.T) {
sm := NewSubscriptionManager()
sm.Subscribe("sub-1", "conn-1", "public", "users", nil)
// Get subscriptions for non-existent connection
subs := sm.GetSubscriptionsByConnection("conn-2")
assert.Empty(t, subs)
}
func TestSubscriptionManager_Count(t *testing.T) {
sm := NewSubscriptionManager()
assert.Equal(t, 0, sm.Count())
sm.Subscribe("sub-1", "conn-1", "public", "users", nil)
assert.Equal(t, 1, sm.Count())
sm.Subscribe("sub-2", "conn-1", "public", "posts", nil)
assert.Equal(t, 2, sm.Count())
sm.Unsubscribe("sub-1")
assert.Equal(t, 1, sm.Count())
sm.Unsubscribe("sub-2")
assert.Equal(t, 0, sm.Count())
}
func TestSubscriptionManager_CountForEntity(t *testing.T) {
sm := NewSubscriptionManager()
sm.Subscribe("sub-1", "conn-1", "public", "users", nil)
sm.Subscribe("sub-2", "conn-2", "public", "users", nil)
sm.Subscribe("sub-3", "conn-1", "public", "posts", nil)
assert.Equal(t, 2, sm.CountForEntity("public", "users"))
assert.Equal(t, 1, sm.CountForEntity("public", "posts"))
assert.Equal(t, 0, sm.CountForEntity("public", "orders"))
}
func TestSubscriptionManager_UnsubscribeUpdatesEntityIndex(t *testing.T) {
sm := NewSubscriptionManager()
sm.Subscribe("sub-1", "conn-1", "public", "users", nil)
sm.Subscribe("sub-2", "conn-2", "public", "users", nil)
assert.Equal(t, 2, sm.CountForEntity("public", "users"))
// Unsubscribe one
sm.Unsubscribe("sub-1")
assert.Equal(t, 1, sm.CountForEntity("public", "users"))
// Unsubscribe the other
sm.Unsubscribe("sub-2")
assert.Equal(t, 0, sm.CountForEntity("public", "users"))
}
func TestSubscription_MatchesFilters_NoFilters(t *testing.T) {
sub := &Subscription{
ID: "sub-1",
ConnectionID: "conn-1",
Schema: "public",
Entity: "users",
Options: nil,
Active: true,
}
data := map[string]interface{}{
"id": 1,
"name": "John",
"status": "active",
}
// Should match when no filters are specified
assert.True(t, sub.MatchesFilters(data))
}
func TestSubscription_MatchesFilters_WithFilters(t *testing.T) {
sub := &Subscription{
ID: "sub-1",
ConnectionID: "conn-1",
Schema: "public",
Entity: "users",
Options: &common.RequestOptions{
Filters: []common.FilterOption{
{Column: "status", Operator: "eq", Value: "active"},
},
},
Active: true,
}
data := map[string]interface{}{
"id": 1,
"name": "John",
"status": "active",
}
// Current implementation returns true for all data
// This test documents the expected behavior
assert.True(t, sub.MatchesFilters(data))
}
func TestSubscription_MatchesFilters_EmptyFiltersArray(t *testing.T) {
sub := &Subscription{
ID: "sub-1",
ConnectionID: "conn-1",
Schema: "public",
Entity: "users",
Options: &common.RequestOptions{
Filters: []common.FilterOption{},
},
Active: true,
}
data := map[string]interface{}{
"id": 1,
"name": "John",
}
// Should match when filters array is empty
assert.True(t, sub.MatchesFilters(data))
}
func TestMakeEntityKey(t *testing.T) {
tests := []struct {
name string
schema string
entity string
expected string
}{
{
name: "With schema",
schema: "public",
entity: "users",
expected: "public.users",
},
{
name: "Without schema",
schema: "",
entity: "users",
expected: "users",
},
{
name: "Different schema",
schema: "custom",
entity: "posts",
expected: "custom.posts",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := makeEntityKey(tt.schema, tt.entity)
assert.Equal(t, tt.expected, result)
})
}
}
func TestSubscriptionManager_ConcurrentOperations(t *testing.T) {
sm := NewSubscriptionManager()
// This test verifies that concurrent operations don't cause race conditions
// Run with: go test -race
done := make(chan bool)
// Goroutine 1: Subscribe
go func() {
for i := 0; i < 100; i++ {
sm.Subscribe("sub-"+string(rune(i)), "conn-1", "public", "users", nil)
}
done <- true
}()
// Goroutine 2: Get subscriptions
go func() {
for i := 0; i < 100; i++ {
sm.GetSubscriptionsByEntity("public", "users")
}
done <- true
}()
// Goroutine 3: Count
go func() {
for i := 0; i < 100; i++ {
sm.Count()
}
done <- true
}()
// Wait for all goroutines
<-done
<-done
<-done
}
func TestSubscriptionManager_CompleteLifecycle(t *testing.T) {
sm := NewSubscriptionManager()
// Create subscriptions
options := &common.RequestOptions{
Filters: []common.FilterOption{
{Column: "status", Operator: "eq", Value: "active"},
},
}
sub1 := sm.Subscribe("sub-1", "conn-1", "public", "users", options)
require.NotNil(t, sub1)
assert.Equal(t, 1, sm.Count())
sub2 := sm.Subscribe("sub-2", "conn-1", "public", "posts", nil)
require.NotNil(t, sub2)
assert.Equal(t, 2, sm.Count())
// Get by entity
userSubs := sm.GetSubscriptionsByEntity("public", "users")
assert.Len(t, userSubs, 1)
assert.Equal(t, "sub-1", userSubs[0].ID)
// Get by connection
connSubs := sm.GetSubscriptionsByConnection("conn-1")
assert.Len(t, connSubs, 2)
// Get specific subscription
sub, exists := sm.GetSubscription("sub-1")
assert.True(t, exists)
assert.Equal(t, "sub-1", sub.ID)
assert.NotNil(t, sub.Options)
// Count by entity
assert.Equal(t, 1, sm.CountForEntity("public", "users"))
assert.Equal(t, 1, sm.CountForEntity("public", "posts"))
// Unsubscribe
ok := sm.Unsubscribe("sub-1")
assert.True(t, ok)
assert.Equal(t, 1, sm.Count())
assert.Equal(t, 0, sm.CountForEntity("public", "users"))
// Verify subscription is gone
_, exists = sm.GetSubscription("sub-1")
assert.False(t, exists)
// Unsubscribe second subscription
ok = sm.Unsubscribe("sub-2")
assert.True(t, ok)
assert.Equal(t, 0, sm.Count())
}