package handlers import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "bookhoard/internal/database" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgtype" "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestPreviewCollection_NoAuth(t *testing.T) { e := echo.New() handler := &CollectionHandler{} reqBody := map[string]interface{}{ "library_id": uuid.New().String(), "rules": []map[string]interface{}{}, "manual_book_ids": []string{}, "limit": 20, } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/api/collections/preview", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() c := e.NewContext(req, rec) err := handler.PreviewCollection(c) assert.NoError(t, err) assert.Equal(t, http.StatusUnauthorized, rec.Code) } func TestPreviewCollection_MissingLibraryID(t *testing.T) { e := echo.New() handler := &CollectionHandler{} reqBody := map[string]interface{}{ "library_id": "", "rules": []map[string]interface{}{}, "manual_book_ids": []string{}, "limit": 20, } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/api/collections/preview", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() // Mock user (would normally come from middleware) user := database.Users{ ID: pgtype.UUID{Bytes: uuid.New(), Valid: true}, } c := e.NewContext(req, rec) c.Set("user", user) err := handler.PreviewCollection(c) assert.NoError(t, err) assert.Equal(t, http.StatusBadRequest, rec.Code) } func TestPreviewCollection_InvalidLibraryID(t *testing.T) { e := echo.New() handler := &CollectionHandler{} reqBody := map[string]interface{}{ "library_id": "invalid-uuid", "rules": []map[string]interface{}{}, "manual_book_ids": []string{}, "limit": 20, } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/api/collections/preview", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() // Mock user user := database.Users{ ID: pgtype.UUID{Bytes: uuid.New(), Valid: true}, } c := e.NewContext(req, rec) c.Set("user", user) err := handler.PreviewCollection(c) assert.NoError(t, err) assert.Equal(t, http.StatusBadRequest, rec.Code) } func TestPreviewCollection_LimitValidation(t *testing.T) { e := echo.New() handler := &CollectionHandler{} libraryID := uuid.New() tests := []struct { name string limit int expectedStatus int }{ { name: "valid limit 10", limit: 10, expectedStatus: http.StatusBadRequest, // No actual library, so will return error }, { name: "valid limit 20", limit: 20, expectedStatus: http.StatusBadRequest, // No actual library, so will return error }, { name: "limit too high (101)", limit: 101, expectedStatus: http.StatusBadRequest, // Limit validation }, { name: "limit zero", limit: 0, expectedStatus: http.StatusBadRequest, // Should default to 20, but library doesn't exist }, { name: "negative limit", limit: -5, expectedStatus: http.StatusBadRequest, // No actual library }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reqBody := map[string]interface{}{ "library_id": libraryID.String(), "rules": []map[string]interface{}{}, "manual_book_ids": []string{}, "limit": tt.limit, } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/api/collections/preview", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() // Mock user user := database.Users{ ID: pgtype.UUID{Bytes: uuid.New(), Valid: true}, } c := e.NewContext(req, rec) c.Set("user", user) err := handler.PreviewCollection(c) require.NoError(t, err) assert.Equal(t, tt.expectedStatus, rec.Code) }) } } func TestPreviewCollection_RuleValidation(t *testing.T) { e := echo.New() handler := &CollectionHandler{} libraryID := uuid.New() tests := []struct { name string rules interface{} expectedStatus int }{ { name: "empty rules array", rules: []map[string]interface{}{}, expectedStatus: http.StatusBadRequest, // Library doesn't exist }, { name: "valid rule structure", rules: []map[string]interface{}{ { "id": "rule1", "field": "genre", "operator": "equals", "value": "Sci-Fi", "priority": 1, }, }, expectedStatus: http.StatusBadRequest, // Library doesn't exist }, { name: "multiple rules", rules: []map[string]interface{}{ { "id": "rule1", "field": "genre", "operator": "equals", "value": "Sci-Fi", "priority": 1, }, { "id": "rule2", "field": "author", "operator": "contains", "value": "Asimov", "priority": 2, }, }, expectedStatus: http.StatusBadRequest, // Library doesn't exist }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reqBody := map[string]interface{}{ "library_id": libraryID.String(), "rules": tt.rules, "manual_book_ids": []string{}, "limit": 20, } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/api/collections/preview", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() // Mock user user := database.Users{ ID: pgtype.UUID{Bytes: uuid.New(), Valid: true}, } c := e.NewContext(req, rec) c.Set("user", user) err := handler.PreviewCollection(c) require.NoError(t, err) assert.Equal(t, tt.expectedStatus, rec.Code) }) } } func TestPreviewCollection_ManualBookSelection(t *testing.T) { e := echo.New() handler := &CollectionHandler{} libraryID := uuid.New() reqBody := map[string]interface{}{ "library_id": libraryID.String(), "rules": []map[string]interface{}{}, "manual_book_ids": []string{ uuid.New().String(), uuid.New().String(), uuid.New().String(), }, "limit": 20, } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/api/collections/preview", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() // Mock user user := database.Users{ ID: pgtype.UUID{Bytes: uuid.New(), Valid: true}, } c := e.NewContext(req, rec) c.Set("user", user) err := handler.PreviewCollection(c) require.NoError(t, err) // Should return 400 because library doesn't exist assert.Equal(t, http.StatusBadRequest, rec.Code) }