Files
agent-desk-ng/internal/services/ws_service_test.go
mono 2f7a491666 feat: harden support, localize knowledge and add operator availability [skip ci]
Source-only publication of reviewed commit 64e96a7f423673dc29ad56485be9fc79bce8af1c.

Passed in isolation: focused frontend/backend tests, race checks, TypeScript typecheck, lint, Go compilation, Flowgram build, and deterministic SDK build/tests.

Not accepted as a production release: Next.js export/native assembly remains blocked by environment memory limits; browser interaction and MySQL staging acceptance remain unverified. No production server, database/schema, runtime configuration, deployment, release tag, or workflow execution was performed.

Expected source tree: 0a4a7528e9.
2026-10-16 15:21:42 +00:00

178 lines
5.5 KiB
Go

// 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:
}
}