hatch-surf/internal/handler/spaces_test.go

295 lines
8.6 KiB
Go

package handler
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/elfoundation/hatch/internal/store"
"github.com/elfoundation/hatch/internal/testutil"
"github.com/go-chi/chi/v5"
)
// testSpaceRouter creates a chi router with all routes registered using a fake repo.
func testSpaceRouter(repo store.Repository) chi.Router {
r := chi.NewRouter()
h := New(repo)
h.RegisterRoutes(r)
return r
}
func TestCreateSpace(t *testing.T) {
repo := testutil.NewFakeRepository()
r := testSpaceRouter(repo)
body := `{"name": "Test Space", "description": "A test space", "isPublic": true}`
req := httptest.NewRequest(http.MethodPost, "/api/spaces", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusCreated {
t.Fatalf("expected 201, got %d: %s", w.Code, w.Body.String())
}
var space store.Space
if err := json.NewDecoder(w.Body).Decode(&space); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
if space.Name != "Test Space" {
t.Errorf("expected name 'Test Space', got %q", space.Name)
}
if space.Description != "A test space" {
t.Errorf("expected description 'A test space', got %q", space.Description)
}
if !space.IsPublic {
t.Error("expected isPublic to be true")
}
if space.Slug == "" {
t.Error("expected slug to be generated")
}
}
func TestCreateSpaceMissingName(t *testing.T) {
repo := testutil.NewFakeRepository()
r := testSpaceRouter(repo)
body := `{"description": "Missing name"}`
req := httptest.NewRequest(http.MethodPost, "/api/spaces", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusBadRequest {
t.Fatalf("expected 400, got %d: %s", w.Code, w.Body.String())
}
}
func TestListPublicSpaces(t *testing.T) {
repo := testutil.NewFakeRepository()
r := testSpaceRouter(repo)
// Create some spaces
space1 := &store.Space{ID: "1", Slug: "space-1", Name: "Space 1", IsPublic: true}
space2 := &store.Space{ID: "2", Slug: "space-2", Name: "Space 2", IsPublic: false}
space3 := &store.Space{ID: "3", Slug: "space-3", Name: "Space 3", IsPublic: true}
repo.CreateSpace(nil, space1)
repo.CreateSpace(nil, space2)
repo.CreateSpace(nil, space3)
req := httptest.NewRequest(http.MethodGet, "/api/spaces", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
}
var spaces []*store.Space
if err := json.NewDecoder(w.Body).Decode(&spaces); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
if len(spaces) != 2 {
t.Fatalf("expected 2 spaces, got %d", len(spaces))
}
// Check that only public spaces are returned
for _, space := range spaces {
if !space.IsPublic {
t.Errorf("expected only public spaces, got %q", space.Name)
}
}
}
func TestGetSpaceBySlug(t *testing.T) {
repo := testutil.NewFakeRepository()
r := testSpaceRouter(repo)
space := &store.Space{ID: "1", Slug: "test-space", Name: "Test Space", IsPublic: true}
repo.CreateSpace(nil, space)
req := httptest.NewRequest(http.MethodGet, "/api/spaces/test-space", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
}
var retrievedSpace store.Space
if err := json.NewDecoder(w.Body).Decode(&retrievedSpace); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
if retrievedSpace.ID != space.ID {
t.Errorf("expected ID %q, got %q", space.ID, retrievedSpace.ID)
}
if retrievedSpace.Name != space.Name {
t.Errorf("expected name %q, got %q", space.Name, retrievedSpace.Name)
}
}
func TestGetSpaceBySlugNotFound(t *testing.T) {
repo := testutil.NewFakeRepository()
r := testSpaceRouter(repo)
req := httptest.NewRequest(http.MethodGet, "/api/spaces/nonexistent", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusNotFound {
t.Fatalf("expected 404, got %d: %s", w.Code, w.Body.String())
}
}
func TestUpdateSpace(t *testing.T) {
repo := testutil.NewFakeRepository()
r := testSpaceRouter(repo)
space := &store.Space{ID: "1", Slug: "test-space", Name: "Original Name", IsPublic: true}
repo.CreateSpace(nil, space)
body := `{"name": "Updated Name", "isPublic": false}`
req := httptest.NewRequest(http.MethodPatch, "/api/spaces/test-space", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
}
var updatedSpace store.Space
if err := json.NewDecoder(w.Body).Decode(&updatedSpace); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
if updatedSpace.Name != "Updated Name" {
t.Errorf("expected name 'Updated Name', got %q", updatedSpace.Name)
}
if updatedSpace.IsPublic != false {
t.Error("expected isPublic to be false")
}
}
func TestDeleteSpace(t *testing.T) {
repo := testutil.NewFakeRepository()
r := testSpaceRouter(repo)
space := &store.Space{ID: "1", Slug: "test-space", Name: "Test Space", IsPublic: true}
repo.CreateSpace(nil, space)
req := httptest.NewRequest(http.MethodDelete, "/api/spaces/test-space", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusNoContent {
t.Fatalf("expected 204, got %d: %s", w.Code, w.Body.String())
}
// Verify space was deleted
_, err := repo.GetSpaceBySlug(nil, "test-space")
if err == nil {
t.Error("expected space to be deleted")
}
}
func TestCreatePhoto(t *testing.T) {
repo := testutil.NewFakeRepository()
r := testSpaceRouter(repo)
space := &store.Space{ID: "1", Slug: "test-space", Name: "Test Space", IsPublic: true}
repo.CreateSpace(nil, space)
body := `{"url": "https://example.com/photo.jpg", "thumbnailUrl": "https://example.com/thumb.jpg", "faceCount": 2}`
req := httptest.NewRequest(http.MethodPost, "/api/spaces/test-space/photos", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusCreated {
t.Fatalf("expected 201, got %d: %s", w.Code, w.Body.String())
}
var photo store.Photo
if err := json.NewDecoder(w.Body).Decode(&photo); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
if photo.URL != "https://example.com/photo.jpg" {
t.Errorf("expected URL 'https://example.com/photo.jpg', got %q", photo.URL)
}
if photo.ThumbnailURL != "https://example.com/thumb.jpg" {
t.Errorf("expected thumbnail URL 'https://example.com/thumb.jpg', got %q", photo.ThumbnailURL)
}
if photo.FaceCount != 2 {
t.Errorf("expected face count 2, got %d", photo.FaceCount)
}
}
func TestListPhotos(t *testing.T) {
repo := testutil.NewFakeRepository()
r := testSpaceRouter(repo)
space := &store.Space{ID: "1", Slug: "test-space", Name: "Test Space", IsPublic: true}
repo.CreateSpace(nil, space)
// Create some photos
photo1 := &store.Photo{ID: "1", SpaceID: "1", URL: "https://example.com/photo1.jpg"}
photo2 := &store.Photo{ID: "2", SpaceID: "1", URL: "https://example.com/photo2.jpg"}
photo3 := &store.Photo{ID: "3", SpaceID: "2", URL: "https://example.com/photo3.jpg"}
repo.CreatePhoto(nil, photo1)
repo.CreatePhoto(nil, photo2)
repo.CreatePhoto(nil, photo3)
req := httptest.NewRequest(http.MethodGet, "/api/spaces/test-space/photos", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
}
var photos []*store.Photo
if err := json.NewDecoder(w.Body).Decode(&photos); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
if len(photos) != 2 {
t.Fatalf("expected 2 photos, got %d", len(photos))
}
}
func TestGuestFaceSearch(t *testing.T) {
repo := testutil.NewFakeRepository()
r := testSpaceRouter(repo)
space := &store.Space{ID: "1", Slug: "test-space", Name: "Test Space", IsPublic: true}
repo.CreateSpace(nil, space)
body := `{"imageUrl": "https://example.com/test.jpg"}`
req := httptest.NewRequest(http.MethodPost, "/api/spaces/test-space/face-search/guest", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.RemoteAddr = "192.168.1.1:12345"
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
}
var response map[string]interface{}
if err := json.NewDecoder(w.Body).Decode(&response); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
if response["status"] != "success" {
t.Errorf("expected status 'success', got %q", response["status"])
}
}