harryagasi
feat(analysis): add success/failed status to chat messages
981b42b
Raw
History Blame Contribute Delete
5.14 kB
package api
import (
"context"
"errors"
"io"
"log/slog"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/google/uuid"
"project/internal/auth"
"project/internal/models"
)
type analysisServiceStub struct {
append func(ctx context.Context, analysisID uuid.UUID, userID, role, content string, messageID uuid.UUID) (*models.ChatMessage, error)
}
func (s analysisServiceStub) CreateAnalysis(ctx context.Context, title, objective, userID string, businessQuestions models.BusinessQuestionList, dataBind models.DataBindList) (*models.Analysis, error) {
return nil, nil
}
func (s analysisServiceStub) GetAnalysis(ctx context.Context, id uuid.UUID, userID string) (*models.Analysis, error) {
return nil, nil
}
func (s analysisServiceStub) ListAnalyses(ctx context.Context, userID, status string, limit, offset int) ([]*models.Analysis, error) {
return nil, nil
}
func (s analysisServiceStub) UpdateAnalysis(ctx context.Context, id uuid.UUID, userID, title, objective, status string) (*models.Analysis, error) {
return nil, nil
}
func (s analysisServiceStub) DeleteAnalysis(ctx context.Context, id uuid.UUID, userID string) error {
return nil
}
func (s analysisServiceStub) UpdateDataBind(ctx context.Context, id uuid.UUID, userID string, expectedVersion int, dataBind models.DataBindList) (*models.Analysis, error) {
return nil, nil
}
func (s analysisServiceStub) ListMessages(ctx context.Context, analysisID uuid.UUID, userID string, limit, offset int) ([]*models.ChatMessage, error) {
return nil, nil
}
func (s analysisServiceStub) AppendMessage(ctx context.Context, analysisID uuid.UUID, userID, role, content string, messageID uuid.UUID) (*models.ChatMessage, error) {
if s.append != nil {
return s.append(ctx, analysisID, userID, role, content, messageID)
}
return models.NewChatMessage(analysisID, userID, role, content, messageID), nil
}
func (s analysisServiceStub) GenerateReport(ctx context.Context, analysisID uuid.UUID, userID string) (*models.Report, error) {
return nil, nil
}
func (s analysisServiceStub) ListReports(ctx context.Context, analysisID uuid.UUID, userID string, limit, offset int) ([]*models.Report, error) {
return nil, nil
}
func TestSendMessageRequiresAuthenticatedUser(t *testing.T) {
h := NewAnalysisHandler(analysisServiceStub{}, slog.New(slog.NewTextHandler(io.Discard, nil)))
req := httptest.NewRequest(http.MethodPost, "/api/v1/analyses/"+uuid.NewString()+"/messages", strings.NewReader(`{"role":"user","content":"hello"}`))
w := httptest.NewRecorder()
h.SendMessage(w, req)
if w.Code != http.StatusUnauthorized {
t.Fatalf("status = %d, want %d; body=%s", w.Code, http.StatusUnauthorized, w.Body.String())
}
}
func TestSendMessageRejectsInvalidRole(t *testing.T) {
analysisID := uuid.New()
h := NewAnalysisHandler(analysisServiceStub{append: func(ctx context.Context, analysisID uuid.UUID, userID, role, content string, messageID uuid.UUID) (*models.ChatMessage, error) {
return nil, models.ErrMessageRoleInvalid
}}, slog.New(slog.NewTextHandler(io.Discard, nil)))
req := httptest.NewRequest(http.MethodPost, "/api/v1/analyses/"+analysisID.String()+"/messages", strings.NewReader(`{"role":"agent","content":"hello"}`))
req.SetPathValue("id", analysisID.String())
req = req.WithContext(auth.WithUser(req.Context(), auth.ContextUser{ID: "user-1"}))
w := httptest.NewRecorder()
h.SendMessage(w, req)
if w.Code != http.StatusBadRequest {
t.Fatalf("status = %d, want %d; body=%s", w.Code, http.StatusBadRequest, w.Body.String())
}
if !strings.Contains(w.Body.String(), "role must be") {
t.Fatalf("body missing role validation message: %s", w.Body.String())
}
}
func TestSendMessageReturnsSingleMessageEnvelope(t *testing.T) {
analysisID := uuid.New()
var capturedUserID string
h := NewAnalysisHandler(analysisServiceStub{append: func(ctx context.Context, id uuid.UUID, userID, role, content string, messageID uuid.UUID) (*models.ChatMessage, error) {
if id != analysisID {
return nil, errors.New("unexpected analysis id")
}
capturedUserID = userID
return models.NewChatMessage(id, userID, role, content, messageID), nil
}}, slog.New(slog.NewTextHandler(io.Discard, nil)))
req := httptest.NewRequest(http.MethodPost, "/api/v1/analyses/"+analysisID.String()+"/messages", strings.NewReader(`{"role":"ai","content":"answer","message_id":"`+uuid.NewString()+`"}`))
req.SetPathValue("id", analysisID.String())
req = req.WithContext(auth.WithUser(req.Context(), auth.ContextUser{ID: "user-1"}))
w := httptest.NewRecorder()
h.SendMessage(w, req)
if w.Code != http.StatusCreated {
t.Fatalf("status = %d, want %d; body=%s", w.Code, http.StatusCreated, w.Body.String())
}
if capturedUserID != "user-1" {
t.Fatalf("captured user id = %q, want token user", capturedUserID)
}
body := w.Body.String()
if !strings.Contains(body, `"message"`) || strings.Contains(body, `"messages"`) {
t.Fatalf("response should contain singular data.message only: %s", body)
}
if !strings.Contains(body, `"role":"ai"`) || !strings.Contains(body, `"content":"answer"`) {
t.Fatalf("response missing message role/content: %s", body)
}
}