297 lines
8.8 KiB
Go
297 lines
8.8 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)
|
|
|
|
// Face search without face service configured should return 500
|
|
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.StatusInternalServerError {
|
|
t.Fatalf("expected 500 when face service not configured, 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["error"] != "face search service not configured" {
|
|
t.Errorf("expected error about face search service not configured, got %q", response["error"])
|
|
}
|
|
}
|