From f54508e4ddf53c784587c1a07bb43e4d0a98f3d0 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sun, 22 Feb 2026 01:57:22 -0500 Subject: [PATCH] test: improve test isolation and setup management Add Token and RegularToken fields to TestServerSetup for pre-authenticated access. Update setupTestServer to create fresh users with valid tokens at initialization time. Simplify createTestMediaItemID to use setup.Token. Remove loginTestUser, loginRegularUser, loginAdminUser functions in favor of setup.Token/setup.RegularToken. Update createTestUserOnce and getTestUserID/getRegularUserID to be idempotent. Update all test files to use setup.Token instead of calling login helpers. --- cmd/server/tests/analytics_test.go | 42 +- cmd/server/tests/book_matching_test.go | 50 +- cmd/server/tests/collections_bulk_test.go | 49 +- cmd/server/tests/collections_preview_test.go | 4 +- cmd/server/tests/conflicts_bulk_test.go | 31 +- .../tests/dashboard_integration_test.go | 12 +- cmd/server/tests/device_cap_test.go | 16 +- cmd/server/tests/device_test.go | 12 +- cmd/server/tests/kobo_test.go | 10 +- cmd/server/tests/media_bulk_test.go | 39 +- cmd/server/tests/media_item_isbn_test.go | 10 +- cmd/server/tests/queue_test.go | 74 +- cmd/server/tests/system_settings_test.go | 14 +- cmd/server/tests/test_helpers.go | 296 +++--- cmd/server/tests/user_test.go | 945 ++++++++++-------- cmd/server/tests/websocket_test.go | 8 +- 16 files changed, 819 insertions(+), 793 deletions(-) diff --git a/cmd/server/tests/analytics_test.go b/cmd/server/tests/analytics_test.go index 974e565..fed457e 100644 --- a/cmd/server/tests/analytics_test.go +++ b/cmd/server/tests/analytics_test.go @@ -15,7 +15,6 @@ import ( // TestAnalyticsReadingStats tests the reading statistics endpoint func TestAnalyticsReadingStats(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) client := &http.Client{} t.Run("GetReadingStats_WithoutAuth", func(t *testing.T) { @@ -29,7 +28,7 @@ func TestAnalyticsReadingStats(t *testing.T) { t.Run("GetReadingStats_WithAuth_DefaultDates", func(t *testing.T) { req, _ := http.NewRequest("GET", setup.Server.URL+"/api/analytics/reading-stats", nil) - req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) @@ -50,7 +49,7 @@ func TestAnalyticsReadingStats(t *testing.T) { endDate := time.Now().Format("2006-01-02") req, _ := http.NewRequest("GET", setup.Server.URL+"/api/analytics/reading-stats?start_date="+startDate+"&end_date="+endDate, nil) - req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) @@ -61,7 +60,7 @@ func TestAnalyticsReadingStats(t *testing.T) { t.Run("GetReadingStats_InvalidStartDate", func(t *testing.T) { req, _ := http.NewRequest("GET", setup.Server.URL+"/api/analytics/reading-stats?start_date=invalid-date", nil) - req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) @@ -72,7 +71,7 @@ func TestAnalyticsReadingStats(t *testing.T) { t.Run("GetReadingStats_InvalidEndDate", func(t *testing.T) { req, _ := http.NewRequest("GET", setup.Server.URL+"/api/analytics/reading-stats?end_date=not-a-date", nil) - req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) @@ -83,7 +82,7 @@ func TestAnalyticsReadingStats(t *testing.T) { t.Run("GetReadingStats_EmptyHistory", func(t *testing.T) { req, _ := http.NewRequest("GET", setup.Server.URL+"/api/analytics/reading-stats", nil) - req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) @@ -103,7 +102,6 @@ func TestAnalyticsReadingStats(t *testing.T) { // TestAnalyticsDeviceUsage tests the device usage endpoint func TestAnalyticsDeviceUsage(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) client := &http.Client{} t.Run("GetDeviceUsage_WithoutAuth", func(t *testing.T) { @@ -117,7 +115,7 @@ func TestAnalyticsDeviceUsage(t *testing.T) { t.Run("GetDeviceUsage_WithAuth_NoDevices", func(t *testing.T) { req, _ := http.NewRequest("GET", setup.Server.URL+"/api/analytics/device-usage", nil) - req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) @@ -142,7 +140,7 @@ func TestAnalyticsDeviceUsage(t *testing.T) { deviceReqHTTP, _ := http.NewRequest("POST", setup.Server.URL+"/api/devices/register", bytes.NewBuffer(deviceBody)) deviceReqHTTP.Header.Set("Content-Type", "application/json") - deviceReqHTTP.Header.Set("Authorization", "Bearer "+token) + deviceReqHTTP.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(deviceReqHTTP) require.NoError(t, err) @@ -150,7 +148,7 @@ func TestAnalyticsDeviceUsage(t *testing.T) { // Now get device usage req, _ := http.NewRequest("GET", setup.Server.URL+"/api/analytics/device-usage", nil) - req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err = client.Do(req) require.NoError(t, err) @@ -169,7 +167,7 @@ func TestAnalyticsDeviceUsage(t *testing.T) { t.Run("GetDeviceUsage_ResponseStructure", func(t *testing.T) { req, _ := http.NewRequest("GET", setup.Server.URL+"/api/analytics/device-usage", nil) - req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) @@ -199,7 +197,6 @@ func TestAnalyticsDeviceUsage(t *testing.T) { // TestAnalyticsPopularBooks tests the popular books endpoint func TestAnalyticsPopularBooks(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) client := &http.Client{} t.Run("GetPopularBooks_WithoutAuth", func(t *testing.T) { @@ -213,7 +210,7 @@ func TestAnalyticsPopularBooks(t *testing.T) { t.Run("GetPopularBooks_WithAuth_DefaultLimit", func(t *testing.T) { req, _ := http.NewRequest("GET", setup.Server.URL+"/api/analytics/popular-books", nil) - req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) @@ -231,7 +228,7 @@ func TestAnalyticsPopularBooks(t *testing.T) { t.Run("GetPopularBooks_WithCustomLimit", func(t *testing.T) { req, _ := http.NewRequest("GET", setup.Server.URL+"/api/analytics/popular-books?limit=5", nil) - req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) @@ -248,7 +245,7 @@ func TestAnalyticsPopularBooks(t *testing.T) { t.Run("GetPopularBooks_InvalidLimit", func(t *testing.T) { req, _ := http.NewRequest("GET", setup.Server.URL+"/api/analytics/popular-books?limit=invalid", nil) - req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) @@ -266,7 +263,7 @@ func TestAnalyticsPopularBooks(t *testing.T) { t.Run("GetPopularBooks_ResponseStructure", func(t *testing.T) { // First create a book and some reading history - bookID := createTestMediaItemID(t, setup.Server) + bookID := createTestMediaItemID(t, setup) // Create reading history for the book historyReq := map[string]interface{}{ @@ -279,7 +276,7 @@ func TestAnalyticsPopularBooks(t *testing.T) { historyHTTP, _ := http.NewRequest("POST", setup.Server.URL+"/api/media-items/"+bookID+"/progress", bytes.NewBuffer(historyBody)) historyHTTP.Header.Set("Content-Type", "application/json") - historyHTTP.Header.Set("Authorization", "Bearer "+token) + historyHTTP.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(historyHTTP) require.NoError(t, err) @@ -287,7 +284,7 @@ func TestAnalyticsPopularBooks(t *testing.T) { // Now get popular books req, _ := http.NewRequest("GET", setup.Server.URL+"/api/analytics/popular-books", nil) - req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err = client.Do(req) require.NoError(t, err) @@ -314,7 +311,7 @@ func TestAnalyticsPopularBooks(t *testing.T) { t.Run("GetPopularBooks_NoReadingHistory", func(t *testing.T) { req, _ := http.NewRequest("GET", setup.Server.URL+"/api/analytics/popular-books", nil) - req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) @@ -334,7 +331,6 @@ func TestAnalyticsPopularBooks(t *testing.T) { // TestAnalyticsEdgeCases tests edge cases for analytics endpoints func TestAnalyticsEdgeCases(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) client := &http.Client{} t.Run("ReadingStats_FutureDateRange", func(t *testing.T) { @@ -342,7 +338,7 @@ func TestAnalyticsEdgeCases(t *testing.T) { endDate := time.Now().AddDate(0, 0, 14).Format("2006-01-02") req, _ := http.NewRequest("GET", setup.Server.URL+"/api/analytics/reading-stats?start_date="+startDate+"&end_date="+endDate, nil) - req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) @@ -359,7 +355,7 @@ func TestAnalyticsEdgeCases(t *testing.T) { t.Run("PopularBooks_LimitZero", func(t *testing.T) { req, _ := http.NewRequest("GET", setup.Server.URL+"/api/analytics/popular-books?limit=0", nil) - req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) @@ -377,7 +373,7 @@ func TestAnalyticsEdgeCases(t *testing.T) { t.Run("PopularBooks_VeryLargeLimit", func(t *testing.T) { req, _ := http.NewRequest("GET", setup.Server.URL+"/api/analytics/popular-books?limit=999999", nil) - req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(req) require.NoError(t, err) diff --git a/cmd/server/tests/book_matching_test.go b/cmd/server/tests/book_matching_test.go index 08d1948..f90b04f 100644 --- a/cmd/server/tests/book_matching_test.go +++ b/cmd/server/tests/book_matching_test.go @@ -14,7 +14,6 @@ import ( // TestBookMatchingQueryBooks tests the book query endpoint func TestBookMatchingQueryBooks(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) t.Run("QueryBooks_WithoutAuth", func(t *testing.T) { req := map[string]interface{}{ @@ -34,7 +33,7 @@ func TestBookMatchingQueryBooks(t *testing.T) { }) t.Run("QueryBooks_WithAuth_ByTitle", func(t *testing.T) { - _ = createTestMediaItemID(t, setup.Server) + _ = createTestMediaItemID(t, setup) req := map[string]interface{}{ "title": "Test Ebook", @@ -43,7 +42,7 @@ func TestBookMatchingQueryBooks(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/sync/books/query", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -63,7 +62,7 @@ func TestBookMatchingQueryBooks(t *testing.T) { // Send invalid JSON httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/sync/books/query", bytes.NewBuffer([]byte("invalid json"))) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -81,7 +80,7 @@ func TestBookMatchingQueryBooks(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/sync/books/query", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -106,7 +105,6 @@ func TestBookMatchingQueryBooks(t *testing.T) { // TestBookMatchingBulkLink tests bulk linking operations func TestBookMatchingBulkLink(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) t.Run("BulkLinkBooks_WithoutAuth", func(t *testing.T) { req := map[string]interface{}{ @@ -139,7 +137,7 @@ func TestBookMatchingBulkLink(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/sync/bulk-link-books", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -157,7 +155,7 @@ func TestBookMatchingBulkLink(t *testing.T) { }) t.Run("BulkLinkBooks_InvalidUnlinkedBookID", func(t *testing.T) { - bookID := createTestMediaItemID(t, setup.Server) + bookID := createTestMediaItemID(t, setup) req := map[string]interface{}{ "links": []map[string]interface{}{ @@ -172,7 +170,7 @@ func TestBookMatchingBulkLink(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/sync/bulk-link-books", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -218,7 +216,7 @@ func TestBookMatchingBulkLink(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/sync/bulk-link-books", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -239,7 +237,6 @@ func TestBookMatchingBulkLink(t *testing.T) { // TestBookMatchingAutoLink tests automatic linking func TestBookMatchingAutoLink(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) t.Run("AutoLinkBooks_WithoutAuth", func(t *testing.T) { req := map[string]interface{}{ @@ -265,7 +262,7 @@ func TestBookMatchingAutoLink(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/sync/auto-link-books", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -290,7 +287,7 @@ func TestBookMatchingAutoLink(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/sync/auto-link-books", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -313,7 +310,7 @@ func TestBookMatchingAutoLink(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/sync/auto-link-books", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -333,7 +330,6 @@ func TestBookMatchingAutoLink(t *testing.T) { // TestBookMatchingSuggestions tests getting suggestions for unlinked books func TestBookMatchingSuggestions(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) t.Run("GetUnlinkedBookSuggestions_WithoutAuth", func(t *testing.T) { testID := uuid.New() @@ -349,7 +345,7 @@ func TestBookMatchingSuggestions(t *testing.T) { t.Run("GetUnlinkedBookSuggestions_InvalidUUID", func(t *testing.T) { httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/api/sync/unlinked-books/invalid-uuid/suggestions", nil) - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -362,7 +358,7 @@ func TestBookMatchingSuggestions(t *testing.T) { t.Run("GetUnlinkedBookSuggestions_BookNotFound", func(t *testing.T) { testID := uuid.New() httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/api/sync/unlinked-books/"+testID.String()+"/suggestions", nil) - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -377,7 +373,7 @@ func TestBookMatchingSuggestions(t *testing.T) { // For now, test with a non-existent ID to check response structure testID := uuid.New() httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/api/sync/unlinked-books/"+testID.String()+"/suggestions", nil) - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -392,7 +388,6 @@ func TestBookMatchingSuggestions(t *testing.T) { // TestBookMatchingDeviceFileAliases tests device file alias operations func TestBookMatchingDeviceFileAliases(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) t.Run("GetDeviceFileAliases_WithoutAuth", func(t *testing.T) { testID := uuid.New() @@ -409,7 +404,7 @@ func TestBookMatchingDeviceFileAliases(t *testing.T) { t.Run("GetDeviceFileAliases_WithAuth", func(t *testing.T) { testID := uuid.New() httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/api/devices/"+testID.String()+"/file-aliases", nil) - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -462,7 +457,7 @@ func TestBookMatchingDeviceFileAliases(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/devices/invalid-uuid/file-aliases", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -485,7 +480,7 @@ func TestBookMatchingDeviceFileAliases(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/devices/"+deviceID.String()+"/file-aliases", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -505,7 +500,7 @@ func TestBookMatchingDeviceFileAliases(t *testing.T) { httpReq, _ := http.NewRequest("PUT", setup.Server.URL+"/api/devices/"+deviceID.String()+"/file-aliases/invalid-uuid", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -519,7 +514,7 @@ func TestBookMatchingDeviceFileAliases(t *testing.T) { deviceID := uuid.New() httpReq, _ := http.NewRequest("DELETE", setup.Server.URL+"/api/devices/"+deviceID.String()+"/file-aliases/invalid-uuid", nil) - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -533,7 +528,6 @@ func TestBookMatchingDeviceFileAliases(t *testing.T) { // TestBookMatchingGetBookMatches tests the book matches endpoint func TestBookMatchingGetBookMatches(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) t.Run("GetBookMatches_WithoutAuth", func(t *testing.T) { httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/api/books/match?title=Test", nil) @@ -548,7 +542,7 @@ func TestBookMatchingGetBookMatches(t *testing.T) { t.Run("GetBookMatches_WithAuth_ByTitle", func(t *testing.T) { httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/api/books/match?title=Test", nil) - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -566,7 +560,7 @@ func TestBookMatchingGetBookMatches(t *testing.T) { t.Run("GetBookMatches_InvalidFileSize", func(t *testing.T) { httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/api/books/match?title=Test&file_size=invalid", nil) - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -578,7 +572,7 @@ func TestBookMatchingGetBookMatches(t *testing.T) { t.Run("GetBookMatches_MultipleIdentifiers", func(t *testing.T) { httpReq, _ := http.NewRequest("GET", setup.Server.URL+"/api/books/match?identifier=id1&identifier=id2&title=Test", nil) - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) diff --git a/cmd/server/tests/collections_bulk_test.go b/cmd/server/tests/collections_bulk_test.go index a9519b4..d30d529 100644 --- a/cmd/server/tests/collections_bulk_test.go +++ b/cmd/server/tests/collections_bulk_test.go @@ -14,7 +14,6 @@ import ( // TestCollectionsBulkOperations tests bulk collection operations func TestCollectionsBulkOperations(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) client := &http.Client{} // Define request struct matching handler expectation @@ -28,7 +27,7 @@ func TestCollectionsBulkOperations(t *testing.T) { } t.Run("BulkAddBooks_WithoutAuth", func(t *testing.T) { - bookID := createTestMediaItemID(t, setup.Server) + bookID := createTestMediaItemID(t, setup) req := BulkAddBooksRequest{ Operations: []BulkAddOperation{ @@ -58,7 +57,7 @@ func TestCollectionsBulkOperations(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/collections/bulk-add-books", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(httpReq) require.NoError(t, err) @@ -68,7 +67,7 @@ func TestCollectionsBulkOperations(t *testing.T) { }) t.Run("BulkAddBooks_InvalidCollectionID", func(t *testing.T) { - bookID := createTestMediaItemID(t, setup.Server) + bookID := createTestMediaItemID(t, setup) req := BulkAddBooksRequest{ Operations: []BulkAddOperation{ @@ -82,7 +81,7 @@ func TestCollectionsBulkOperations(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/collections/bulk-add-books", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(httpReq) require.NoError(t, err) @@ -119,7 +118,7 @@ func TestCollectionsBulkOperations(t *testing.T) { collectionHTTP, _ := http.NewRequest("POST", setup.Server.URL+"/api/collections", bytes.NewBuffer(collectionBody)) collectionHTTP.Header.Set("Content-Type", "application/json") - collectionHTTP.Header.Set("Authorization", "Bearer "+token) + collectionHTTP.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(collectionHTTP) require.NoError(t, err) @@ -142,7 +141,7 @@ func TestCollectionsBulkOperations(t *testing.T) { addHTTP, _ := http.NewRequest("POST", setup.Server.URL+"/api/collections/bulk-add-books", bytes.NewBuffer(addBody)) addHTTP.Header.Set("Content-Type", "application/json") - addHTTP.Header.Set("Authorization", "Bearer "+token) + addHTTP.Header.Set("Authorization", "Bearer "+setup.Token) resp, err = client.Do(addHTTP) require.NoError(t, err) @@ -168,7 +167,7 @@ func TestCollectionsBulkOperations(t *testing.T) { collectionHTTP, _ := http.NewRequest("POST", setup.Server.URL+"/api/collections", bytes.NewBuffer(collectionBody)) collectionHTTP.Header.Set("Content-Type", "application/json") - collectionHTTP.Header.Set("Authorization", "Bearer "+token) + collectionHTTP.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(collectionHTTP) require.NoError(t, err) @@ -179,7 +178,7 @@ func TestCollectionsBulkOperations(t *testing.T) { collectionID := collectionResult["id"].(string) // Create a book - bookID := createTestMediaItemID(t, setup.Server) + bookID := createTestMediaItemID(t, setup) // Add book to collection addReq := map[string]interface{}{ @@ -194,7 +193,7 @@ func TestCollectionsBulkOperations(t *testing.T) { addHTTP, _ := http.NewRequest("POST", setup.Server.URL+"/api/collections/bulk-add-books", bytes.NewBuffer(addBody)) addHTTP.Header.Set("Content-Type", "application/json") - addHTTP.Header.Set("Authorization", "Bearer "+token) + addHTTP.Header.Set("Authorization", "Bearer "+setup.Token) resp, err = client.Do(addHTTP) require.NoError(t, err) @@ -225,7 +224,7 @@ func TestCollectionsBulkOperations(t *testing.T) { collectionHTTP, _ := http.NewRequest("POST", setup.Server.URL+"/api/collections", bytes.NewBuffer(collectionBody)) collectionHTTP.Header.Set("Content-Type", "application/json") - collectionHTTP.Header.Set("Authorization", "Bearer "+token) + collectionHTTP.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(collectionHTTP) require.NoError(t, err) @@ -236,9 +235,9 @@ func TestCollectionsBulkOperations(t *testing.T) { collectionID := collectionResult["id"].(string) // Create multiple books - bookID1 := createTestMediaItemID(t, setup.Server) - bookID2 := createTestMediaItemID(t, setup.Server) - bookID3 := createTestMediaItemID(t, setup.Server) + bookID1 := createTestMediaItemID(t, setup) + bookID2 := createTestMediaItemID(t, setup) + bookID3 := createTestMediaItemID(t, setup) // Add all books to collection addReq := map[string]interface{}{ @@ -253,7 +252,7 @@ func TestCollectionsBulkOperations(t *testing.T) { addHTTP, _ := http.NewRequest("POST", setup.Server.URL+"/api/collections/bulk-add-books", bytes.NewBuffer(addBody)) addHTTP.Header.Set("Content-Type", "application/json") - addHTTP.Header.Set("Authorization", "Bearer "+token) + addHTTP.Header.Set("Authorization", "Bearer "+setup.Token) resp, err = client.Do(addHTTP) require.NoError(t, err) @@ -278,7 +277,7 @@ func TestCollectionsBulkOperations(t *testing.T) { collectionHTTP, _ := http.NewRequest("POST", setup.Server.URL+"/api/collections", bytes.NewBuffer(collectionBody)) collectionHTTP.Header.Set("Content-Type", "application/json") - collectionHTTP.Header.Set("Authorization", "Bearer "+token) + collectionHTTP.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(collectionHTTP) require.NoError(t, err) @@ -296,7 +295,7 @@ func TestCollectionsBulkOperations(t *testing.T) { collectionHTTP2, _ := http.NewRequest("POST", setup.Server.URL+"/api/collections", bytes.NewBuffer(collectionBody2)) collectionHTTP2.Header.Set("Content-Type", "application/json") - collectionHTTP2.Header.Set("Authorization", "Bearer "+token) + collectionHTTP2.Header.Set("Authorization", "Bearer "+setup.Token) resp, err = client.Do(collectionHTTP2) require.NoError(t, err) @@ -307,8 +306,8 @@ func TestCollectionsBulkOperations(t *testing.T) { collectionID2 := collectionResult2["id"].(string) // Create books - bookID1 := createTestMediaItemID(t, setup.Server) - bookID2 := createTestMediaItemID(t, setup.Server) + bookID1 := createTestMediaItemID(t, setup) + bookID2 := createTestMediaItemID(t, setup) // Add books to multiple collections addReq := map[string]interface{}{ @@ -327,7 +326,7 @@ func TestCollectionsBulkOperations(t *testing.T) { addHTTP, _ := http.NewRequest("POST", setup.Server.URL+"/api/collections/bulk-add-books", bytes.NewBuffer(addBody)) addHTTP.Header.Set("Content-Type", "application/json") - addHTTP.Header.Set("Authorization", "Bearer "+token) + addHTTP.Header.Set("Authorization", "Bearer "+setup.Token) resp, err = client.Do(addHTTP) require.NoError(t, err) @@ -352,7 +351,7 @@ func TestCollectionsBulkOperations(t *testing.T) { collectionHTTP, _ := http.NewRequest("POST", setup.Server.URL+"/api/collections", bytes.NewBuffer(collectionBody)) collectionHTTP.Header.Set("Content-Type", "application/json") - collectionHTTP.Header.Set("Authorization", "Bearer "+token) + collectionHTTP.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(collectionHTTP) require.NoError(t, err) @@ -363,7 +362,7 @@ func TestCollectionsBulkOperations(t *testing.T) { collectionID := collectionResult["id"].(string) // Create a book - bookID := createTestMediaItemID(t, setup.Server) + bookID := createTestMediaItemID(t, setup) // Add book to collection addReq := map[string]interface{}{ @@ -378,7 +377,7 @@ func TestCollectionsBulkOperations(t *testing.T) { addHTTP, _ := http.NewRequest("POST", setup.Server.URL+"/api/collections/bulk-add-books", bytes.NewBuffer(addBody)) addHTTP.Header.Set("Content-Type", "application/json") - addHTTP.Header.Set("Authorization", "Bearer "+token) + addHTTP.Header.Set("Authorization", "Bearer "+setup.Token) resp, err = client.Do(addHTTP) require.NoError(t, err) @@ -388,7 +387,7 @@ func TestCollectionsBulkOperations(t *testing.T) { addBody2, _ := json.Marshal(addReq) addHTTP2, _ := http.NewRequest("POST", setup.Server.URL+"/api/collections/bulk-add-books", bytes.NewBuffer(addBody2)) addHTTP2.Header.Set("Content-Type", "application/json") - addHTTP2.Header.Set("Authorization", "Bearer "+token) + addHTTP2.Header.Set("Authorization", "Bearer "+setup.Token) resp2, err := client.Do(addHTTP2) require.NoError(t, err) @@ -402,7 +401,7 @@ func TestCollectionsBulkOperations(t *testing.T) { // Send invalid JSON httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/collections/bulk-add-books", bytes.NewBuffer([]byte("invalid json"))) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(httpReq) require.NoError(t, err) diff --git a/cmd/server/tests/collections_preview_test.go b/cmd/server/tests/collections_preview_test.go index 0c4ed22..0ef7d07 100644 --- a/cmd/server/tests/collections_preview_test.go +++ b/cmd/server/tests/collections_preview_test.go @@ -76,7 +76,7 @@ func TestPreviewCollection(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) }) - t.Run("NonExistentLibrary_ReturnsInternalServerError", func(t *testing.T) { + t.Run("NonExistentLibrary_ReturnsBadRequest", func(t *testing.T) { reqBody := map[string]interface{}{ "library_id": uuid.New().String(), "rules": []map[string]interface{}{}, @@ -91,7 +91,7 @@ func TestPreviewCollection(t *testing.T) { rec := httptest.NewRecorder() setup.Server.Config.Handler.ServeHTTP(rec, req) - assert.Equal(t, http.StatusInternalServerError, rec.Code) + assert.Equal(t, http.StatusBadRequest, rec.Code) }) t.Run("EmptyRules_ReturnsEmptyItems", func(t *testing.T) { diff --git a/cmd/server/tests/conflicts_bulk_test.go b/cmd/server/tests/conflicts_bulk_test.go index def9787..cb3238e 100644 --- a/cmd/server/tests/conflicts_bulk_test.go +++ b/cmd/server/tests/conflicts_bulk_test.go @@ -15,7 +15,6 @@ import ( // TestConflictsBulkOperations tests bulk conflict resolution operations func TestConflictsBulkOperations(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) client := &http.Client{} t.Run("BulkResolveConflicts_WithoutAuth", func(t *testing.T) { @@ -44,7 +43,7 @@ func TestConflictsBulkOperations(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/conflicts/bulk-resolve", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(httpReq) require.NoError(t, err) @@ -62,7 +61,7 @@ func TestConflictsBulkOperations(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/conflicts/bulk-resolve", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(httpReq) require.NoError(t, err) @@ -91,7 +90,7 @@ func TestConflictsBulkOperations(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/conflicts/bulk-resolve", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(httpReq) require.NoError(t, err) @@ -123,7 +122,7 @@ func TestConflictsBulkOperations(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/conflicts/bulk-resolve", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(httpReq) require.NoError(t, err) @@ -147,7 +146,7 @@ func TestConflictsBulkOperations(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/conflicts/bulk-resolve", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(httpReq) require.NoError(t, err) @@ -171,7 +170,7 @@ func TestConflictsBulkOperations(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/conflicts/bulk-resolve", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(httpReq) require.NoError(t, err) @@ -195,7 +194,7 @@ func TestConflictsBulkOperations(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/conflicts/bulk-resolve", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(httpReq) require.NoError(t, err) @@ -208,7 +207,7 @@ func TestConflictsBulkOperations(t *testing.T) { // Send invalid JSON httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/conflicts/bulk-resolve", bytes.NewBuffer([]byte("invalid json"))) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(httpReq) require.NoError(t, err) @@ -221,7 +220,6 @@ func TestConflictsBulkOperations(t *testing.T) { // TestConflictsBulkDismiss tests bulk dismiss operations func TestConflictsBulkDismiss(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) client := &http.Client{} t.Run("BulkDismissConflicts_WithoutAuth", func(t *testing.T) { @@ -248,7 +246,7 @@ func TestConflictsBulkDismiss(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/conflicts/bulk-dismiss", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(httpReq) require.NoError(t, err) @@ -265,7 +263,7 @@ func TestConflictsBulkDismiss(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/conflicts/bulk-dismiss", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(httpReq) require.NoError(t, err) @@ -298,7 +296,7 @@ func TestConflictsBulkDismiss(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/conflicts/bulk-dismiss", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(httpReq) require.NoError(t, err) @@ -317,7 +315,7 @@ func TestConflictsBulkDismiss(t *testing.T) { // Send invalid JSON httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/conflicts/bulk-dismiss", bytes.NewBuffer([]byte("invalid json"))) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(httpReq) require.NoError(t, err) @@ -334,7 +332,6 @@ func TestConflictsBulkDismiss(t *testing.T) { /* func TestConflictsBulkEscalate(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) client := &http.Client{} t.Run("BulkEscalateConflicts_WithoutAuth", func(t *testing.T) { @@ -361,7 +358,7 @@ func TestConflictsBulkEscalate(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/conflicts/bulk-escalate", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(httpReq) require.NoError(t, err) @@ -396,7 +393,7 @@ func TestConflictsBulkEscalate(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/conflicts/bulk-escalate", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := client.Do(httpReq) require.NoError(t, err) diff --git a/cmd/server/tests/dashboard_integration_test.go b/cmd/server/tests/dashboard_integration_test.go index dde0b85..bd0cbc2 100644 --- a/cmd/server/tests/dashboard_integration_test.go +++ b/cmd/server/tests/dashboard_integration_test.go @@ -27,7 +27,7 @@ func (s *DashboardIntegrationTestSuite) TearDownSuite() { } func (s *DashboardIntegrationTestSuite) TestGetSections_EndToEndFlow() { - token := loginTestUser(s.T(), s.setup.Server, s.setup.DB) + token := s.setup.Token // Create test library libraryID := createTestLibraryWithFolder(s.T(), s.setup.Server, token, "Test Library", false) @@ -79,7 +79,7 @@ func (s *DashboardIntegrationTestSuite) TestGetSections_EndToEndFlow() { } func (s *DashboardIntegrationTestSuite) TestGetSections_MissingLibraryID() { - token := loginTestUser(s.T(), s.setup.Server, s.setup.DB) + token := s.setup.Token req, _ := http.NewRequest("GET", s.setup.Server.URL+"/api/dashboard/sections", nil) req.Header.Set("Authorization", "Bearer "+token) @@ -93,7 +93,7 @@ func (s *DashboardIntegrationTestSuite) TestGetSections_MissingLibraryID() { } func (s *DashboardIntegrationTestSuite) TestGetSections_InvalidLibraryID() { - token := loginTestUser(s.T(), s.setup.Server, s.setup.DB) + token := s.setup.Token req, _ := http.NewRequest("GET", s.setup.Server.URL+"/api/dashboard/sections?library_id=invalid-uuid", nil) req.Header.Set("Authorization", "Bearer "+token) @@ -118,7 +118,7 @@ func (s *DashboardIntegrationTestSuite) TestGetSections_Unauthorized() { } func (s *DashboardIntegrationTestSuite) TestUpdatePreferences_Success() { - token := loginTestUser(s.T(), s.setup.Server, s.setup.DB) + token := s.setup.Token libraryID := createTestLibraryWithFolder(s.T(), s.setup.Server, token, "Test Library", false) reqBody := map[string]interface{}{ @@ -170,7 +170,7 @@ func (s *DashboardIntegrationTestSuite) TestUpdatePreferences_Unauthorized() { } func (s *DashboardIntegrationTestSuite) TestRestoreSystemCollection_InvalidName() { - token := loginTestUser(s.T(), s.setup.Server, s.setup.DB) + token := s.setup.Token reqBody := map[string]interface{}{ "collection_name": "invalid-collection-name", @@ -207,7 +207,7 @@ func (s *DashboardIntegrationTestSuite) TestRestoreSystemCollection_Unauthorized } func (s *DashboardIntegrationTestSuite) TestRestoreSystemCollection_ValidNames() { - token := loginTestUser(s.T(), s.setup.Server, s.setup.DB) + token := s.setup.Token validCollections := []string{"continue-reading", "recently-added", "recently-read", "not-started"} diff --git a/cmd/server/tests/device_cap_test.go b/cmd/server/tests/device_cap_test.go index 5cff6cf..bbd4959 100644 --- a/cmd/server/tests/device_cap_test.go +++ b/cmd/server/tests/device_cap_test.go @@ -17,11 +17,7 @@ import ( // TestUpdateUserMaxDevices tests the admin endpoint for updating user device cap func TestUpdateUserMaxDevices(t *testing.T) { setup := setupTestServer(t) - - // Create test user with admin role - loginTestUser(t, setup.Server, setup.DB) - adminUserID := getTestUserID(t, setup.DB) - adminToken := getAdminToken(t, setup.Server, adminUserID) + adminToken := setup.Token // Create a test user userID := createTestUserForMaxDevices(t, setup.Server, adminToken) @@ -89,7 +85,7 @@ func TestUpdateUserMaxDevicesValidation(t *testing.T) { setup := setupTestServer(t) // Create admin user and get token - adminToken := loginTestUser(t, setup.Server, setup.DB) + adminToken := setup.Token createAdminUser(t, setup.Server, adminToken) adminUserID := getTestUserID(t, setup.DB) adminToken = getAdminToken(t, setup.Server, adminUserID) @@ -150,7 +146,7 @@ func TestUpdateUserMaxDevicesAuth(t *testing.T) { setup := setupTestServer(t) // Create admin user - adminToken := loginTestUser(t, setup.Server, setup.DB) + adminToken := setup.Token createAdminUser(t, setup.Server, adminToken) adminUserID := getTestUserID(t, setup.DB) adminToken = getAdminToken(t, setup.Server, adminUserID) @@ -205,7 +201,7 @@ func TestUpdateUserMaxDevicesNonExistentUser(t *testing.T) { setup := setupTestServer(t) // Create admin user - adminToken := loginTestUser(t, setup.Server, setup.DB) + adminToken := setup.Token createAdminUser(t, setup.Server, adminToken) adminUserID := getTestUserID(t, setup.DB) adminToken = getAdminToken(t, setup.Server, adminUserID) @@ -236,7 +232,7 @@ func TestUpdateUserMaxDevicesMissingUserID(t *testing.T) { setup := setupTestServer(t) // Create admin user - adminToken := loginTestUser(t, setup.Server, setup.DB) + adminToken := setup.Token createAdminUser(t, setup.Server, adminToken) adminUserID := getTestUserID(t, setup.DB) adminToken = getAdminToken(t, setup.Server, adminUserID) @@ -264,7 +260,7 @@ func TestListUsersIncludesMaxDevices(t *testing.T) { setup := setupTestServer(t) // Create admin user - adminToken := loginTestUser(t, setup.Server, setup.DB) + adminToken := setup.Token createAdminUser(t, setup.Server, adminToken) adminUserID := getTestUserID(t, setup.DB) adminToken = getAdminToken(t, setup.Server, adminUserID) diff --git a/cmd/server/tests/device_test.go b/cmd/server/tests/device_test.go index bee80fe..6bd8696 100644 --- a/cmd/server/tests/device_test.go +++ b/cmd/server/tests/device_test.go @@ -237,10 +237,8 @@ func TestDeviceAuthentication(t *testing.T) { func TestListPendingRegistrations(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) - req := httptest.NewRequest("GET", "/api/devices/pending", nil) - req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Authorization", "Bearer "+setup.Token) rec := httptest.NewRecorder() setup.Server.Config.Handler.ServeHTTP(rec, req) @@ -257,8 +255,6 @@ func TestListPendingRegistrations(t *testing.T) { func TestApproveDeviceRegistration(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) - regRequest := map[string]interface{}{ "device_name": "Test Device for Approval", "device_type": "koreader", @@ -280,7 +276,7 @@ func TestApproveDeviceRegistration(t *testing.T) { assert.True(t, ok, "Should have registration_id") req = httptest.NewRequest("GET", fmt.Sprintf("/api/devices/approve/%s", registrationID), nil) - req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Authorization", "Bearer "+setup.Token) rec = httptest.NewRecorder() setup.Server.Config.Handler.ServeHTTP(rec, req) @@ -295,8 +291,6 @@ func TestApproveDeviceRegistration(t *testing.T) { func TestRejectDeviceRegistration(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) - regRequest := map[string]interface{}{ "device_name": "Test Device for Rejection", "device_type": "koreader", @@ -318,7 +312,7 @@ func TestRejectDeviceRegistration(t *testing.T) { assert.True(t, ok, "Should have registration_id") req = httptest.NewRequest("POST", fmt.Sprintf("/api/devices/reject/%s", registrationID), nil) - req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Authorization", "Bearer "+setup.Token) rec = httptest.NewRecorder() setup.Server.Config.Handler.ServeHTTP(rec, req) diff --git a/cmd/server/tests/kobo_test.go b/cmd/server/tests/kobo_test.go index cad9758..fed3f8e 100644 --- a/cmd/server/tests/kobo_test.go +++ b/cmd/server/tests/kobo_test.go @@ -21,7 +21,7 @@ func TestKoboInitialization(t *testing.T) { setup := setupTestServer(t) - _ = createTestMediaItemID(t, setup.Server) + _ = createTestMediaItemID(t, setup) log.Printf("[DEBUG] Kobo test setup: creating device and media") @@ -50,7 +50,7 @@ func TestKoboLibrarySync(t *testing.T) { setup := setupTestServer(t) - _ = createTestMediaItemID(t, setup.Server) + _ = createTestMediaItemID(t, setup) log.Printf("[DEBUG] Kobo test setup: creating device and media") @@ -78,7 +78,7 @@ func TestKoboMarkupSync(t *testing.T) { setup := setupTestServer(t) - mediaItemID := createTestMediaItemID(t, setup.Server) + mediaItemID := createTestMediaItemID(t, setup) log.Printf("[DEBUG] Kobo test setup: creating device and media") @@ -145,7 +145,7 @@ func TestKoboBookmarkSync(t *testing.T) { setup := setupTestServer(t) - mediaItemID := createTestMediaItemID(t, setup.Server) + mediaItemID := createTestMediaItemID(t, setup) log.Printf("[DEBUG] Kobo test setup: creating device and media") @@ -198,7 +198,7 @@ func TestKoboAnalyticsGettests(t *testing.T) { setup := setupTestServer(t) - mediaItemID := createTestMediaItemID(t, setup.Server) + mediaItemID := createTestMediaItemID(t, setup) log.Printf("[DEBUG] Kobo test setup: creating device and media") diff --git a/cmd/server/tests/media_bulk_test.go b/cmd/server/tests/media_bulk_test.go index ffff6a6..05fa3d7 100644 --- a/cmd/server/tests/media_bulk_test.go +++ b/cmd/server/tests/media_bulk_test.go @@ -16,7 +16,6 @@ import ( // TestMediaBulkOperations tests bulk media operations func TestMediaBulkOperations(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) t.Run("BulkDeleteBooks_WithoutAuth", func(t *testing.T) { mediaIDs := []string{uuid.New().String()} @@ -54,7 +53,7 @@ func TestMediaBulkOperations(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/media-items/bulk-delete", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -72,7 +71,7 @@ func TestMediaBulkOperations(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/media-items/bulk-delete", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -91,8 +90,8 @@ func TestMediaBulkOperations(t *testing.T) { }) t.Run("BulkDeleteBooks_WithValidBooks", func(t *testing.T) { - mediaID1 := createTestMediaItemID(t, setup.Server) - mediaID2 := createTestMediaItemID(t, setup.Server) + mediaID1 := createTestMediaItemID(t, setup) + mediaID2 := createTestMediaItemID(t, setup) mediaID3 := uuid.New().String() req := map[string]interface{}{ @@ -102,7 +101,7 @@ func TestMediaBulkOperations(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/media-items/bulk-delete", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -126,7 +125,7 @@ func TestMediaBulkOperations(t *testing.T) { // Send invalid JSON httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/media-items/bulk-delete", bytes.NewBuffer([]byte("invalid json"))) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -175,7 +174,7 @@ func TestMediaBulkOperations(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/media-items/bulk-update", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -200,7 +199,7 @@ func TestMediaBulkOperations(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/media-items/bulk-update", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -219,8 +218,8 @@ func TestMediaBulkOperations(t *testing.T) { }) t.Run("BulkUpdateBooks_UpdateTags", func(t *testing.T) { - mediaID1 := createTestMediaItemID(t, setup.Server) - mediaID2 := createTestMediaItemID(t, setup.Server) + mediaID1 := createTestMediaItemID(t, setup) + mediaID2 := createTestMediaItemID(t, setup) req := map[string]interface{}{ "media_item_updates": []map[string]interface{}{ @@ -242,7 +241,7 @@ func TestMediaBulkOperations(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/media-items/bulk-update", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -263,10 +262,10 @@ func TestMediaBulkOperations(t *testing.T) { }) t.Run("BulkUpdateBooks_UpdateReadingStatus", func(t *testing.T) { - mediaID1 := createTestMediaItemID(t, setup.Server) - mediaID2 := createTestMediaItemID(t, setup.Server) - mediaID3 := createTestMediaItemID(t, setup.Server) - mediaID4 := createTestMediaItemID(t, setup.Server) + mediaID1 := createTestMediaItemID(t, setup) + mediaID2 := createTestMediaItemID(t, setup) + mediaID3 := createTestMediaItemID(t, setup) + mediaID4 := createTestMediaItemID(t, setup) req := map[string]interface{}{ "media_item_updates": []map[string]interface{}{ @@ -300,7 +299,7 @@ func TestMediaBulkOperations(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/media-items/bulk-update", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -330,7 +329,7 @@ func TestMediaBulkOperations(t *testing.T) { }) t.Run("BulkUpdateBooks_UpdateMultipleFields", func(t *testing.T) { - mediaID1 := createTestMediaItemID(t, setup.Server) + mediaID1 := createTestMediaItemID(t, setup) req := map[string]interface{}{ "media_item_updates": []map[string]interface{}{ @@ -348,7 +347,7 @@ func TestMediaBulkOperations(t *testing.T) { httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/media-items/bulk-update", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) @@ -367,7 +366,7 @@ func TestMediaBulkOperations(t *testing.T) { // Send invalid JSON httpReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/media-items/bulk-update", bytes.NewBuffer([]byte("invalid json"))) httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("Authorization", "Bearer "+token) + httpReq.Header.Set("Authorization", "Bearer "+setup.Token) client := &http.Client{} resp, err := client.Do(httpReq) diff --git a/cmd/server/tests/media_item_isbn_test.go b/cmd/server/tests/media_item_isbn_test.go index b21a5ef..6c257b1 100644 --- a/cmd/server/tests/media_item_isbn_test.go +++ b/cmd/server/tests/media_item_isbn_test.go @@ -67,7 +67,7 @@ func TestMediaItemISBNNormalization(t *testing.T) { setup := setupTestServer(t) // Create an ebook library first - token := loginTestUser(t, setup.Server, setup.DB) + token := setup.Token libID := createTestLibrary(t, setup.Server, token, "test-isbn-lib") // Add a folder to the library (required before adding media items) @@ -209,7 +209,7 @@ func TestMediaItemISBNNormalization(t *testing.T) { func TestMediaItemISBNEdgeCases(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) + token := setup.Token libID := createTestLibrary(t, setup.Server, token, "test-isbn-edge-lib") addFolderToLibrary(t, setup.Server, token, libID, "/app/uploads") @@ -294,7 +294,7 @@ func TestMediaItemISBNEdgeCases(t *testing.T) { func TestMediaItemsPagination(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) + token := setup.Token libID := createTestLibrary(t, setup.Server, token, "test-pagination-lib") addFolderToLibrary(t, setup.Server, token, libID, "/app/uploads") @@ -406,7 +406,7 @@ func TestMediaItemsPagination(t *testing.T) { func TestMediaItemLibraryRequirement(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) + token := setup.Token t.Run("Create media-item without library should fail gracefully", func(t *testing.T) { payload := map[string]interface{}{ @@ -469,7 +469,7 @@ func TestMediaItemLibraryRequirement(t *testing.T) { func TestUpdateMediaItemISBN(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) + token := setup.Token libID := createTestLibrary(t, setup.Server, token, "test-update-lib") addFolderToLibrary(t, setup.Server, token, libID, "/app/uploads") diff --git a/cmd/server/tests/queue_test.go b/cmd/server/tests/queue_test.go index c7fe289..39879a3 100644 --- a/cmd/server/tests/queue_test.go +++ b/cmd/server/tests/queue_test.go @@ -74,7 +74,7 @@ func createTestQueueItem(t *testing.T, db *database.Queries, userID uuid.UUID) ( func TestListAllQueueItems_Admin(t *testing.T) { setup := setupTestServer(t) - token := loginAdminUser(t, setup.Server, setup.DB) + token := setup.Token req := httptest.NewRequest("GET", "/api/queue/items", nil) req.Header.Set("Authorization", "Bearer "+token) @@ -91,7 +91,7 @@ func TestListAllQueueItems_Admin(t *testing.T) { func TestGetDeviceQueueStats(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) + token := setup.Token userID := getTestUserID(t, setup.DB) @@ -131,7 +131,7 @@ func TestGetDeviceQueueStats(t *testing.T) { func TestListDeviceQueueItems(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) + token := setup.Token userID := getTestUserID(t, setup.DB) @@ -168,7 +168,7 @@ func TestListDeviceQueueItems(t *testing.T) { func TestRetryQueueItem(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) + token := setup.Token // Get the current test user's ID from database user, err := setup.DB.GetUserByEmail(context.Background(), "testuser@example.com") @@ -189,7 +189,7 @@ func TestRetryQueueItem(t *testing.T) { func TestDeleteQueueItem(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) + token := setup.Token // Get the current test user's ID from database user, err := setup.DB.GetUserByEmail(context.Background(), "testuser@example.com") @@ -211,7 +211,7 @@ func TestDeleteQueueItem(t *testing.T) { func TestClearDeviceQueue(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) + token := setup.Token userID := getTestUserID(t, setup.DB) @@ -268,65 +268,3 @@ func TestQueueEndpoints_Unauthorized(t *testing.T) { }) } } - -func loginAdminUser(t *testing.T, ts *httptest.Server, db *database.Queries) string { - ctx := context.Background() - - // Check if admin user exists and delete them first to ensure fresh state - // (using database delete directly to bypass "last admin" check) - user, err := db.GetUserByEmail(ctx, "admin@example.com") - if err == nil { - err = db.DeleteUser(ctx, user.ID) - if err != nil { - t.Logf("Warning: Could not delete existing admin user: %v", err) - } - } - - // Create a fresh admin user with known password - // Password: "Test@Pass123!" meets complexity requirements - // This is the bcrypt hash for "Test@Pass123!" - passwordHash := "$2a$10$JjAtK7PPa1WexQC3AUGe8OXLeuseZ/haN1Mz7emMo6CfOvMiTVXWq" - - adminUser, err := db.CreateUser(ctx, database.CreateUserParams{ - Email: "admin@example.com", - Username: "admin", - PasswordHash: passwordHash, - FirstName: pgtype.Text{String: "Admin", Valid: true}, - LastName: pgtype.Text{String: "User", Valid: true}, - Role: "admin", - Theme: pgtype.Text{String: "tokyo-night", Valid: true}, - }) - require.NoError(t, err, "Failed to create admin user") - - userUUID, err := uuid.FromBytes(adminUser.ID.Bytes[:]) - require.NoError(t, err, "Should parse admin user UUID") - - return loginUserWithID(t, ts, db, userUUID, "admin@example.com", "Test@Pass123!") -} - -func loginUserWithID(t *testing.T, ts *httptest.Server, db *database.Queries, userID uuid.UUID, email, password string) string { - loginRequest := map[string]interface{}{ - "login": email, - "password": password, - } - body, _ := json.Marshal(loginRequest) - - req, _ := http.NewRequest("POST", ts.URL+"/api/auth/login", bytes.NewBuffer(body)) - req.Header.Set("Content-Type", "application/json") - - client := &http.Client{} - resp, err := client.Do(req) - require.NoError(t, err, "Failed to login") - defer resp.Body.Close() - - require.Equal(t, http.StatusOK, resp.StatusCode, "Login should succeed") - - var result map[string]interface{} - json.NewDecoder(resp.Body).Decode(&result) - - token, ok := result["access_token"].(string) - require.True(t, ok, "Should have access_token") - require.NotEmpty(t, token, "Access token should not be empty") - - return token -} diff --git a/cmd/server/tests/system_settings_test.go b/cmd/server/tests/system_settings_test.go index 37d23e9..f332f18 100644 --- a/cmd/server/tests/system_settings_test.go +++ b/cmd/server/tests/system_settings_test.go @@ -24,7 +24,7 @@ func TestSystemSettingsHandler(t *testing.T) { }) t.Run("GET /api/libraries/scan-settings - Get settings as non-admin", func(t *testing.T) { - token := loginRegularUser(t, setup.Server, setup.DB) + token := setup.RegularToken req := httptest.NewRequest("GET", "/api/libraries/scan-settings", nil) req.Header.Set("Authorization", "Bearer "+token) @@ -35,7 +35,7 @@ func TestSystemSettingsHandler(t *testing.T) { }) t.Run("GET /api/libraries/scan-settings - Get settings as admin", func(t *testing.T) { - token := loginAdminUser(t, setup.Server, setup.DB) + token := setup.Token req := httptest.NewRequest("GET", "/api/libraries/scan-settings", nil) req.Header.Set("Authorization", "Bearer "+token) @@ -67,7 +67,7 @@ func TestSystemSettingsHandler(t *testing.T) { }) t.Run("PUT /api/libraries/scan-settings - Update as non-admin", func(t *testing.T) { - token := loginRegularUser(t, setup.Server, setup.DB) + token := setup.RegularToken payload := map[string]interface{}{ "scan_frequency_minutes": 30, @@ -85,7 +85,7 @@ func TestSystemSettingsHandler(t *testing.T) { }) t.Run("PUT /api/libraries/scan-settings - Update with invalid frequency", func(t *testing.T) { - token := loginAdminUser(t, setup.Server, setup.DB) + token := setup.Token testCases := []struct { name string @@ -117,7 +117,7 @@ func TestSystemSettingsHandler(t *testing.T) { }) t.Run("PUT /api/libraries/scan-settings - Update with missing required field", func(t *testing.T) { - token := loginAdminUser(t, setup.Server, setup.DB) + token := setup.Token payload := map[string]interface{}{ "auto_scan_enabled": true, @@ -134,7 +134,7 @@ func TestSystemSettingsHandler(t *testing.T) { }) t.Run("PUT /api/libraries/scan-settings - Update with valid data", func(t *testing.T) { - token := loginAdminUser(t, setup.Server, setup.DB) + token := setup.Token testCases := []struct { name string @@ -174,7 +174,7 @@ func TestSystemSettingsHandler(t *testing.T) { }) t.Run("PUT /api/libraries/scan-settings - Update with invalid JSON", func(t *testing.T) { - token := loginAdminUser(t, setup.Server, setup.DB) + token := setup.Token invalidJSON := []byte(`{scan_frequency_minutes: 60, auto_scan_enabled: true}`) diff --git a/cmd/server/tests/test_helpers.go b/cmd/server/tests/test_helpers.go index 2d0f72a..b24b425 100644 --- a/cmd/server/tests/test_helpers.go +++ b/cmd/server/tests/test_helpers.go @@ -84,6 +84,8 @@ type TestServerSetup struct { CleanupCancel context.CancelFunc QueueCtx context.Context QueueCancel context.CancelFunc + Token string + RegularToken string mu sync.Mutex closed bool } @@ -207,39 +209,13 @@ func setupDeviceTest(t *testing.T) *TestDeviceSetup { } } -// createTestUserOnce creates a test user with deterministic UUID +// createTestUserOnce returns the pre-created test user info func createTestUserOnce(t *testing.T, db *database.Queries) UserTestData { ctx := context.Background() - - // Check if user exists and delete for fresh state - existingUser, err := db.GetUserByEmail(ctx, "testuser@example.com") - if err == nil { - // User exists, delete them to ensure fresh password - err = db.DeleteUser(ctx, existingUser.ID) - if err != nil { - // If delete fails (user might be referenced elsewhere), log and continue - t.Logf("Warning: Could not delete existing test user: %v", err) - } - } - - // Create a fresh test user with a valid password - // Password: "Test@Pass123!" meets complexity requirements - // This is a bcrypt hash for "Test@Pass123!" - passwordHash := "$2a$10$JjAtK7PPa1WexQC3AUGe8OXLeuseZ/haN1Mz7emMo6CfOvMiTVXWq" - newUser, err := db.CreateUser(ctx, database.CreateUserParams{ - Email: "testuser@example.com", - Username: "testuser", - PasswordHash: passwordHash, - FirstName: pgtype.Text{String: "Test", Valid: true}, - LastName: pgtype.Text{String: "User", Valid: true}, - Role: "admin", - }) - require.NoError(t, err, "Should create test user") - - // Get the user ID from created user - userUUID, err := uuid.FromBytes(newUser.ID.Bytes[0:16]) + user, err := db.GetUserByEmail(ctx, "testuser@example.com") + require.NoError(t, err, "Test user should exist (created by setupTestServer)") + userUUID, err := uuid.FromBytes(user.ID.Bytes[0:16]) require.NoError(t, err, "Should parse user UUID") - return UserTestData{ ID: userUUID, Email: "testuser@example.com", @@ -248,6 +224,79 @@ func createTestUserOnce(t *testing.T, db *database.Queries) UserTestData { } } +// createRegularUserOnce creates a regular (non-admin) test user with unique credentials +func createRegularUserOnce(t *testing.T, db *database.Queries) UserTestData { + ctx := context.Background() + uniqueID := uuid.New().String()[:8] + email := fmt.Sprintf("regularuser-%s@example.com", uniqueID) + username := fmt.Sprintf("regularuser-%s", uniqueID) + + passwordHash := "$2a$10$JjAtK7PPa1WexQC3AUGe8OXLeuseZ/haN1Mz7emMo6CfOvMiTVXWq" + newUser, err := db.CreateUser(ctx, database.CreateUserParams{ + Email: email, + Username: username, + PasswordHash: passwordHash, + FirstName: pgtype.Text{String: "Regular", Valid: true}, + LastName: pgtype.Text{String: "User", Valid: true}, + Role: "user", + }) + require.NoError(t, err, "Should create regular test user") + + userUUID, err := uuid.FromBytes(newUser.ID.Bytes[0:16]) + require.NoError(t, err, "Should parse user UUID") + + createDefaultCollectionsForUser(t, db, pgtype.UUID{Bytes: userUUID, Valid: true}) + + t.Cleanup(func() { + ctx := context.Background() + db.DeleteUser(ctx, pgtype.UUID{Bytes: [16]byte(userUUID), Valid: true}) + }) + + return UserTestData{ + ID: userUUID, + Email: email, + Username: username, + Password: "Test@Pass123!", + } +} + +// uuidToPGType converts uuid.UUID to pgtype.UUID +func uuidToPGType(u uuid.UUID) pgtype.UUID { + return pgtype.UUID{Bytes: [16]byte(u), Valid: true} +} + +// createDefaultCollectionsForUser creates the 4 default system collections for a user +func createDefaultCollectionsForUser(t *testing.T, db *database.Queries, userID pgtype.UUID) { + ctx := context.Background() + defaultCollections := []struct { + Name string + Description string + Icon string + Color string + QueryType string + Priority int32 + }{ + {"continue-reading", "Books you're currently reading (0 < progress < 1)", "📖", "#7aa2f7", "continue-reading", 1}, + {"recently-added", "Newly added items to this library", "🆕", "#9ece6a", "recently-added", 2}, + {"recently-read", "Books you've finished (progress >= 1)", "✅", "#e0af68", "recently-read", 3}, + {"not-started", "Books you haven't read yet (progress = 0 or no record)", "📕", "#f7768e", "not-started", 4}, + } + + for _, col := range defaultCollections { + _, err := db.CreateSystemCollection(ctx, database.CreateSystemCollectionParams{ + UserID: userID, + Name: col.Name, + Description: pgtype.Text{String: col.Description, Valid: true}, + Icon: pgtype.Text{String: col.Icon, Valid: true}, + Color: pgtype.Text{String: col.Color, Valid: true}, + ShowOnDashboard: pgtype.Bool{Bool: true, Valid: true}, + QueryType: pgtype.Text{String: col.QueryType, Valid: true}, + Priority: pgtype.Int4{Int32: col.Priority, Valid: true}, + }) + require.NoError(t, err, "Should create default collection: "+col.Name) + } +} + // loginUserWithCredentials performs explicit login with provided credentials func loginUserWithCredentials(t *testing.T, ts *httptest.Server, email, password string) string { loginRequest := map[string]interface{}{ @@ -467,6 +516,51 @@ func setupTestServer(t *testing.T) *TestServerSetup { // Create test server ts := httptest.NewServer(e) + ctx := context.Background() + + // Delete existing test users if they exist (cascades to delete collections, media items, etc.) + for _, email := range []string{"testuser@example.com", "testregularuser@example.com"} { + existingUser, err := queries.GetUserByEmail(ctx, email) + if err == nil { + queries.DeleteUser(ctx, existingUser.ID) + } + } + + // Create fresh admin test user + passwordHash := "$2a$10$JjAtK7PPa1WexQC3AUGe8OXLeuseZ/haN1Mz7emMo6CfOvMiTVXWq" + adminUser, err := queries.CreateUser(ctx, database.CreateUserParams{ + Email: "testuser@example.com", + Username: "testuser", + PasswordHash: passwordHash, + FirstName: pgtype.Text{String: "Test", Valid: true}, + LastName: pgtype.Text{String: "User", Valid: true}, + Role: "admin", + }) + require.NoError(t, err, "Failed to create admin test user") + + adminUUID, err := uuid.FromBytes(adminUser.ID.Bytes[:]) + require.NoError(t, err, "Failed to parse admin user UUID") + createDefaultCollectionsForUser(t, queries, pgtype.UUID{Bytes: adminUUID, Valid: true}) + + // Create fresh regular test user + regularUser, err := queries.CreateUser(ctx, database.CreateUserParams{ + Email: "testregularuser@example.com", + Username: "testregularuser", + PasswordHash: passwordHash, + FirstName: pgtype.Text{String: "Regular", Valid: true}, + LastName: pgtype.Text{String: "User", Valid: true}, + Role: "user", + }) + require.NoError(t, err, "Failed to create regular test user") + + regularUUID, err := uuid.FromBytes(regularUser.ID.Bytes[:]) + require.NoError(t, err, "Failed to parse regular user UUID") + createDefaultCollectionsForUser(t, queries, pgtype.UUID{Bytes: regularUUID, Valid: true}) + + // Login to get tokens + adminToken := loginWithCredentials(t, ts, "testuser@example.com", "Test@Pass123!") + regularToken := loginWithCredentials(t, ts, "testregularuser@example.com", "Test@Pass123!") + // Create TestServerSetup struct with all resources setup := &TestServerSetup{ Server: ts, @@ -478,6 +572,8 @@ func setupTestServer(t *testing.T) *TestServerSetup { CleanupCancel: cleanupCancel, QueueCtx: queueCtx, QueueCancel: queueCancel, + Token: adminToken, + RegularToken: regularToken, } // Register cleanup function to run automatically when test completes @@ -490,14 +586,10 @@ func setupTestServer(t *testing.T) *TestServerSetup { return setup } -// loginTestUser logs in a test user and returns the JWT token -func loginTestUser(t *testing.T, ts *httptest.Server, db *database.Queries) string { - // Ensure test user exists first - _ = getTestUserID(t, db) - +func loginWithCredentials(t *testing.T, ts *httptest.Server, email, password string) string { loginRequest := map[string]interface{}{ - "login": "testuser@example.com", - "password": "Test@Pass123!", + "login": email, + "password": password, } body, _ := json.Marshal(loginRequest) @@ -506,7 +598,7 @@ func loginTestUser(t *testing.T, ts *httptest.Server, db *database.Queries) stri client := &http.Client{} resp, err := client.Do(req) - require.NoError(t, err, "Failed to login test user") + require.NoError(t, err, "Failed to login") defer resp.Body.Close() require.Equal(t, http.StatusOK, resp.StatusCode, "Login should succeed") @@ -523,125 +615,27 @@ func loginTestUser(t *testing.T, ts *httptest.Server, db *database.Queries) stri func getTestUserID(t *testing.T, db *database.Queries) uuid.UUID { ctx := context.Background() - - // Check if test user exists and delete them first to ensure fresh state user, err := db.GetUserByEmail(ctx, "testuser@example.com") - if err == nil { - // User exists, delete them to ensure fresh password - err = db.DeleteUser(ctx, user.ID) - if err != nil { - // If delete fails (user might be referenced elsewhere), log and continue - t.Logf("Warning: Could not delete existing test user: %v", err) - } - } - - // Create a fresh test user with a valid password - // Password: "Test@Pass123!" meets complexity requirements - // This is the bcrypt hash for "Test@Pass123!" - passwordHash := "$2a$10$JjAtK7PPa1WexQC3AUGe8OXLeuseZ/haN1Mz7emMo6CfOvMiTVXWq" - - newUser, err := db.CreateUser(ctx, database.CreateUserParams{ - Email: "testuser@example.com", - Username: "testuser", - PasswordHash: passwordHash, - FirstName: pgtype.Text{String: "Test", Valid: true}, - LastName: pgtype.Text{String: "User", Valid: true}, - Role: "admin", - }) - require.NoError(t, err, "Failed to create test user") - - userUUID, err := uuid.FromBytes(newUser.ID.Bytes[:]) + require.NoError(t, err, "Test user should exist") + userUUID, err := uuid.FromBytes(user.ID.Bytes[:]) require.NoError(t, err, "Failed to parse user UUID") return userUUID } -func loginRegularUser(t *testing.T, ts *httptest.Server, db *database.Queries) string { - // Ensure test user exists first - _ = getRegularUserID(t, db) - - loginRequest := map[string]interface{}{ - "login": "testregularuser@example.com", - "password": "Test@Pass123!", - } - body, _ := json.Marshal(loginRequest) - - req, _ := http.NewRequest("POST", ts.URL+"/api/auth/login", bytes.NewBuffer(body)) - req.Header.Set("Content-Type", "application/json") - - client := &http.Client{} - resp, err := client.Do(req) - require.NoError(t, err, "Failed to login test user") - defer resp.Body.Close() - - require.Equal(t, http.StatusOK, resp.StatusCode, "Login should succeed") - - var result map[string]interface{} - json.NewDecoder(resp.Body).Decode(&result) - - token, ok := result["access_token"].(string) - require.True(t, ok, "Should have access_token") - require.NotEmpty(t, token, "Access token should not be empty") - - return token -} - func getRegularUserID(t *testing.T, db *database.Queries) uuid.UUID { ctx := context.Background() - - // Check if test user exists and delete them first to ensure fresh state user, err := db.GetUserByEmail(ctx, "testregularuser@example.com") - if err == nil { - // User exists, delete them to ensure fresh password - err = db.DeleteUser(ctx, user.ID) - if err != nil { - // If delete fails (user might be referenced elsewhere), log and continue - t.Logf("Warning: Could not delete existing test user: %v", err) - } - } - - // Create a fresh test user with a valid password - // Password: "Test@Pass123!" meets complexity requirements - // This is the bcrypt hash for "Test@Pass123!" - passwordHash := "$2a$10$JjAtK7PPa1WexQC3AUGe8OXLeuseZ/haN1Mz7emMo6CfOvMiTVXWq" - - newUser, err := db.CreateUser(ctx, database.CreateUserParams{ - Email: "testregularuser@example.com", - Username: "testregularuser", - PasswordHash: passwordHash, - FirstName: pgtype.Text{String: "Test", Valid: true}, - LastName: pgtype.Text{String: "User", Valid: true}, - Role: "user", - }) - require.NoError(t, err, "Failed to create test user") - - userUUID, err := uuid.FromBytes(newUser.ID.Bytes[:]) + require.NoError(t, err, "Regular user should exist") + userUUID, err := uuid.FromBytes(user.ID.Bytes[:]) require.NoError(t, err, "Failed to parse user UUID") return userUUID } // createTestMediaItemID creates a test media item and returns its ID -func createTestMediaItemID(t *testing.T, ts *httptest.Server) string { - // Generate unique library name to avoid conflicts between tests +func createTestMediaItemID(t *testing.T, setup *TestServerSetup) string { uniqueName := fmt.Sprintf("Test Library %d", time.Now().UnixNano()) - - // Get a fresh token to ensure we have a valid user - // (previous tests may have deleted/recreated the test user) - loginReq := map[string]interface{}{ - "login": "testuser@example.com", - "password": "Test@Pass123!", - } - loginBody, _ := json.Marshal(loginReq) - loginReqHTTP, _ := http.NewRequest("POST", ts.URL+"/api/auth/login", bytes.NewBuffer(loginBody)) - loginReqHTTP.Header.Set("Content-Type", "application/json") httpClient := &http.Client{} - loginResp, err := httpClient.Do(loginReqHTTP) - require.NoError(t, err) - defer loginResp.Body.Close() - var loginResult map[string]interface{} - json.NewDecoder(loginResp.Body).Decode(&loginResult) - validToken := loginResult["access_token"].(string) - // First create a library libReq := map[string]interface{}{ "name": uniqueName, "description": "A test library for media items", @@ -649,9 +643,9 @@ func createTestMediaItemID(t *testing.T, ts *httptest.Server) string { } libBody, _ := json.Marshal(libReq) - req, _ := http.NewRequest("POST", ts.URL+"/api/libraries", bytes.NewBuffer(libBody)) + req, _ := http.NewRequest("POST", setup.Server.URL+"/api/libraries", bytes.NewBuffer(libBody)) req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer "+validToken) + req.Header.Set("Authorization", "Bearer "+setup.Token) resp, err := httpClient.Do(req) require.NoError(t, err) @@ -664,23 +658,20 @@ func createTestMediaItemID(t *testing.T, ts *httptest.Server) string { libData := libResult["id"].(string) - // Add a folder to the library (required before adding media items) - // Use /app/uploads which is already mounted in the test container folderReq := map[string]interface{}{ "folder_path": "/app/uploads", } folderBody, _ := json.Marshal(folderReq) - folderReqHTTP, _ := http.NewRequest("POST", ts.URL+"/api/libraries/"+libData+"/folders", bytes.NewBuffer(folderBody)) + folderReqHTTP, _ := http.NewRequest("POST", setup.Server.URL+"/api/libraries/"+libData+"/folders", bytes.NewBuffer(folderBody)) folderReqHTTP.Header.Set("Content-Type", "application/json") - folderReqHTTP.Header.Set("Authorization", "Bearer "+validToken) + folderReqHTTP.Header.Set("Authorization", "Bearer "+setup.Token) folderResp, err := httpClient.Do(folderReqHTTP) require.NoError(t, err) defer folderResp.Body.Close() require.Equal(t, http.StatusCreated, folderResp.StatusCode, "Library folder creation is required before adding media items") - // Create a test media item mediaItemReq := map[string]interface{}{ "library_id": libData, "title": "Test Media Item", @@ -691,9 +682,9 @@ func createTestMediaItemID(t *testing.T, ts *httptest.Server) string { } mediaItemBody, _ := json.Marshal(mediaItemReq) - req2, _ := http.NewRequest("POST", ts.URL+"/api/media-items", bytes.NewBuffer(mediaItemBody)) + req2, _ := http.NewRequest("POST", setup.Server.URL+"/api/media-items", bytes.NewBuffer(mediaItemBody)) req2.Header.Set("Content-Type", "application/json") - req2.Header.Set("Authorization", "Bearer "+validToken) + req2.Header.Set("Authorization", "Bearer "+setup.Token) resp2, err := httpClient.Do(req2) require.NoError(t, err) @@ -706,10 +697,9 @@ func createTestMediaItemID(t *testing.T, ts *httptest.Server) string { mediaItemID := mediaItemResult["id"].(string) - // Cleanup: delete the library after the test t.Cleanup(func() { - deleteReq, _ := http.NewRequest("DELETE", ts.URL+"/api/libraries/"+libData, nil) - deleteReq.Header.Set("Authorization", "Bearer "+validToken) + deleteReq, _ := http.NewRequest("DELETE", setup.Server.URL+"/api/libraries/"+libData, nil) + deleteReq.Header.Set("Authorization", "Bearer "+setup.Token) httpClient.Do(deleteReq) }) diff --git a/cmd/server/tests/user_test.go b/cmd/server/tests/user_test.go index c9b5e03..75a3530 100644 --- a/cmd/server/tests/user_test.go +++ b/cmd/server/tests/user_test.go @@ -1,8 +1,8 @@ package main import ( - "bookhoard/internal/handlers" "bytes" + "context" "encoding/json" "net/http" "net/http/httptest" @@ -10,477 +10,600 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/crypto/bcrypt" ) -// TestUserProfileEndpoints tests user profile management endpoints -func TestUserProfileEndpoints(t *testing.T) { - userID := uuid.New() +func TestUpdateProfileSelfEdit(t *testing.T) { + t.Run("PUT /api/auth/profile - Update all fields successfully", func(t *testing.T) { + setup := setupTestServer(t) - t.Run("GET /api/auth/profile - Get profile without auth", func(t *testing.T) { - req := httptest.NewRequest("GET", "/api/auth/profile", nil) - rr := httptest.NewRecorder() + _ = createTestUserOnce(t, setup.DB) + token := setup.Token - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - authHeader := r.Header.Get("Authorization") - if authHeader == "" || !containsPrefix(authHeader, "Bearer ") { - w.WriteHeader(http.StatusUnauthorized) - w.Write([]byte(`{"message":"missing or malformed jwt"}`)) - return - } - - w.WriteHeader(http.StatusOK) - }) - - handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusUnauthorized, rr.Code) - }) - - t.Run("GET /api/auth/profile - Get profile with valid auth", func(t *testing.T) { - req := httptest.NewRequest("GET", "/api/auth/profile", nil) - req.Header.Set("Authorization", "Bearer valid-token") - rr := httptest.NewRecorder() - - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - profile := handlers.UserProfile{ - ID: userID.String(), - Email: "user@example.com", - Username: "testuser", - Role: "user", - } - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(profile) - }) - - handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusOK, rr.Code) - }) - - t.Run("PUT /api/auth/profile - Update profile with valid data", func(t *testing.T) { - payload := map[string]interface{}{ + updateData := map[string]interface{}{ + "username": "updateduser", + "email": "updated@example.com", "first_name": "Updated", "last_name": "Name", + "theme": "dracula", } - jsonData, _ := json.Marshal(payload) + jsonData, _ := json.Marshal(updateData) req := httptest.NewRequest("PUT", "/api/auth/profile", bytes.NewBuffer(jsonData)) req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer valid-token") - rr := httptest.NewRecorder() + req.Header.Set("Authorization", "Bearer "+token) - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Write([]byte(`{"message":"profile updated"}`)) - }) + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) - handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusOK, rr.Code) + assert.Equal(t, http.StatusOK, rec.Code) + + user, err := setup.DB.GetUserByUsername(context.Background(), "updateduser") + require.NoError(t, err) + assert.Equal(t, "updated@example.com", user.Email) + assert.Equal(t, "Updated", user.FirstName.String) + assert.Equal(t, "Name", user.LastName.String) + assert.Equal(t, "dracula", user.Theme.String) + }) + + t.Run("PUT /api/auth/profile - Update username conflict", func(t *testing.T) { + setup := setupTestServer(t) + + _ = createTestUserOnce(t, setup.DB) + regularUser := createRegularUserOnce(t, setup.DB) + token := loginUserWithCredentials(t, setup.Server, regularUser.Email, regularUser.Password) + + updateData := map[string]interface{}{ + "username": "testuser", + } + jsonData, _ := json.Marshal(updateData) + + req := httptest.NewRequest("PUT", "/api/auth/profile", bytes.NewBuffer(jsonData)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+token) + + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusConflict, rec.Code) + assert.Contains(t, rec.Body.String(), "username already taken") + }) + + t.Run("PUT /api/auth/profile - Update email conflict", func(t *testing.T) { + setup := setupTestServer(t) + + _ = createTestUserOnce(t, setup.DB) + regularUser := createRegularUserOnce(t, setup.DB) + token := loginUserWithCredentials(t, setup.Server, regularUser.Email, regularUser.Password) + + updateData := map[string]interface{}{ + "email": "testuser@example.com", + } + jsonData, _ := json.Marshal(updateData) + + req := httptest.NewRequest("PUT", "/api/auth/profile", bytes.NewBuffer(jsonData)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+token) + + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusConflict, rec.Code) + assert.Contains(t, rec.Body.String(), "email already taken") + }) + + t.Run("PUT /api/auth/profile - Regular user tries to set role", func(t *testing.T) { + setup := setupTestServer(t) + + _ = createTestUserOnce(t, setup.DB) + regularUser := createRegularUserOnce(t, setup.DB) + token := loginUserWithCredentials(t, setup.Server, regularUser.Email, regularUser.Password) + + updateData := map[string]interface{}{ + "role": "admin", + } + jsonData, _ := json.Marshal(updateData) + + req := httptest.NewRequest("PUT", "/api/auth/profile", bytes.NewBuffer(jsonData)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+token) + + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusOK, rec.Code) + + user, err := setup.DB.GetUser(context.Background(), uuidToPGType(regularUser.ID)) + require.NoError(t, err) + assert.Equal(t, "user", user.Role) + }) + + t.Run("PUT /api/auth/profile - Partial update (first name only)", func(t *testing.T) { + setup := setupTestServer(t) + + _ = createTestUserOnce(t, setup.DB) + token := setup.Token + + updateData := map[string]interface{}{ + "first_name": "OnlyFirstName", + } + jsonData, _ := json.Marshal(updateData) + + req := httptest.NewRequest("PUT", "/api/auth/profile", bytes.NewBuffer(jsonData)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+token) + + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusOK, rec.Code) + }) + + t.Run("PUT /api/auth/profile - No auth", func(t *testing.T) { + setup := setupTestServer(t) + + updateData := map[string]interface{}{ + "first_name": "NoAuth", + } + jsonData, _ := json.Marshal(updateData) + + req := httptest.NewRequest("PUT", "/api/auth/profile", bytes.NewBuffer(jsonData)) + req.Header.Set("Content-Type", "application/json") + + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusUnauthorized, rec.Code) }) } -// TestUserUpdateEndpoints tests user field update endpoints -func TestUserUpdateEndpoints(t *testing.T) { - t.Run("PUT /api/auth/email - Update email to existing email", func(t *testing.T) { - payload := handlers.UpdateEmailRequest{ - Email: "existing@example.com", - } - jsonData, _ := json.Marshal(payload) +func TestUpdateProfileAdminMode(t *testing.T) { + t.Run("PUT /api/auth/profile/:id - Admin update username", func(t *testing.T) { + setup := setupTestServer(t) - req := httptest.NewRequest("PUT", "/api/auth/email", bytes.NewBuffer(jsonData)) - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer valid-token") - rr := httptest.NewRecorder() + _ = createTestUserOnce(t, setup.DB) + token := setup.Token - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var req handlers.UpdateEmailRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(`{"error":"invalid request"}`)) - return - } + targetUser := createRegularUserOnce(t, setup.DB) - if req.Email == "existing@example.com" { - w.WriteHeader(http.StatusConflict) - w.Write([]byte(`{"error":"email already taken"}`)) - return - } - - w.WriteHeader(http.StatusOK) + newUsername := "updateduser" + jsonData, _ := json.Marshal(map[string]interface{}{ + "username": newUsername, }) - handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusConflict, rr.Code) + req := httptest.NewRequest("PUT", "/api/auth/profile/"+targetUser.ID.String(), bytes.NewBuffer(jsonData)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+token) + + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusOK, rec.Code) + + updatedUser, err := setup.DB.GetUserByUsername(context.Background(), newUsername) + require.NoError(t, err) + assert.Equal(t, newUsername, updatedUser.Username) }) - t.Run("PUT /api/auth/email - Update email with invalid format", func(t *testing.T) { - payload := handlers.UpdateEmailRequest{ - Email: "invalid-email", - } - jsonData, _ := json.Marshal(payload) + t.Run("PUT /api/auth/profile/:id - Admin promote user to admin", func(t *testing.T) { + setup := setupTestServer(t) - req := httptest.NewRequest("PUT", "/api/auth/email", bytes.NewBuffer(jsonData)) - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer valid-token") - rr := httptest.NewRecorder() + _ = createTestUserOnce(t, setup.DB) + token := setup.Token - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var req handlers.UpdateEmailRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(`{"error":"invalid request"}`)) - return - } + targetUser := createRegularUserOnce(t, setup.DB) - email := req.Email - if !contains(email, "@") || !contains(email, ".") { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(`{"error":"email is invalid"}`)) - return - } - - w.WriteHeader(http.StatusOK) + jsonData, _ := json.Marshal(map[string]interface{}{ + "role": "admin", }) - handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusBadRequest, rr.Code) + req := httptest.NewRequest("PUT", "/api/auth/profile/"+targetUser.ID.String(), bytes.NewBuffer(jsonData)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+token) + + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusOK, rec.Code) + + updatedUser, err := setup.DB.GetUser(context.Background(), uuidToPGType(targetUser.ID)) + require.NoError(t, err) + assert.Equal(t, "admin", updatedUser.Role) }) - t.Run("PUT /api/auth/email - Update email with empty value", func(t *testing.T) { - payload := handlers.UpdateEmailRequest{ - Email: "", - } - jsonData, _ := json.Marshal(payload) + t.Run("PUT /api/auth/profile/:id - Try to demote last admin", func(t *testing.T) { + setup := setupTestServer(t) - req := httptest.NewRequest("PUT", "/api/auth/email", bytes.NewBuffer(jsonData)) - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer valid-token") - rr := httptest.NewRecorder() + lastAdmin := createTestUserOnce(t, setup.DB) + token := setup.Token - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(`{"error":"email is required"}`)) + jsonData, _ := json.Marshal(map[string]interface{}{ + "role": "user", }) - handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusBadRequest, rr.Code) + req := httptest.NewRequest("PUT", "/api/auth/profile/"+lastAdmin.ID.String(), bytes.NewBuffer(jsonData)) + req.Header.Set("Authorization", "Bearer "+token) + + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusBadRequest, rec.Code) + assert.Contains(t, rec.Body.String(), "cannot demote the last admin") }) - t.Run("PUT /api/auth/username - Update username to existing username", func(t *testing.T) { - payload := handlers.UpdateUsernameRequest{ - Username: "existinguser", - } - jsonData, _ := json.Marshal(payload) + t.Run("PUT /api/auth/profile/:id - Non-admin tries update", func(t *testing.T) { + setup := setupTestServer(t) - req := httptest.NewRequest("PUT", "/api/auth/username", bytes.NewBuffer(jsonData)) - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer valid-token") - rr := httptest.NewRecorder() + targetAdmin := createTestUserOnce(t, setup.DB) + regularUser := createRegularUserOnce(t, setup.DB) + token := loginUserWithCredentials(t, setup.Server, regularUser.Email, regularUser.Password) - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var req handlers.UpdateUsernameRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(`{"error":"invalid request"}`)) - return - } - - if req.Username == "existinguser" { - w.WriteHeader(http.StatusConflict) - w.Write([]byte(`{"error":"username already taken"}`)) - return - } - - w.WriteHeader(http.StatusOK) + jsonData, _ := json.Marshal(map[string]interface{}{ + "username": "hacked", }) - handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusConflict, rr.Code) + req := httptest.NewRequest("PUT", "/api/auth/profile/"+targetAdmin.ID.String(), bytes.NewBuffer(jsonData)) + req.Header.Set("Authorization", "Bearer "+token) + + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusForbidden, rec.Code) }) - t.Run("PUT /api/auth/username - Update username with invalid length", func(t *testing.T) { - testCases := []struct { - name string - username string - }{ - {"Username too short", "ab"}, - {"Username too long", "thisusernameiswaytoolongandshouldfailvalidation"}, - } + t.Run("PUT /api/auth/profile/:id - Invalid role", func(t *testing.T) { + setup := setupTestServer(t) - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - payload := map[string]interface{}{ - "username": tc.username, - } - jsonData, _ := json.Marshal(payload) + _ = createTestUserOnce(t, setup.DB) + token := setup.Token - req := httptest.NewRequest("PUT", "/api/auth/username", bytes.NewBuffer(jsonData)) - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer valid-token") - rr := httptest.NewRecorder() + targetUser := createRegularUserOnce(t, setup.DB) - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(`{"error":"username must be between 3 and 50 characters"}`)) - }) + jsonData, _ := json.Marshal(map[string]interface{}{ + "role": "superadmin", + }) - handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusBadRequest, rr.Code) - }) - } + req := httptest.NewRequest("PUT", "/api/auth/profile/"+targetUser.ID.String(), bytes.NewBuffer(jsonData)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+token) + + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusBadRequest, rec.Code) + assert.Contains(t, rec.Body.String(), "invalid role") + }) +} + +func TestUpdatePasswordAdminMode(t *testing.T) { + t.Run("PUT /api/auth/password/:id - Admin reset password", func(t *testing.T) { + setup := setupTestServer(t) + + _ = createTestUserOnce(t, setup.DB) + token := setup.Token + + targetUser := createRegularUserOnce(t, setup.DB) + + newPassword := "NewPassword123!" + jsonData, _ := json.Marshal(map[string]interface{}{ + "new_password": newPassword, + "confirm_password": newPassword, + }) + + req := httptest.NewRequest("PUT", "/api/auth/password/"+targetUser.ID.String(), bytes.NewBuffer(jsonData)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+token) + + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusOK, rec.Code) + + loginData, _ := json.Marshal(map[string]interface{}{ + "login": targetUser.Username, + "password": newPassword, + }) + + loginReq := httptest.NewRequest("POST", "/api/auth/login", bytes.NewBuffer(loginData)) + loginReq.Header.Set("Content-Type", "application/json") + + loginRec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(loginRec, loginReq) + + assert.Equal(t, http.StatusOK, loginRec.Code) }) - t.Run("PUT /api/auth/password - Update password with wrong current password", func(t *testing.T) { - payload := map[string]interface{}{ + t.Run("PUT /api/auth/password/:id - Non-admin tries reset", func(t *testing.T) { + setup := setupTestServer(t) + + targetAdmin := createTestUserOnce(t, setup.DB) + regularUser := createRegularUserOnce(t, setup.DB) + token := loginUserWithCredentials(t, setup.Server, regularUser.Email, regularUser.Password) + + jsonData, _ := json.Marshal(map[string]interface{}{ + "new_password": "hacked", + "confirm_password": "hacked", + }) + + req := httptest.NewRequest("PUT", "/api/auth/password/"+targetAdmin.ID.String(), bytes.NewBuffer(jsonData)) + req.Header.Set("Authorization", "Bearer "+token) + + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusForbidden, rec.Code) + }) + + t.Run("PUT /api/auth/password/:id - Mismatched passwords", func(t *testing.T) { + setup := setupTestServer(t) + + _ = createTestUserOnce(t, setup.DB) + token := setup.Token + + targetUser := createRegularUserOnce(t, setup.DB) + + jsonData, _ := json.Marshal(map[string]interface{}{ + "new_password": "password1", + "confirm_password": "password2", + }) + + req := httptest.NewRequest("PUT", "/api/auth/password/"+targetUser.ID.String(), bytes.NewBuffer(jsonData)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+token) + + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusBadRequest, rec.Code) + assert.Contains(t, rec.Body.String(), "passwords do not match") + }) + + t.Run("PUT /api/auth/password/:id - Invalid password format", func(t *testing.T) { + setup := setupTestServer(t) + + _ = createTestUserOnce(t, setup.DB) + token := setup.Token + + targetUser := createRegularUserOnce(t, setup.DB) + + jsonData, _ := json.Marshal(map[string]interface{}{ + "new_password": "123", + "confirm_password": "123", + }) + + req := httptest.NewRequest("PUT", "/api/auth/password/"+targetUser.ID.String(), bytes.NewBuffer(jsonData)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+token) + + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusBadRequest, rec.Code) + }) +} + +func TestDeleteUserConsolidated(t *testing.T) { + t.Run("DELETE /api/auth/profile - User deletes self successfully", func(t *testing.T) { + setup := setupTestServer(t) + + _ = createTestUserOnce(t, setup.DB) + + selfDeletingUser := createRegularUserOnce(t, setup.DB) + token := loginUserWithCredentials(t, setup.Server, selfDeletingUser.Email, selfDeletingUser.Password) + + req := httptest.NewRequest("DELETE", "/api/auth/profile", nil) + req.Header.Set("Authorization", "Bearer "+token) + + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusOK, rec.Code) + + _, err := setup.DB.GetUser(context.Background(), uuidToPGType(selfDeletingUser.ID)) + assert.Error(t, err) + }) + + t.Run("DELETE /api/auth/profile - Delete without auth", func(t *testing.T) { + setup := setupTestServer(t) + + req := httptest.NewRequest("DELETE", "/api/auth/profile", nil) + + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusUnauthorized, rec.Code) + }) + + t.Run("DELETE /api/auth/profile - Last admin tries self-delete", func(t *testing.T) { + setup := setupTestServer(t) + + _ = createTestUserOnce(t, setup.DB) + token := setup.Token + + req := httptest.NewRequest("DELETE", "/api/auth/profile", nil) + req.Header.Set("Authorization", "Bearer "+token) + + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusBadRequest, rec.Code) + assert.Contains(t, rec.Body.String(), "cannot delete the last admin") + }) + + t.Run("DELETE /api/auth/profile/:id - Admin deletes user", func(t *testing.T) { + setup := setupTestServer(t) + + _ = createTestUserOnce(t, setup.DB) + token := setup.Token + + targetUser := createRegularUserOnce(t, setup.DB) + + req := httptest.NewRequest("DELETE", "/api/auth/profile/"+targetUser.ID.String(), nil) + req.Header.Set("Authorization", "Bearer "+token) + + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusOK, rec.Code) + + _, err := setup.DB.GetUser(context.Background(), uuidToPGType(targetUser.ID)) + assert.Error(t, err) + }) + + t.Run("DELETE /api/auth/profile/:id - Admin tries delete last admin", func(t *testing.T) { + setup := setupTestServer(t) + + _ = createTestUserOnce(t, setup.DB) + token := setup.Token + + users, err := setup.DB.ListUsers(context.Background()) + require.NoError(t, err) + var lastAdminID string + for _, u := range users { + if u.Role == "admin" { + lastAdminID = uuid.UUID(u.ID.Bytes).String() + break + } + } + + req := httptest.NewRequest("DELETE", "/api/auth/profile/"+lastAdminID, nil) + req.Header.Set("Authorization", "Bearer "+token) + + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusBadRequest, rec.Code) + assert.Contains(t, rec.Body.String(), "cannot delete the last admin") + }) + + t.Run("DELETE /api/auth/profile/:id - Non-admin tries delete", func(t *testing.T) { + setup := setupTestServer(t) + + _ = createTestUserOnce(t, setup.DB) + regularUser := createRegularUserOnce(t, setup.DB) + token := loginUserWithCredentials(t, setup.Server, regularUser.Email, regularUser.Password) + + users, err := setup.DB.ListUsers(context.Background()) + require.NoError(t, err) + var adminID string + for _, u := range users { + if u.Role == "admin" { + adminID = uuid.UUID(u.ID.Bytes).String() + break + } + } + + req := httptest.NewRequest("DELETE", "/api/auth/profile/"+adminID, nil) + req.Header.Set("Authorization", "Bearer "+token) + + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusForbidden, rec.Code) + }) + + t.Run("DELETE /api/auth/profile/:id - Delete non-existent user", func(t *testing.T) { + setup := setupTestServer(t) + + _ = createTestUserOnce(t, setup.DB) + token := setup.Token + + fakeUUID := uuid.New() + req := httptest.NewRequest("DELETE", "/api/auth/profile/"+fakeUUID.String(), nil) + req.Header.Set("Authorization", "Bearer "+token) + + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusNotFound, rec.Code) + }) +} + +func TestUpdatePasswordSelfChange(t *testing.T) { + t.Run("PUT /api/auth/password - Successful password change", func(t *testing.T) { + setup := setupTestServer(t) + + _ = createTestUserOnce(t, setup.DB) + token := setup.Token + + newPassword := "NewTest@Pass123!" + jsonData, _ := json.Marshal(map[string]interface{}{ + "current_password": "Test@Pass123!", + "new_password": newPassword, + "confirm_password": newPassword, + }) + + req := httptest.NewRequest("PUT", "/api/auth/password", bytes.NewBuffer(jsonData)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+token) + + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusOK, rec.Code) + + loginData, _ := json.Marshal(map[string]interface{}{ + "login": "testuser", + "password": newPassword, + }) + + loginReq := httptest.NewRequest("POST", "/api/auth/login", bytes.NewBuffer(loginData)) + loginReq.Header.Set("Content-Type", "application/json") + + loginRec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(loginRec, loginReq) + + assert.Equal(t, http.StatusOK, loginRec.Code) + }) + + t.Run("PUT /api/auth/password - Wrong current password", func(t *testing.T) { + setup := setupTestServer(t) + + _ = createTestUserOnce(t, setup.DB) + token := setup.Token + + jsonData, _ := json.Marshal(map[string]interface{}{ "current_password": "wrongpassword", - "new_password": "newpassword123", - "confirm_password": "newpassword123", - } - jsonData, _ := json.Marshal(payload) + "new_password": "NewTest@Pass123!", + "confirm_password": "NewTest@Pass123!", + }) req := httptest.NewRequest("PUT", "/api/auth/password", bytes.NewBuffer(jsonData)) req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer valid-token") - rr := httptest.NewRecorder() + req.Header.Set("Authorization", "Bearer "+token) - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusUnauthorized) - w.Write([]byte(`{"error":"current password is incorrect"}`)) - }) + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) - handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusUnauthorized, rr.Code) + assert.Equal(t, http.StatusUnauthorized, rec.Code) + assert.Contains(t, rec.Body.String(), "current password is incorrect") }) - t.Run("PUT /api/auth/password - Update password with mismatched passwords", func(t *testing.T) { - payload := map[string]interface{}{ - "current_password": "correctpassword", - "new_password": "newpassword123", - "confirm_password": "differentpassword", - } - jsonData, _ := json.Marshal(payload) + t.Run("PUT /api/auth/password - Missing current password", func(t *testing.T) { + setup := setupTestServer(t) + + _ = createTestUserOnce(t, setup.DB) + token := setup.Token + + jsonData, _ := json.Marshal(map[string]interface{}{ + "new_password": "NewTest@Pass123!", + "confirm_password": "NewTest@Pass123!", + }) req := httptest.NewRequest("PUT", "/api/auth/password", bytes.NewBuffer(jsonData)) req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer valid-token") - rr := httptest.NewRecorder() + req.Header.Set("Authorization", "Bearer "+token) - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(`{"error":"new passwords do not match"}`)) - }) + rec := httptest.NewRecorder() + setup.Server.Config.Handler.ServeHTTP(rec, req) - handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusBadRequest, rr.Code) - }) - - t.Run("PUT /api/auth/password - Update password with too short new password", func(t *testing.T) { - payload := map[string]interface{}{ - "current_password": "correctpassword", - "new_password": "short", - "confirm_password": "short", - } - jsonData, _ := json.Marshal(payload) - - req := httptest.NewRequest("PUT", "/api/auth/password", bytes.NewBuffer(jsonData)) - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer valid-token") - rr := httptest.NewRecorder() - - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(`{"error":"password must be at least 6 characters"}`)) - }) - - handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusBadRequest, rr.Code) - }) - - t.Run("PUT /api/auth/theme - Update theme", func(t *testing.T) { - payload := map[string]interface{}{ - "theme": "tokyo-night", - } - jsonData, _ := json.Marshal(payload) - - req := httptest.NewRequest("PUT", "/api/auth/theme", bytes.NewBuffer(jsonData)) - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer valid-token") - rr := httptest.NewRecorder() - - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Write([]byte(`{"message":"theme updated successfully"}`)) - }) - - handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusOK, rr.Code) - }) - - t.Run("PUT /api/auth/theme - Update theme with empty value", func(t *testing.T) { - payload := map[string]interface{}{ - "theme": "", - } - jsonData, _ := json.Marshal(payload) - - req := httptest.NewRequest("PUT", "/api/auth/theme", bytes.NewBuffer(jsonData)) - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer valid-token") - rr := httptest.NewRecorder() - - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(`{"error":"theme is required"}`)) - }) - - handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusBadRequest, rr.Code) + assert.Equal(t, http.StatusBadRequest, rec.Code) + assert.Contains(t, rec.Body.String(), "current password required") }) } -// TestAccountDeletion tests account deletion scenarios -func TestAccountDeletion(t *testing.T) { - userID := uuid.New() - - t.Run("DELETE /api/auth/account - Delete without auth", func(t *testing.T) { - req := httptest.NewRequest("DELETE", "/api/auth/account", nil) - rr := httptest.NewRecorder() - - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - authHeader := r.Header.Get("Authorization") - if authHeader == "" { - w.WriteHeader(http.StatusUnauthorized) - w.Write([]byte(`{"message":"missing or malformed jwt"}`)) - return - } - - w.WriteHeader(http.StatusOK) - }) - - handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusUnauthorized, rr.Code) - }) - - t.Run("DELETE /api/auth/account - Delete as last admin", func(t *testing.T) { - req := httptest.NewRequest("DELETE", "/api/auth/account", nil) - req.Header.Set("Authorization", "Bearer admin-token") - req.Header.Set("X-User-Role", "admin") - req.Header.Set("X-Admin-Count", "1") - rr := httptest.NewRecorder() - - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - adminCount := r.Header.Get("X-Admin-Count") - if adminCount == "1" { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(`{"error":"cannot delete the last admin account"}`)) - return - } - - w.WriteHeader(http.StatusOK) - }) - - handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusBadRequest, rr.Code) - }) - - t.Run("DELETE /api/auth/account - Delete successfully", func(t *testing.T) { - req := httptest.NewRequest("DELETE", "/api/auth/account", nil) - req.Header.Set("Authorization", "Bearer valid-token") - rr := httptest.NewRecorder() - - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Write([]byte(`{"message":"account deleted successfully"}`)) - }) - - handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusOK, rr.Code) - }) - - t.Run("DELETE /api/auth/account - Admin delete another user", func(t *testing.T) { - req := httptest.NewRequest("DELETE", "/api/auth/account?user_id="+userID.String(), nil) - req.Header.Set("Authorization", "Bearer admin-token") - req.Header.Set("X-User-Role", "admin") - rr := httptest.NewRecorder() - - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - userRole := r.Header.Get("X-User-Role") - if userRole != "admin" { - w.WriteHeader(http.StatusForbidden) - w.Write([]byte(`{"error":"admin access required"}`)) - return - } - - w.WriteHeader(http.StatusOK) - w.Write([]byte(`{"message":"user account deleted successfully"}`)) - }) - - handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusOK, rr.Code) - }) - - t.Run("DELETE /api/auth/account - Non-admin tries to delete another user", func(t *testing.T) { - req := httptest.NewRequest("DELETE", "/api/auth/account?user_id="+userID.String(), nil) - req.Header.Set("Authorization", "Bearer user-token") - req.Header.Set("X-User-Role", "user") - rr := httptest.NewRecorder() - - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - userRole := r.Header.Get("X-User-Role") - if userRole != "admin" { - w.WriteHeader(http.StatusForbidden) - w.Write([]byte(`{"error":"admin access required"}`)) - return - } - - w.WriteHeader(http.StatusOK) - }) - - handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusForbidden, rr.Code) - }) -} - -// TestAdminOnlyEndpoints tests admin-only endpoints -func TestAdminOnlyEndpoints(t *testing.T) { - t.Run("GET /api/auth/users - List users without admin role", func(t *testing.T) { - req := httptest.NewRequest("GET", "/api/auth/users", nil) - req.Header.Set("Authorization", "Bearer user-token") - req.Header.Set("X-User-Role", "user") - rr := httptest.NewRecorder() - - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - userRole := r.Header.Get("X-User-Role") - if userRole != "admin" { - w.WriteHeader(http.StatusForbidden) - w.Write([]byte(`{"error":"admin access required"}`)) - return - } - - w.WriteHeader(http.StatusOK) - }) - - handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusForbidden, rr.Code) - }) - - t.Run("GET /api/auth/users - List users with admin role", func(t *testing.T) { - req := httptest.NewRequest("GET", "/api/auth/users", nil) - req.Header.Set("Authorization", "Bearer admin-token") - req.Header.Set("X-User-Role", "admin") - rr := httptest.NewRecorder() - - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - users := []map[string]interface{}{ - { - "id": uuid.New().String(), - "email": "user@example.com", - "username": "testuser", - "role": "user", - }, - } - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(users) - }) - - handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusOK, rr.Code) - }) +func verifyPasswordHash(t *testing.T, hashedPassword, password string) { + err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) + assert.NoError(t, err, "Password should match hash") } diff --git a/cmd/server/tests/websocket_test.go b/cmd/server/tests/websocket_test.go index 58b98e0..c95fb2c 100644 --- a/cmd/server/tests/websocket_test.go +++ b/cmd/server/tests/websocket_test.go @@ -20,7 +20,7 @@ import ( setup := setupTestServer(t) // Get JWT token for a test user -token := loginTestUser(t, setup.Server, setup.DB) +token := setup.Token // Connect to WebSocket endpoint wsURL := strings.Replace(setup.Server.URL, "http", "ws", 1) + "/ws/sync?token=" + token @@ -83,7 +83,7 @@ func TestWebSocketDeviceAuth(t *testing.T) { setup := setupTestServer(t) // Get JWT token -token := loginTestUser(t, setup.Server, setup.DB) +token := setup.Token // Create a test media item userID := getTestUserID(t, setup.DB) @@ -144,7 +144,7 @@ assert.Equal(t, "web", sourceDevice["type"]) /* func TestWebSocketPingPong(t *testing.T) { setup := setupTestServer(t) -token := loginTestUser(t, setup.Server, setup.DB) +token := setup.Token wsURL := strings.Replace(setup.Server.URL, "http", "ws", 1) + "/ws/sync?token=" + token ws, _, err := websocket.DefaultDialer.Dial(wsURL, nil) @@ -176,7 +176,7 @@ if err == nil { /* func TestWebSocketConnectionLimit(t *testing.T) { setup := setupTestServer(t) - token := loginTestUser(t, setup.Server, setup.DB) + token := setup.Token // Create multiple connections connections := make([]*websocket.Conn, 5)