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()) } }