Files
gochat/backend/internal/dispatch/coverage3_test.go
T

540 lines
15 KiB
Go

package dispatch
import (
"context"
"encoding/json"
"fmt"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/gochat/gochat/internal/channel"
"github.com/gochat/gochat/internal/model"
)
// --- mock listener for testing ---
type mockListener_Cov3 struct {
name string
err error
calls int32
events []*channel.ChannelEvent
mu sync.Mutex
}
func (m *mockListener_Cov3) Name() string { return m.name }
func (m *mockListener_Cov3) OnEvent(ctx context.Context, event *channel.ChannelEvent) error {
atomic.AddInt32(&m.calls, 1)
m.mu.Lock()
m.events = append(m.events, event)
m.mu.Unlock()
return m.err
}
func newMockListener_Cov3(name string, err error) *mockListener_Cov3 {
return &mockListener_Cov3{name: name, err: err}
}
// --- ListenerRegistry tests ---
func TestListenerRegistry_SubscribeWildcard_Cov3(t *testing.T) {
r := NewListenerRegistry()
r.Subscribe("listener1") // wildcard
subs := r.SubscribersForEvent("message.created")
assert.Contains(t, subs, "listener1")
}
func TestListenerRegistry_SubscribeSpecific_Cov3(t *testing.T) {
r := NewListenerRegistry()
r.Subscribe("listener1", "message.created")
subs := r.SubscribersForEvent("message.created")
assert.Contains(t, subs, "listener1")
// Should NOT be returned for other events
subs2 := r.SubscribersForEvent("conversation.created")
assert.NotContains(t, subs2, "listener1")
}
func TestListenerRegistry_SubscribeMultipleEvents_Cov3(t *testing.T) {
r := NewListenerRegistry()
r.Subscribe("listener1", "message.created", "conversation.created")
subs1 := r.SubscribersForEvent("message.created")
assert.Contains(t, subs1, "listener1")
subs2 := r.SubscribersForEvent("conversation.created")
assert.Contains(t, subs2, "listener1")
}
func TestListenerRegistry_Unsubscribe_Cov3(t *testing.T) {
r := NewListenerRegistry()
r.Subscribe("listener1", "message.created")
r.Unsubscribe("listener1")
subs := r.SubscribersForEvent("message.created")
assert.NotContains(t, subs, "listener1")
}
func TestListenerRegistry_UnsubscribeCleansEmpty_Cov3(t *testing.T) {
r := NewListenerRegistry()
r.Subscribe("listener1", "message.created")
r.Unsubscribe("listener1")
// Event should be removed from subscriptions when no more listeners
assert.Empty(t, r.subscriptions["message.created"])
}
func TestListenerRegistry_AllSubscriptions_Cov3(t *testing.T) {
r := NewListenerRegistry()
r.Subscribe("listener1", "message.created")
r.Subscribe("listener2", "conversation.created")
subs := r.AllSubscriptions()
assert.Contains(t, subs, "message.created")
assert.Contains(t, subs, "conversation.created")
}
func TestListenerRegistry_HasSubscribers_Wildcard_Cov3(t *testing.T) {
r := NewListenerRegistry()
r.Subscribe("listener1") // wildcard
assert.True(t, r.HasSubscribers("any.event"))
}
func TestListenerRegistry_HasSubscribers_Specific_Cov3(t *testing.T) {
r := NewListenerRegistry()
r.Subscribe("listener1", "message.created")
assert.True(t, r.HasSubscribers("message.created"))
assert.False(t, r.HasSubscribers("conversation.created"))
}
func TestListenerRegistry_HasSubscribers_None_Cov3(t *testing.T) {
r := NewListenerRegistry()
assert.False(t, r.HasSubscribers("message.created"))
}
func TestListenerRegistry_SubscribersForEvent_NoMatch_Cov3(t *testing.T) {
r := NewListenerRegistry()
subs := r.SubscribersForEvent("nonexistent.event")
assert.Empty(t, subs)
}
func TestListenerRegistry_SubscribersForEvent_Dedup_Cov3(t *testing.T) {
r := NewListenerRegistry()
r.Subscribe("listener1") // wildcard
r.Subscribe("listener1", "message.created") // specific (same listener)
subs := r.SubscribersForEvent("message.created")
// Should be deduplicated — only one entry
assert.Len(t, subs, 1)
assert.Contains(t, subs, "listener1")
}
// --- Event helpers tests ---
func TestExtractConversationID_Uint_Cov3(t *testing.T) {
data := map[string]interface{}{"conversation_id": uint(42)}
id, err := ExtractConversationID(data)
require.NoError(t, err)
assert.Equal(t, uint(42), id)
}
func TestExtractConversationID_Int_Cov3(t *testing.T) {
data := map[string]interface{}{"conversation_id": int(42)}
id, err := ExtractConversationID(data)
require.NoError(t, err)
assert.Equal(t, uint(42), id)
}
func TestExtractConversationID_Float64_Cov3(t *testing.T) {
data := map[string]interface{}{"conversation_id": float64(42)}
id, err := ExtractConversationID(data)
require.NoError(t, err)
assert.Equal(t, uint(42), id)
}
func TestExtractConversationID_Missing_Cov3(t *testing.T) {
data := map[string]interface{}{}
_, err := ExtractConversationID(data)
assert.Error(t, err)
}
func TestExtractConversationID_WrongType_Cov3(t *testing.T) {
data := map[string]interface{}{"conversation_id": "not a number"}
_, err := ExtractConversationID(data)
assert.Error(t, err)
}
func TestExtractAccountID_Uint_Cov3(t *testing.T) {
data := map[string]interface{}{"account_id": uint(1)}
id, err := ExtractAccountID(data)
require.NoError(t, err)
assert.Equal(t, uint(1), id)
}
func TestExtractAccountID_Int_Cov3(t *testing.T) {
data := map[string]interface{}{"account_id": int(1)}
id, err := ExtractAccountID(data)
require.NoError(t, err)
assert.Equal(t, uint(1), id)
}
func TestExtractAccountID_Float64_Cov3(t *testing.T) {
data := map[string]interface{}{"account_id": float64(1)}
id, err := ExtractAccountID(data)
require.NoError(t, err)
assert.Equal(t, uint(1), id)
}
func TestExtractAccountID_Missing_Cov3(t *testing.T) {
data := map[string]interface{}{}
_, err := ExtractAccountID(data)
assert.Error(t, err)
}
func TestExtractAccountID_WrongType_Cov3(t *testing.T) {
data := map[string]interface{}{"account_id": true}
_, err := ExtractAccountID(data)
assert.Error(t, err)
}
func TestExtractInboxID_Uint_Cov3(t *testing.T) {
data := map[string]interface{}{"inbox_id": uint(5)}
id, err := ExtractInboxID(data)
require.NoError(t, err)
assert.Equal(t, uint(5), id)
}
func TestExtractInboxID_Int_Cov3(t *testing.T) {
data := map[string]interface{}{"inbox_id": int(5)}
id, err := ExtractInboxID(data)
require.NoError(t, err)
assert.Equal(t, uint(5), id)
}
func TestExtractInboxID_Float64_Cov3(t *testing.T) {
data := map[string]interface{}{"inbox_id": float64(5)}
id, err := ExtractInboxID(data)
require.NoError(t, err)
assert.Equal(t, uint(5), id)
}
func TestExtractInboxID_Missing_Cov3(t *testing.T) {
data := map[string]interface{}{}
_, err := ExtractInboxID(data)
assert.Error(t, err)
}
func TestExtractInboxID_WrongType_Cov3(t *testing.T) {
data := map[string]interface{}{"inbox_id": "abc"}
_, err := ExtractInboxID(data)
assert.Error(t, err)
}
func TestExtractAssigneeID_Uint_Cov3(t *testing.T) {
data := map[string]interface{}{"assignee_id": uint(3)}
id, err := ExtractAssigneeID(data)
require.NoError(t, err)
assert.Equal(t, uint(3), id)
}
func TestExtractAssigneeID_Int_Cov3(t *testing.T) {
data := map[string]interface{}{"assignee_id": int(3)}
id, err := ExtractAssigneeID(data)
require.NoError(t, err)
assert.Equal(t, uint(3), id)
}
func TestExtractAssigneeID_Float64_Cov3(t *testing.T) {
data := map[string]interface{}{"assignee_id": float64(3)}
id, err := ExtractAssigneeID(data)
require.NoError(t, err)
assert.Equal(t, uint(3), id)
}
func TestExtractAssigneeID_Missing_Cov3(t *testing.T) {
data := map[string]interface{}{}
_, err := ExtractAssigneeID(data)
assert.Error(t, err)
}
func TestExtractAssigneeID_WrongType_Cov3(t *testing.T) {
data := map[string]interface{}{"assignee_id": []int{1}}
_, err := ExtractAssigneeID(data)
assert.Error(t, err)
}
// --- Event constructors ---
func TestNewChannelEvent_Cov3(t *testing.T) {
event := NewChannelEvent("message.created", map[string]interface{}{"foo": "bar"})
assert.Equal(t, "message.created", string(event.Type))
assert.Equal(t, "bar", event.Data["foo"])
assert.NotZero(t, event.Timestamp)
}
func TestNewChannelEvent_NilData_Cov3(t *testing.T) {
event := NewChannelEvent("test.event", nil)
assert.NotNil(t, event.Data)
}
func TestNewDispatchEvent_Cov3(t *testing.T) {
event := NewDispatchEvent(DispatchEventCampaignStarted, map[string]interface{}{"id": 1})
assert.Equal(t, "campaign.started", string(event.Type))
}
func TestEventDataWithConversation_Cov3(t *testing.T) {
data := EventDataWithConversation(1, 2, 3)
assert.Equal(t, uint(1), data["conversation_id"])
assert.Equal(t, uint(2), data["account_id"])
assert.Equal(t, uint(3), data["inbox_id"])
}
func TestEventDataWithAssignment_Cov3(t *testing.T) {
data := EventDataWithAssignment(1, 2, 3, 4)
assert.Equal(t, uint(1), data["conversation_id"])
assert.Equal(t, uint(2), data["account_id"])
assert.Equal(t, uint(3), data["inbox_id"])
assert.Equal(t, uint(4), data["assignee_id"])
}
// --- Dispatcher tests ---
func TestEventDispatcher_Dispatch_Sync_Cov3(t *testing.T) {
cd := channel.NewDispatcher()
ed := NewEventDispatcher(cd)
l1 := newMockListener_Cov3("sync_listener", nil)
ed.RegisterSync(l1, "message.created")
event := &channel.ChannelEvent{
Type: "message.created",
Data: map[string]interface{}{},
}
err := ed.Dispatch(context.Background(), event)
require.NoError(t, err)
assert.Equal(t, int32(1), atomic.LoadInt32(&l1.calls))
}
func TestEventDispatcher_Dispatch_SyncError_Cov3(t *testing.T) {
cd := channel.NewDispatcher()
ed := NewEventDispatcher(cd)
l1 := newMockListener_Cov3("err_listener", fmt.Errorf("listener error"))
ed.RegisterSync(l1, "message.created")
event := &channel.ChannelEvent{
Type: "message.created",
Data: map[string]interface{}{},
}
err := ed.Dispatch(context.Background(), event)
assert.Error(t, err)
assert.Contains(t, err.Error(), "listener error")
}
func TestEventDispatcher_Dispatch_Async_Cov3(t *testing.T) {
cd := channel.NewDispatcher()
ed := NewEventDispatcher(cd)
l1 := newMockListener_Cov3("async_listener", nil)
ed.RegisterAsync(l1, "message.created")
event := &channel.ChannelEvent{
Type: "message.created",
Data: map[string]interface{}{},
}
err := ed.Dispatch(context.Background(), event)
require.NoError(t, err) // async errors are not returned
// Wait for async goroutine to complete
time.Sleep(100 * time.Millisecond)
assert.Equal(t, int32(1), atomic.LoadInt32(&l1.calls))
}
func TestEventDispatcher_Dispatch_Wildcard_Cov3(t *testing.T) {
cd := channel.NewDispatcher()
ed := NewEventDispatcher(cd)
l1 := newMockListener_Cov3("wildcard_listener", nil)
ed.RegisterSync(l1) // wildcard — all events
event := &channel.ChannelEvent{
Type: "any.event",
Data: map[string]interface{}{},
}
err := ed.Dispatch(context.Background(), event)
require.NoError(t, err)
assert.Equal(t, int32(1), atomic.LoadInt32(&l1.calls))
}
func TestEventDispatcher_Dispatch_NoSubscribers_Cov3(t *testing.T) {
cd := channel.NewDispatcher()
ed := NewEventDispatcher(cd)
event := &channel.ChannelEvent{
Type: "message.created",
Data: map[string]interface{}{},
}
err := ed.Dispatch(context.Background(), event)
require.NoError(t, err)
}
func TestEventDispatcher_DispatchAsync_Cov3(t *testing.T) {
cd := channel.NewDispatcher()
ed := NewEventDispatcher(cd)
l1 := newMockListener_Cov3("sync_listener", nil)
ed.RegisterSync(l1, "message.created")
event := &channel.ChannelEvent{
Type: "message.created",
Data: map[string]interface{}{},
}
ed.DispatchAsync(context.Background(), event)
time.Sleep(100 * time.Millisecond)
assert.Equal(t, int32(1), atomic.LoadInt32(&l1.calls))
}
func TestEventDispatcher_DispatchAsync_NoSubscribers_Cov3(t *testing.T) {
cd := channel.NewDispatcher()
ed := NewEventDispatcher(cd)
event := &channel.ChannelEvent{
Type: "message.created",
Data: map[string]interface{}{},
}
ed.DispatchAsync(context.Background(), event)
// should not panic
}
func TestEventDispatcher_DispatchAsync_UnknownListener_Cov3(t *testing.T) {
cd := channel.NewDispatcher()
ed := NewEventDispatcher(cd)
// Manually add a subscriber name that doesn't exist in entries
ed.registry.Subscribe("nonexistent_listener", "message.created")
event := &channel.ChannelEvent{
Type: "message.created",
Data: map[string]interface{}{},
}
ed.DispatchAsync(context.Background(), event)
// should not panic — just skips unknown listener
}
func TestEventDispatcher_Dispatch_UnknownListenerEntry_Cov3(t *testing.T) {
cd := channel.NewDispatcher()
ed := NewEventDispatcher(cd)
// Add subscriber that doesn't have an entry
ed.registry.Subscribe("ghost", "message.created")
event := &channel.ChannelEvent{
Type: "message.created",
Data: map[string]interface{}{},
}
err := ed.Dispatch(context.Background(), event)
require.NoError(t, err)
}
func TestEventDispatcher_PerformListenerJob_Cov3(t *testing.T) {
cd := channel.NewDispatcher()
ed := NewEventDispatcher(cd)
l1 := newMockListener_Cov3("job_listener", nil)
ed.RegisterSync(l1, "message.created")
event := &channel.ChannelEvent{
Type: "message.created",
Data: map[string]interface{}{},
}
payload := listenerJobPayload{ListenerName: "job_listener", Event: event}
payloadBytes, _ := json.Marshal(payload)
job := &model.BackgroundJob{
Payload: payloadBytes,
}
err := ed.performListenerJob(context.Background(), job)
require.NoError(t, err)
assert.Equal(t, int32(1), atomic.LoadInt32(&l1.calls))
}
func TestEventDispatcher_PerformListenerJob_BadPayload_Cov3(t *testing.T) {
cd := channel.NewDispatcher()
ed := NewEventDispatcher(cd)
job := &model.BackgroundJob{
Payload: json.RawMessage(`{"bad": "json"`),
}
err := ed.performListenerJob(context.Background(), job)
assert.Error(t, err)
}
func TestEventDispatcher_PerformListenerJob_UnknownListener_Cov3(t *testing.T) {
cd := channel.NewDispatcher()
ed := NewEventDispatcher(cd)
event := &channel.ChannelEvent{
Type: "message.created",
Data: map[string]interface{}{},
}
payload := listenerJobPayload{ListenerName: "unknown", Event: event}
payloadBytes, _ := json.Marshal(payload)
job := &model.BackgroundJob{
Payload: payloadBytes,
}
err := ed.performListenerJob(context.Background(), job)
assert.Error(t, err)
assert.Contains(t, err.Error(), "not registered")
}
func TestEventDispatcher_PerformListenerJob_NilEvent_Cov3(t *testing.T) {
cd := channel.NewDispatcher()
ed := NewEventDispatcher(cd)
l1 := newMockListener_Cov3("job_listener", nil)
ed.RegisterSync(l1, "message.created")
payload := listenerJobPayload{ListenerName: "job_listener", Event: nil}
payloadBytes, _ := json.Marshal(payload)
job := &model.BackgroundJob{
Payload: payloadBytes,
}
err := ed.performListenerJob(context.Background(), job)
assert.Error(t, err)
assert.Contains(t, err.Error(), "missing event")
}
func TestEventDispatcher_ChannelDispatcher_Cov3(t *testing.T) {
cd := channel.NewDispatcher()
ed := NewEventDispatcher(cd)
assert.NotNil(t, ed.ChannelDispatcher())
}
func TestEventDispatcher_Registry_Cov3(t *testing.T) {
cd := channel.NewDispatcher()
ed := NewEventDispatcher(cd)
assert.NotNil(t, ed.Registry())
}
func TestEventDispatcher_RegisterSync_Cov3(t *testing.T) {
cd := channel.NewDispatcher()
ed := NewEventDispatcher(cd)
l := newMockListener_Cov3("test", nil)
ed.RegisterSync(l, "event1")
assert.Contains(t, ed.entries, "test")
assert.Equal(t, SyncMode, ed.entries["test"].mode)
}
func TestEventDispatcher_RegisterAsync_Cov3(t *testing.T) {
cd := channel.NewDispatcher()
ed := NewEventDispatcher(cd)
l := newMockListener_Cov3("test", nil)
ed.RegisterAsync(l, "event1")
assert.Contains(t, ed.entries, "test")
assert.Equal(t, AsyncMode, ed.entries["test"].mode)
}