package service import ( "context" "sync" "testing" "git.threesix.ai/jordan/foundary-1770670477/pkg/logging" "git.threesix.ai/jordan/foundary-1770670477/services/studio-api/internal/domain" "git.threesix.ai/jordan/foundary-1770670477/services/studio-api/internal/port" ) // mockExampleRepository implements port.ExampleRepository for testing. type mockExampleRepository struct { mu sync.RWMutex examples map[domain.ExampleID]*domain.Example } var _ port.ExampleRepository = (*mockExampleRepository)(nil) func newMockExampleRepository() *mockExampleRepository { return &mockExampleRepository{ examples: make(map[domain.ExampleID]*domain.Example), } } func (m *mockExampleRepository) List(ctx context.Context) ([]domain.Example, error) { m.mu.RLock() defer m.mu.RUnlock() result := make([]domain.Example, 0, len(m.examples)) for _, e := range m.examples { result = append(result, *e) } return result, nil } func (m *mockExampleRepository) Get(ctx context.Context, id domain.ExampleID) (*domain.Example, error) { m.mu.RLock() defer m.mu.RUnlock() e, ok := m.examples[id] if !ok { return nil, domain.ErrExampleNotFound } // Return a copy to avoid mutation copy := *e return ©, nil } func (m *mockExampleRepository) Create(ctx context.Context, example *domain.Example) error { m.mu.Lock() defer m.mu.Unlock() // Store a copy copy := *example m.examples[example.ID] = © return nil } func (m *mockExampleRepository) Update(ctx context.Context, example *domain.Example) error { m.mu.Lock() defer m.mu.Unlock() if _, ok := m.examples[example.ID]; !ok { return domain.ErrExampleNotFound } // Store a copy copy := *example m.examples[example.ID] = © return nil } func (m *mockExampleRepository) Delete(ctx context.Context, id domain.ExampleID) error { m.mu.Lock() defer m.mu.Unlock() if _, ok := m.examples[id]; !ok { return domain.ErrExampleNotFound } delete(m.examples, id) return nil } func (m *mockExampleRepository) ExistsByName(ctx context.Context, name string) (bool, error) { m.mu.RLock() defer m.mu.RUnlock() for _, e := range m.examples { if e.Name == name { return true, nil } } return false, nil } func TestExampleService_Create(t *testing.T) { repo := newMockExampleRepository() svc := NewExampleService(repo, logging.Nop()) t.Run("creates example successfully", func(t *testing.T) { example, err := svc.Create(context.Background(), CreateInput{ Name: "Test Example", Description: "A test description", }) if err != nil { t.Fatalf("unexpected error: %v", err) } if example.Name != "Test Example" { t.Errorf("expected name 'Test Example', got '%s'", example.Name) } if example.ID.IsZero() { t.Error("expected non-empty ID") } }) t.Run("rejects duplicate name", func(t *testing.T) { _, err := svc.Create(context.Background(), CreateInput{ Name: "Test Example", Description: "Another description", }) if err != domain.ErrDuplicateExample { t.Errorf("expected ErrDuplicateExample, got %v", err) } }) t.Run("rejects empty name", func(t *testing.T) { _, err := svc.Create(context.Background(), CreateInput{ Name: "", Description: "Description", }) if err != domain.ErrInvalidExampleName { t.Errorf("expected ErrInvalidExampleName, got %v", err) } }) } func TestExampleService_Get(t *testing.T) { repo := newMockExampleRepository() svc := NewExampleService(repo, logging.Nop()) // Create an example first created, _ := svc.Create(context.Background(), CreateInput{ Name: "Get Test", Description: "Description", }) t.Run("returns existing example", func(t *testing.T) { example, err := svc.Get(context.Background(), created.ID) if err != nil { t.Fatalf("unexpected error: %v", err) } if example.Name != "Get Test" { t.Errorf("expected name 'Get Test', got '%s'", example.Name) } }) t.Run("returns not found for missing example", func(t *testing.T) { _, err := svc.Get(context.Background(), "nonexistent-id") if err != domain.ErrExampleNotFound { t.Errorf("expected ErrExampleNotFound, got %v", err) } }) } func TestExampleService_Update(t *testing.T) { repo := newMockExampleRepository() svc := NewExampleService(repo, logging.Nop()) // Create examples example1, _ := svc.Create(context.Background(), CreateInput{ Name: "Update Test 1", Description: "Original", }) _, _ = svc.Create(context.Background(), CreateInput{ Name: "Update Test 2", Description: "Other", }) t.Run("updates example successfully", func(t *testing.T) { updated, err := svc.Update(context.Background(), example1.ID, UpdateInput{ Name: "Updated Name", Description: "Updated description", }) if err != nil { t.Fatalf("unexpected error: %v", err) } if updated.Name != "Updated Name" { t.Errorf("expected name 'Updated Name', got '%s'", updated.Name) } }) t.Run("allows same name on same example", func(t *testing.T) { _, err := svc.Update(context.Background(), example1.ID, UpdateInput{ Name: "Updated Name", Description: "Same name", }) if err != nil { t.Errorf("unexpected error updating with same name: %v", err) } }) t.Run("rejects name conflict", func(t *testing.T) { _, err := svc.Update(context.Background(), example1.ID, UpdateInput{ Name: "Update Test 2", Description: "Conflict", }) if err != domain.ErrDuplicateExample { t.Errorf("expected ErrDuplicateExample, got %v", err) } }) t.Run("returns not found for missing example", func(t *testing.T) { _, err := svc.Update(context.Background(), "nonexistent-id", UpdateInput{ Name: "Anything", Description: "", }) if err != domain.ErrExampleNotFound { t.Errorf("expected ErrExampleNotFound, got %v", err) } }) } func TestExampleService_Delete(t *testing.T) { repo := newMockExampleRepository() svc := NewExampleService(repo, logging.Nop()) // Create an example first created, _ := svc.Create(context.Background(), CreateInput{ Name: "Delete Test", Description: "To be deleted", }) t.Run("deletes example successfully", func(t *testing.T) { err := svc.Delete(context.Background(), created.ID) if err != nil { t.Fatalf("unexpected error: %v", err) } // Verify deleted _, err = svc.Get(context.Background(), created.ID) if err != domain.ErrExampleNotFound { t.Errorf("expected ErrExampleNotFound after delete, got %v", err) } }) t.Run("returns not found for missing example", func(t *testing.T) { err := svc.Delete(context.Background(), "nonexistent-id") if err != domain.ErrExampleNotFound { t.Errorf("expected ErrExampleNotFound, got %v", err) } }) } func TestExampleService_List(t *testing.T) { repo := newMockExampleRepository() svc := NewExampleService(repo, logging.Nop()) t.Run("returns empty list initially", func(t *testing.T) { examples, err := svc.List(context.Background()) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(examples) != 0 { t.Errorf("expected 0 examples, got %d", len(examples)) } }) // Create some examples _, _ = svc.Create(context.Background(), CreateInput{Name: "List Test 1", Description: ""}) _, _ = svc.Create(context.Background(), CreateInput{Name: "List Test 2", Description: ""}) t.Run("returns all examples", func(t *testing.T) { examples, err := svc.List(context.Background()) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(examples) != 2 { t.Errorf("expected 2 examples, got %d", len(examples)) } }) }