sp4-debug-1770477266/services/chat-svc/internal/api/handlers/chat_test.go
rdev-worker 5a877ca1a1
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
feat: implement mesh-interop service communication
Add auth-svc /validate endpoint for token checking
Add chat-svc with auth client and Redis task queue
Add worker-svc chat handler for task processing

Co-Authored-By: Claude Code <claude@anthropic.com>
2026-02-07 16:45:22 +00:00

171 lines
4.8 KiB
Go

package handlers
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"sync"
"testing"
"github.com/go-chi/chi/v5"
"git.threesix.ai/jordan/sp4-debug-1770477266/pkg/auth"
"git.threesix.ai/jordan/sp4-debug-1770477266/pkg/logging"
"git.threesix.ai/jordan/sp4-debug-1770477266/pkg/queue"
"git.threesix.ai/jordan/sp4-debug-1770477266/services/chat-svc/internal/taskqueue"
)
// mockQueueProducer implements queue.Producer for testing.
type mockQueueProducer struct {
mu sync.Mutex
jobs []queue.Job
}
func (m *mockQueueProducer) Enqueue(ctx context.Context, jobType string, payload map[string]any) (string, error) {
return m.EnqueueWithOptions(ctx, queue.Job{
Type: jobType,
Payload: payload,
})
}
func (m *mockQueueProducer) EnqueueWithOptions(ctx context.Context, job queue.Job) (string, error) {
m.mu.Lock()
defer m.mu.Unlock()
job.ID = "mock-job-id"
m.jobs = append(m.jobs, job)
return job.ID, nil
}
func newTestChatHandler() (*Chat, *mockQueueProducer) {
mockQueue := &mockQueueProducer{}
producer := taskqueue.NewProducer(mockQueue, logging.Nop())
handler := NewChat(producer, logging.Nop())
return handler, mockQueue
}
func TestChat_Send_Success(t *testing.T) {
handler, mockQueue := newTestChatHandler()
r := chi.NewRouter()
r.Post("/api/chat-svc/send", func(w http.ResponseWriter, r *http.Request) {
// Inject authenticated user into context
ctx := auth.SetUser(r.Context(), &auth.User{ID: "user-123", Email: "test@example.com"})
r = r.WithContext(ctx)
if err := handler.Send(w, r); err != nil {
t.Fatalf("unexpected error: %v", err)
}
})
body, _ := json.Marshal(SendRequest{Message: "Hello world"})
req := httptest.NewRequest(http.MethodPost, "/api/chat-svc/send", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusAccepted {
t.Errorf("expected status 202, got %d: %s", w.Code, w.Body.String())
}
var resp map[string]any
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
data, ok := resp["data"].(map[string]any)
if !ok {
t.Fatal("expected 'data' field in response")
}
if data["job_id"] != "mock-job-id" {
t.Errorf("expected job_id 'mock-job-id', got %v", data["job_id"])
}
if data["status"] != "queued" {
t.Errorf("expected status 'queued', got %v", data["status"])
}
// Verify job was enqueued
mockQueue.mu.Lock()
defer mockQueue.mu.Unlock()
if len(mockQueue.jobs) != 1 {
t.Fatalf("expected 1 enqueued job, got %d", len(mockQueue.jobs))
}
if mockQueue.jobs[0].Payload["user_id"] != "user-123" {
t.Errorf("expected user_id 'user-123' in payload, got %v", mockQueue.jobs[0].Payload["user_id"])
}
}
func TestChat_Send_NoAuth(t *testing.T) {
handler, _ := newTestChatHandler()
r := chi.NewRouter()
r.Post("/api/chat-svc/send", func(w http.ResponseWriter, r *http.Request) {
if err := handler.Send(w, r); err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
})
body, _ := json.Marshal(SendRequest{Message: "Hello"})
req := httptest.NewRequest(http.MethodPost, "/api/chat-svc/send", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusUnauthorized {
t.Errorf("expected status 401, got %d", w.Code)
}
}
func TestChat_Send_NilProducer(t *testing.T) {
handler := NewChat(nil, logging.Nop())
r := chi.NewRouter()
r.Post("/api/chat-svc/send", func(w http.ResponseWriter, r *http.Request) {
ctx := auth.SetUser(r.Context(), &auth.User{ID: "user-123"})
r = r.WithContext(ctx)
if err := handler.Send(w, r); err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
return
}
})
body, _ := json.Marshal(SendRequest{Message: "Hello"})
req := httptest.NewRequest(http.MethodPost, "/api/chat-svc/send", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusServiceUnavailable {
t.Errorf("expected status 503, got %d", w.Code)
}
}
func TestChat_Send_EmptyMessage(t *testing.T) {
handler, _ := newTestChatHandler()
r := chi.NewRouter()
r.Post("/api/chat-svc/send", func(w http.ResponseWriter, r *http.Request) {
ctx := auth.SetUser(r.Context(), &auth.User{ID: "user-123"})
r = r.WithContext(ctx)
if err := handler.Send(w, r); err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
})
body, _ := json.Marshal(map[string]string{"message": ""})
req := httptest.NewRequest(http.MethodPost, "/api/chat-svc/send", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
// Empty message should fail validation (required,min=1)
if w.Code != http.StatusBadRequest {
t.Errorf("expected status 400, got %d: %s", w.Code, w.Body.String())
}
}