261 lines
7.2 KiB
Go
261 lines
7.2 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"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"
|
|
)
|
|
|
|
// mockProducer implements queue.Producer for testing.
|
|
type mockProducer struct {
|
|
jobs []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, struct {
|
|
jobType string
|
|
payload map[string]any
|
|
}{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, struct {
|
|
jobType string
|
|
payload map[string]any
|
|
}{jobType: job.Type, payload: job.Payload})
|
|
return "test-job-id", nil
|
|
}
|
|
|
|
func TestPersonaService_Create(t *testing.T) {
|
|
repo := memory.NewPersonaRepository()
|
|
q := &mockProducer{}
|
|
svc := NewPersonaService(repo, q, nil, logging.Nop())
|
|
|
|
t.Run("creates persona with custom name", func(t *testing.T) {
|
|
persona, err := svc.Create(context.Background(), "mysterious woman with dark hair", "woman", "Luna Shadow")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if persona.Name != "Luna Shadow" {
|
|
t.Errorf("expected name 'Luna Shadow', got '%s'", persona.Name)
|
|
}
|
|
if persona.ID.IsZero() {
|
|
t.Error("expected non-empty ID")
|
|
}
|
|
if persona.Gender != "woman" {
|
|
t.Errorf("expected gender 'woman', got '%s'", persona.Gender)
|
|
}
|
|
if persona.Status != domain.PersonaStatusPending {
|
|
t.Errorf("expected status 'pending', got '%s'", persona.Status)
|
|
}
|
|
if persona.Handle == "" {
|
|
t.Error("expected non-empty handle")
|
|
}
|
|
})
|
|
|
|
t.Run("creates persona with generated name", func(t *testing.T) {
|
|
persona, err := svc.Create(context.Background(), "energetic man who loves music", "man", "")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if persona.Name == "" {
|
|
t.Error("expected auto-generated name")
|
|
}
|
|
if persona.ID.IsZero() {
|
|
t.Error("expected non-empty ID")
|
|
}
|
|
})
|
|
|
|
t.Run("enqueues generate_spec job", func(t *testing.T) {
|
|
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 _, ok := j.payload["persona_id"]; !ok {
|
|
t.Error("expected persona_id in job payload")
|
|
}
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("expected generate_spec job to be enqueued")
|
|
}
|
|
})
|
|
|
|
t.Run("initializes empty slices", func(t *testing.T) {
|
|
persona, err := svc.Create(context.Background(), "test persona for slices", "non_binary", "SliceTest")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if persona.Tags == nil {
|
|
t.Error("expected Tags to be initialized (not nil)")
|
|
}
|
|
if persona.ImageURLs == nil {
|
|
t.Error("expected ImageURLs to be initialized (not nil)")
|
|
}
|
|
if persona.VideoURLs == nil {
|
|
t.Error("expected VideoURLs to be initialized (not nil)")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestPersonaService_GetByID(t *testing.T) {
|
|
repo := memory.NewPersonaRepository()
|
|
q := &mockProducer{}
|
|
svc := NewPersonaService(repo, q, nil, logging.Nop())
|
|
|
|
created, _ := svc.Create(context.Background(), "test persona", "woman", "GetTest")
|
|
|
|
t.Run("returns existing persona", func(t *testing.T) {
|
|
persona, err := svc.GetByID(context.Background(), created.ID)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if persona.Name != "GetTest" {
|
|
t.Errorf("expected name 'GetTest', got '%s'", persona.Name)
|
|
}
|
|
})
|
|
|
|
t.Run("returns not found for missing persona", func(t *testing.T) {
|
|
_, err := svc.GetByID(context.Background(), "nonexistent-id")
|
|
if err != domain.ErrPersonaNotFound {
|
|
t.Errorf("expected ErrPersonaNotFound, got %v", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestPersonaService_List(t *testing.T) {
|
|
repo := memory.NewPersonaRepository()
|
|
q := &mockProducer{}
|
|
svc := NewPersonaService(repo, q, nil, logging.Nop())
|
|
|
|
t.Run("returns empty list initially", func(t *testing.T) {
|
|
personas, err := svc.List(context.Background(), 20, 0)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(personas) != 0 {
|
|
t.Errorf("expected 0 personas, got %d", len(personas))
|
|
}
|
|
})
|
|
|
|
// Create some personas
|
|
_, _ = svc.Create(context.Background(), "persona one", "woman", "ListOne")
|
|
_, _ = svc.Create(context.Background(), "persona two", "man", "ListTwo")
|
|
_, _ = svc.Create(context.Background(), "persona three", "non_binary", "ListThree")
|
|
|
|
t.Run("returns all personas", func(t *testing.T) {
|
|
personas, err := svc.List(context.Background(), 20, 0)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(personas) != 3 {
|
|
t.Errorf("expected 3 personas, got %d", len(personas))
|
|
}
|
|
})
|
|
|
|
t.Run("respects limit", func(t *testing.T) {
|
|
personas, err := svc.List(context.Background(), 2, 0)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(personas) != 2 {
|
|
t.Errorf("expected 2 personas, got %d", len(personas))
|
|
}
|
|
})
|
|
|
|
t.Run("clamps negative limit to default", func(t *testing.T) {
|
|
personas, err := svc.List(context.Background(), -1, 0)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(personas) != 3 {
|
|
t.Errorf("expected 3 personas (default limit), got %d", len(personas))
|
|
}
|
|
})
|
|
|
|
t.Run("clamps limit above 100", func(t *testing.T) {
|
|
personas, err := svc.List(context.Background(), 200, 0)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(personas) != 3 {
|
|
t.Errorf("expected 3 personas (all within max limit), got %d", len(personas))
|
|
}
|
|
})
|
|
|
|
t.Run("respects offset", func(t *testing.T) {
|
|
personas, err := svc.List(context.Background(), 20, 2)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(personas) != 1 {
|
|
t.Errorf("expected 1 persona, got %d", len(personas))
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestGenerateHandle(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
}{
|
|
{name: "simple name", input: "Luna Shadow"},
|
|
{name: "special chars", input: "DJ Beats!@#"},
|
|
{name: "unicode", input: "Café Noir"},
|
|
{name: "long name", input: "This Is A Very Long Name That Exceeds The Maximum Handle Length Allowed"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
handle := generateHandle(tt.input)
|
|
if handle == "" {
|
|
t.Error("expected non-empty handle")
|
|
}
|
|
if len(handle) > 50 {
|
|
t.Errorf("handle too long: %d chars", len(handle))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGeneratePersonaName(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
desc string
|
|
check func(string) bool
|
|
}{
|
|
{
|
|
name: "short description",
|
|
desc: "mysterious woman",
|
|
check: func(s string) bool { return s != "" },
|
|
},
|
|
{
|
|
name: "long description uses first 3 words",
|
|
desc: "mysterious woman with dark hair who loves poetry",
|
|
check: func(s string) bool { return s != "" && len(s) <= 50 },
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
name := generatePersonaName(tt.desc)
|
|
if !tt.check(name) {
|
|
t.Errorf("unexpected name for desc %q: %q", tt.desc, name)
|
|
}
|
|
})
|
|
}
|
|
}
|