// Modified in the independent hardening fork; see docs/hardening. package services import ( "encoding/json" "testing" "time" "agent-desk/internal/models" "agent-desk/internal/pkg/dto/response" "agent-desk/internal/pkg/enums" ) func TestWsNotificationTopic(t *testing.T) { svc := newWsService() if got := svc.notificationTopic(123); got != "notification:123" { t.Fatalf("expected notification:123, got %q", got) } } func TestWsNotificationCreatedEventType(t *testing.T) { event := RealtimeNotificationCreatedEvent{ Payload: RealtimeNotificationCreatedPayload{ Notification: response.NotificationResponse{ID: 1}, }, } if got := event.EventType(); got != "notification.created" { t.Fatalf("expected notification.created, got %q", got) } if payload := event.EventPayload(); payload == nil { t.Fatalf("expected payload") } } func TestWsAgentActivityEventType(t *testing.T) { event := RealtimeAgentActivityEvent{ Payload: RealtimeAgentActivityPayload{ ConversationID: 100, StepCode: "searching_knowledge", Status: "running", }, } if got := event.EventType(); got != enums.IMRealtimeEventAgentActivity { t.Fatalf("expected %s, got %q", enums.IMRealtimeEventAgentActivity, got) } if payload := event.EventPayload(); payload == nil { t.Fatalf("expected payload") } } func TestPublishAgentActivity(t *testing.T) { f := newWsAuthFixture(t, true) ws := newWsService() session := newWsAuthClient(f, "test-sess", realtimeRoleAdmin) ws.manager.Register(session, []string{"conversation:100"}) conv := &models.Conversation{ID: 100} ws.PublishAgentActivity(conv, "searching_knowledge", "", "running") select { case payload := <-session.Send: var event struct { Type string `json:"type"` Data RealtimeAgentActivityPayload `json:"data"` } if err := json.Unmarshal(payload, &event); err != nil { t.Fatalf("failed to unmarshal event: %v", err) } if event.Type != enums.IMRealtimeEventAgentActivity { t.Fatalf("expected type %s, got %s", enums.IMRealtimeEventAgentActivity, event.Type) } if event.Data.ConversationID != 100 || event.Data.StepCode != "searching_knowledge" || event.Data.Status != "running" { t.Fatalf("unexpected data: %+v", event.Data) } case <-time.After(time.Second): t.Fatal("timed out waiting for agent activity event") } } func TestWsOperatorPresenceEventType(t *testing.T) { event := RealtimeOperatorPresenceEvent{ Payload: RealtimeOperatorPresencePayload{ ConversationID: 100, UserID: 1, Username: "admin", Nickname: "Admin", Action: "viewing", At: time.Now().Format(time.RFC3339), }, } if got := event.EventType(); got != enums.IMRealtimeEventOperatorPresence { t.Fatalf("expected %s, got %q", enums.IMRealtimeEventOperatorPresence, got) } if payload := event.EventPayload(); payload == nil { t.Fatalf("expected payload") } } func TestPublishOperatorPresence(t *testing.T) { f := newWsAuthFixture(t, true) ws := newWsService() subscriber := newWsAuthClient(f, "subscriber-conn", realtimeRoleAdmin) ws.manager.Register(subscriber, []string{"conversation:100"}) sender := newWsAuthClient(f, "sender-conn", realtimeRoleAdmin) ws.manager.Register(sender, []string{"conversation:100"}) principal := f.principal ws.PublishOperatorPresence(100, principal, "typing", sender.ID) select { case payload := <-subscriber.Send: var event struct { Type string `json:"type"` Data RealtimeOperatorPresencePayload `json:"data"` } if err := json.Unmarshal(payload, &event); err != nil { t.Fatalf("failed to unmarshal event: %v", err) } if event.Type != enums.IMRealtimeEventOperatorPresence { t.Fatalf("expected type %s, got %s", enums.IMRealtimeEventOperatorPresence, event.Type) } if event.Data.ConversationID != 100 || event.Data.UserID != principal.UserID || event.Data.Username != principal.Username || event.Data.Nickname != principal.Nickname || event.Data.Action != "typing" { t.Fatalf("unexpected data: %+v", event.Data) } case <-time.After(time.Second): t.Fatal("timed out waiting for operator presence event on subscriber") } select { case payload := <-sender.Send: t.Fatalf("sender should have been excluded, received: %s", string(payload)) default: } } func TestHandleClientMessagePresence(t *testing.T) { f := newWsAuthFixture(t, true) ws := newWsService() subscriber := newWsAuthClient(f, "subscriber-conn", realtimeRoleAdmin) ws.manager.Register(subscriber, []string{"conversation:100"}) sender := newWsAuthClient(f, "sender-conn", realtimeRoleAdmin) ws.manager.Register(sender, []string{"conversation:100"}) msgJSON := []byte(`{"type":"presence","conversationId":100,"action":"viewing"}`) ws.handleClientMessage(sender, msgJSON) select { case payload := <-subscriber.Send: var event struct { Type string `json:"type"` Data RealtimeOperatorPresencePayload `json:"data"` } if err := json.Unmarshal(payload, &event); err != nil { t.Fatalf("failed to unmarshal event: %v", err) } if event.Type != enums.IMRealtimeEventOperatorPresence { t.Fatalf("expected %s, got %s", enums.IMRealtimeEventOperatorPresence, event.Type) } if event.Data.ConversationID != 100 || event.Data.Action != "viewing" || event.Data.UserID != f.principal.UserID { t.Fatalf("unexpected data: %+v", event.Data) } case <-time.After(time.Second): t.Fatal("timed out waiting for presence broadcast") } select { case payload := <-sender.Send: t.Fatalf("sender should not receive its own presence: %s", string(payload)) default: } }