323 lines
8.0 KiB
Go
323 lines
8.0 KiB
Go
package handlers
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"git.threesix.ai/jordan/persona-community-5/pkg/app"
|
|
"git.threesix.ai/jordan/persona-community-5/pkg/logging"
|
|
"git.threesix.ai/jordan/persona-community-5/pkg/queue"
|
|
"git.threesix.ai/jordan/persona-community-5/services/persona-api/internal/adapter/memory"
|
|
"git.threesix.ai/jordan/persona-community-5/services/persona-api/internal/domain"
|
|
"git.threesix.ai/jordan/persona-community-5/services/persona-api/internal/service"
|
|
)
|
|
|
|
// mockProducer implements queue.Producer for testing.
|
|
type mockProducer struct {
|
|
jobs []mockJob
|
|
}
|
|
|
|
type mockJob struct {
|
|
jobType string
|
|
payload map[string]any
|
|
}
|
|
|
|
var _ queue.Producer = (*mockProducer)(nil)
|
|
|
|
func (m *mockProducer) Enqueue(_ context.Context, jobType string, payload map[string]any) (string, error) {
|
|
m.jobs = append(m.jobs, mockJob{jobType: jobType, payload: payload})
|
|
return "test-job-id", nil
|
|
}
|
|
|
|
func (m *mockProducer) EnqueueWithOptions(_ context.Context, job queue.Job) (string, error) {
|
|
m.jobs = append(m.jobs, mockJob{jobType: job.Type, payload: job.Payload})
|
|
return "test-job-id", nil
|
|
}
|
|
|
|
func newTestPersonaHandler() (*Persona, *memory.PersonaRepository, *mockProducer) {
|
|
repo := memory.NewPersonaRepository()
|
|
q := &mockProducer{}
|
|
svc := service.NewPersonaService(repo, q, nil, logging.Nop())
|
|
handler := NewPersona(svc, logging.Nop())
|
|
return handler, repo, q
|
|
}
|
|
|
|
func TestPersona_Create(t *testing.T) {
|
|
handler, _, q := newTestPersonaHandler()
|
|
|
|
tests := []struct {
|
|
name string
|
|
body any
|
|
wantStatus int
|
|
}{
|
|
{
|
|
name: "valid request",
|
|
body: CreatePersonaRequest{
|
|
Description: "mysterious woman with dark hair",
|
|
Gender: "woman",
|
|
},
|
|
wantStatus: http.StatusAccepted,
|
|
},
|
|
{
|
|
name: "valid request with custom name",
|
|
body: CreatePersonaRequest{
|
|
Description: "energetic man who loves music",
|
|
Gender: "man",
|
|
CustomName: "DJ Beats",
|
|
},
|
|
wantStatus: http.StatusAccepted,
|
|
},
|
|
{
|
|
name: "empty body",
|
|
body: nil,
|
|
wantStatus: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "missing description",
|
|
body: map[string]string{
|
|
"gender": "woman",
|
|
},
|
|
wantStatus: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "invalid gender",
|
|
body: map[string]string{
|
|
"description": "a persona",
|
|
"gender": "invalid",
|
|
},
|
|
wantStatus: http.StatusBadRequest,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Post("/personas", app.Wrap(handler.Create))
|
|
|
|
var body []byte
|
|
if tt.body != nil {
|
|
var err error
|
|
body, err = json.Marshal(tt.body)
|
|
if err != nil {
|
|
t.Fatalf("failed to marshal body: %v", err)
|
|
}
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/personas", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
if w.Code != tt.wantStatus {
|
|
t.Errorf("expected status %d, got %d; body: %s", tt.wantStatus, w.Code, w.Body.String())
|
|
}
|
|
})
|
|
}
|
|
|
|
// Verify a generate_spec job was enqueued
|
|
found := false
|
|
for _, j := range q.jobs {
|
|
if j.jobType == "generate_spec" {
|
|
found = true
|
|
if j.payload["stage"] != "spec" {
|
|
t.Errorf("expected stage 'spec', got %v", j.payload["stage"])
|
|
}
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("expected generate_spec job to be enqueued")
|
|
}
|
|
}
|
|
|
|
func TestPersona_GetByID(t *testing.T) {
|
|
handler, repo, _ := newTestPersonaHandler()
|
|
|
|
// Seed data
|
|
persona := &domain.Persona{
|
|
Name: "Test Persona",
|
|
Handle: "test_persona",
|
|
Gender: "woman",
|
|
Description: "A test persona",
|
|
Tags: []string{},
|
|
ImageURLs: []string{},
|
|
VideoURLs: []string{},
|
|
Status: domain.PersonaStatusPending,
|
|
}
|
|
_ = repo.Create(context.Background(), persona)
|
|
seededID := persona.ID.String()
|
|
|
|
tests := []struct {
|
|
name string
|
|
id string
|
|
wantStatus int
|
|
}{
|
|
{
|
|
name: "existing persona",
|
|
id: seededID,
|
|
wantStatus: http.StatusOK,
|
|
},
|
|
{
|
|
name: "not found",
|
|
id: "550e8400-e29b-41d4-a716-446655440099",
|
|
wantStatus: http.StatusNotFound,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Get("/personas/{id}", app.Wrap(handler.GetByID))
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/personas/"+tt.id, nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
if w.Code != tt.wantStatus {
|
|
t.Errorf("expected status %d, got %d; body: %s", tt.wantStatus, w.Code, w.Body.String())
|
|
}
|
|
|
|
if tt.wantStatus == http.StatusOK {
|
|
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["id"] != seededID {
|
|
t.Errorf("expected id %s, got %v", seededID, data["id"])
|
|
}
|
|
if data["name"] != "Test Persona" {
|
|
t.Errorf("expected name 'Test Persona', got %v", data["name"])
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPersona_List(t *testing.T) {
|
|
handler, repo, _ := newTestPersonaHandler()
|
|
|
|
t.Run("empty list", func(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Get("/personas", app.Wrap(handler.List))
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/personas", nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d", w.Code)
|
|
}
|
|
|
|
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"].([]any)
|
|
if !ok {
|
|
t.Fatal("expected 'data' to be an array")
|
|
}
|
|
if len(data) != 0 {
|
|
t.Errorf("expected 0 items, got %d", len(data))
|
|
}
|
|
})
|
|
|
|
// Seed data
|
|
for i, name := range []string{"Persona A", "Persona B", "Persona C"} {
|
|
p := &domain.Persona{
|
|
Name: name,
|
|
Handle: name + "_handle",
|
|
Gender: "woman",
|
|
Description: "description " + string(rune('A'+i)),
|
|
Tags: []string{},
|
|
ImageURLs: []string{},
|
|
VideoURLs: []string{},
|
|
Status: domain.PersonaStatusPending,
|
|
}
|
|
_ = repo.Create(context.Background(), p)
|
|
}
|
|
|
|
t.Run("returns all personas", func(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Get("/personas", app.Wrap(handler.List))
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/personas", nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d", w.Code)
|
|
}
|
|
|
|
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"].([]any)
|
|
if !ok {
|
|
t.Fatal("expected 'data' to be an array")
|
|
}
|
|
if len(data) != 3 {
|
|
t.Errorf("expected 3 items, got %d", len(data))
|
|
}
|
|
})
|
|
|
|
t.Run("respects limit parameter", func(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Get("/personas", app.Wrap(handler.List))
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/personas?limit=2", nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d", w.Code)
|
|
}
|
|
|
|
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"].([]any)
|
|
if !ok {
|
|
t.Fatal("expected 'data' to be an array")
|
|
}
|
|
if len(data) != 2 {
|
|
t.Errorf("expected 2 items, got %d", len(data))
|
|
}
|
|
})
|
|
|
|
t.Run("respects offset parameter", func(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Get("/personas", app.Wrap(handler.List))
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/personas?offset=2", nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d", w.Code)
|
|
}
|
|
|
|
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"].([]any)
|
|
if !ok {
|
|
t.Fatal("expected 'data' to be an array")
|
|
}
|
|
if len(data) != 1 {
|
|
t.Errorf("expected 1 item, got %d", len(data))
|
|
}
|
|
})
|
|
}
|