403 lines
9.6 KiB
Go
403 lines
9.6 KiB
Go
package handlers
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"git.threesix.ai/jordan/slate-test-1770507751/pkg/logging"
|
|
"git.threesix.ai/jordan/slate-test-1770507751/services/preferences-api/internal/domain"
|
|
"git.threesix.ai/jordan/slate-test-1770507751/services/preferences-api/internal/port"
|
|
"git.threesix.ai/jordan/slate-test-1770507751/services/preferences-api/internal/service"
|
|
)
|
|
|
|
// 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
|
|
}
|
|
copy := *e
|
|
return ©, nil
|
|
}
|
|
|
|
func (m *mockExampleRepository) Create(ctx context.Context, example *domain.Example) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
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
|
|
}
|
|
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 newTestHandler() (*Example, *mockExampleRepository) {
|
|
repo := newMockExampleRepository()
|
|
svc := service.NewExampleService(repo, logging.Nop())
|
|
handler := NewExample(svc, logging.Nop())
|
|
return handler, repo
|
|
}
|
|
|
|
func TestExample_List(t *testing.T) {
|
|
handler, repo := newTestHandler()
|
|
|
|
// Seed data
|
|
ex, _ := domain.NewExample("test-id-1", "Test Example", "Description")
|
|
_ = repo.Create(context.Background(), ex)
|
|
|
|
r := chi.NewRouter()
|
|
r.Get("/api/v1/examples", func(w http.ResponseWriter, r *http.Request) {
|
|
if err := handler.List(w, r); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/examples", 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"]
|
|
if !ok {
|
|
t.Fatal("expected 'data' field in response")
|
|
}
|
|
|
|
items, ok := data.([]any)
|
|
if !ok {
|
|
t.Fatal("expected 'data' to be an array")
|
|
}
|
|
|
|
if len(items) != 1 {
|
|
t.Errorf("expected 1 item, got %d", len(items))
|
|
}
|
|
}
|
|
|
|
func TestExample_Get(t *testing.T) {
|
|
handler, repo := newTestHandler()
|
|
|
|
// Seed data
|
|
ex, _ := domain.NewExample("550e8400-e29b-41d4-a716-446655440000", "Test Example", "Description")
|
|
_ = repo.Create(context.Background(), ex)
|
|
|
|
tests := []struct {
|
|
name string
|
|
id string
|
|
wantStatus int
|
|
}{
|
|
{
|
|
name: "valid uuid - found",
|
|
id: "550e8400-e29b-41d4-a716-446655440000",
|
|
wantStatus: http.StatusOK,
|
|
},
|
|
{
|
|
name: "valid uuid - not found",
|
|
id: "550e8400-e29b-41d4-a716-446655440001",
|
|
wantStatus: http.StatusNotFound,
|
|
},
|
|
{
|
|
name: "invalid uuid",
|
|
id: "not-a-uuid",
|
|
wantStatus: http.StatusBadRequest,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Get("/api/v1/examples/{id}", func(w http.ResponseWriter, r *http.Request) {
|
|
if err := handler.Get(w, r); err != nil {
|
|
// Map error to status for testing
|
|
switch tt.wantStatus {
|
|
case http.StatusNotFound:
|
|
w.WriteHeader(http.StatusNotFound)
|
|
case http.StatusBadRequest:
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
default:
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}
|
|
return
|
|
}
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/examples/"+tt.id, nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
if w.Code != tt.wantStatus {
|
|
t.Errorf("expected status %d, got %d", tt.wantStatus, w.Code)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExample_Create(t *testing.T) {
|
|
handler, repo := newTestHandler()
|
|
|
|
// Seed existing data for duplicate test
|
|
ex, _ := domain.NewExample("existing-id", "Existing Name", "")
|
|
_ = repo.Create(context.Background(), ex)
|
|
|
|
tests := []struct {
|
|
name string
|
|
body any
|
|
wantStatus int
|
|
}{
|
|
{
|
|
name: "valid request",
|
|
body: CreateRequest{
|
|
Name: "New Example",
|
|
Description: "A test description",
|
|
},
|
|
wantStatus: http.StatusCreated,
|
|
},
|
|
{
|
|
name: "empty body",
|
|
body: nil,
|
|
wantStatus: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "duplicate name",
|
|
body: CreateRequest{
|
|
Name: "Existing Name",
|
|
Description: "Conflict",
|
|
},
|
|
wantStatus: http.StatusConflict,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Post("/api/v1/examples", func(w http.ResponseWriter, r *http.Request) {
|
|
if err := handler.Create(w, r); err != nil {
|
|
switch tt.wantStatus {
|
|
case http.StatusBadRequest:
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
case http.StatusConflict:
|
|
w.WriteHeader(http.StatusConflict)
|
|
default:
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}
|
|
return
|
|
}
|
|
})
|
|
|
|
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, "/api/v1/examples", 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", tt.wantStatus, w.Code)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExample_Delete(t *testing.T) {
|
|
handler, repo := newTestHandler()
|
|
|
|
// Seed data
|
|
ex, _ := domain.NewExample("550e8400-e29b-41d4-a716-446655440000", "To Delete", "")
|
|
_ = repo.Create(context.Background(), ex)
|
|
|
|
tests := []struct {
|
|
name string
|
|
id string
|
|
wantStatus int
|
|
}{
|
|
{
|
|
name: "existing example",
|
|
id: "550e8400-e29b-41d4-a716-446655440000",
|
|
wantStatus: http.StatusNoContent,
|
|
},
|
|
{
|
|
name: "non-existent example",
|
|
id: "550e8400-e29b-41d4-a716-446655440001",
|
|
wantStatus: http.StatusNotFound,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Delete("/api/v1/examples/{id}", func(w http.ResponseWriter, r *http.Request) {
|
|
if err := handler.Delete(w, r); err != nil {
|
|
if tt.wantStatus == http.StatusNotFound {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
} else {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
}
|
|
return
|
|
}
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/v1/examples/"+tt.id, nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
if w.Code != tt.wantStatus {
|
|
t.Errorf("expected status %d, got %d", tt.wantStatus, w.Code)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExample_Update(t *testing.T) {
|
|
handler, repo := newTestHandler()
|
|
|
|
// Seed data
|
|
ex1, _ := domain.NewExample("550e8400-e29b-41d4-a716-446655440000", "Example 1", "")
|
|
_ = repo.Create(context.Background(), ex1)
|
|
ex2, _ := domain.NewExample("550e8400-e29b-41d4-a716-446655440001", "Example 2", "")
|
|
_ = repo.Create(context.Background(), ex2)
|
|
|
|
tests := []struct {
|
|
name string
|
|
id string
|
|
body UpdateRequest
|
|
wantStatus int
|
|
}{
|
|
{
|
|
name: "valid update",
|
|
id: "550e8400-e29b-41d4-a716-446655440000",
|
|
body: UpdateRequest{
|
|
Name: "Updated Name",
|
|
Description: "Updated",
|
|
},
|
|
wantStatus: http.StatusOK,
|
|
},
|
|
{
|
|
name: "name conflict",
|
|
id: "550e8400-e29b-41d4-a716-446655440000",
|
|
body: UpdateRequest{
|
|
Name: "Example 2",
|
|
Description: "Conflict",
|
|
},
|
|
wantStatus: http.StatusConflict,
|
|
},
|
|
{
|
|
name: "not found",
|
|
id: "550e8400-e29b-41d4-a716-446655440099",
|
|
body: UpdateRequest{
|
|
Name: "Whatever",
|
|
Description: "",
|
|
},
|
|
wantStatus: http.StatusNotFound,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Put("/api/v1/examples/{id}", func(w http.ResponseWriter, r *http.Request) {
|
|
if err := handler.Update(w, r); err != nil {
|
|
switch tt.wantStatus {
|
|
case http.StatusNotFound:
|
|
w.WriteHeader(http.StatusNotFound)
|
|
case http.StatusConflict:
|
|
w.WriteHeader(http.StatusConflict)
|
|
default:
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
}
|
|
return
|
|
}
|
|
})
|
|
|
|
body, _ := json.Marshal(tt.body)
|
|
req := httptest.NewRequest(http.MethodPut, "/api/v1/examples/"+tt.id, 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", tt.wantStatus, w.Code)
|
|
}
|
|
})
|
|
}
|
|
}
|