package main import ( "bookhoard/internal/database" "bytes" "context" "encoding/json" "fmt" "net/http" "net/http/httptest" "testing" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgtype" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestListAllQueueItems_Admin(t *testing.T) { setup := setupTestServer(t) token := loginAdminUser(t, setup.Server, setup.DB) db) req := httptest.NewRequest("GET", "/api/queue/items", nil) req.Header.Set("Authorization", "Bearer "+token) rec := httptest.NewRecorder() setup.Server.Config.Handler.ServeHTTP(rec, req) assert.Equal(t, http.StatusOK, rec.Code, "Should list all queue items") var response []interface{} json.Unmarshal(rec.Body.Bytes(), &response) assert.NotNil(t, response, "Should have queue items array") } func TestGetDeviceQueueStats(t *testing.T) { setup := setupTestServer(t) token := loginTestUser(t, setup.Server, setup.DB) userID := getTestUserID(t, setup.DB) deviceToken := fmt.Sprintf("dev_%s", uuid.New().String()) device, err := db.CreateDevice(context.Background(), database.CreateDeviceParams{ UserID: pgtype.UUID{Bytes: [16]byte(userID), Valid: true}, DeviceName: "Test Device", DeviceType: "koreader", DeviceIdentifier: "test-device-123", AuthToken: deviceToken, SyncEnabled: pgtype.Bool{Bool: true, Valid: true}, AutoSync: pgtype.Bool{Bool: true, Valid: true}, DeviceMetadata: []byte("{}"), }) require.NoError(t, err, "Should create device") deviceIDBytes := device.ID.Bytes[0:16] deviceID, err := uuid.FromBytes(deviceIDBytes) require.NoError(t, err, "Should parse device ID") req := httptest.NewRequest("GET", fmt.Sprintf("/api/queue/devices/%s/stats", deviceID.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, "Should get device queue stats") var response map[string]interface{} json.Unmarshal(rec.Body.Bytes(), &response) assert.Equal(t, deviceID.String(), response["device_id"], "Should match device ID") } func TestListDeviceQueueItems(t *testing.T) { setup := setupTestServer(t) token := loginTestUser(t, setup.Server, setup.DB) userID := getTestUserID(t, setup.DB) deviceToken := fmt.Sprintf("dev_%s", uuid.New().String()) device, err := db.CreateDevice(context.Background(), database.CreateDeviceParams{ UserID: pgtype.UUID{Bytes: [16]byte(userID), Valid: true}, DeviceName: "Test Device", DeviceType: "koreader", DeviceIdentifier: "test-device-123", AuthToken: deviceToken, SyncEnabled: pgtype.Bool{Bool: true, Valid: true}, AutoSync: pgtype.Bool{Bool: true, Valid: true}, DeviceMetadata: []byte("{}"), }) require.NoError(t, err, "Should create device") deviceIDBytes := device.ID.Bytes[0:16] deviceID, err := uuid.FromBytes(deviceIDBytes) require.NoError(t, err, "Should parse device ID") req := httptest.NewRequest("GET", fmt.Sprintf("/api/queue/devices/%s/items", deviceID.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, "Should list device queue items") var response []interface{} json.Unmarshal(rec.Body.Bytes(), &response) assert.NotNil(t, response, "Should have queue items array") } func TestRetryQueueItem(t *testing.T) { setup := setupTestServer(t) token := loginTestUser(t, setup.Server, setup.DB) itemID := uuid.New().String() req := httptest.NewRequest("POST", fmt.Sprintf("/api/queue/items/%s/retry", itemID), nil) req.Header.Set("Authorization", "Bearer "+token) rec := httptest.NewRecorder() setup.Server.Config.Handler.ServeHTTP(rec, req) assert.Equal(t, http.StatusOK, rec.Code, "Should retry queue item") } func TestDeleteQueueItem(t *testing.T) { setup := setupTestServer(t) token := loginTestUser(t, setup.Server, setup.DB) itemID := uuid.New().String() req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/queue/items/%s", itemID), nil) req.Header.Set("Authorization", "Bearer "+token) rec := httptest.NewRecorder() setup.Server.Config.Handler.ServeHTTP(rec, req) assert.Equal(t, http.StatusOK, rec.Code, "Should delete queue item") } func TestClearDeviceQueue(t *testing.T) { setup := setupTestServer(t) token := loginTestUser(t, setup.Server, setup.DB) userID := getTestUserID(t, setup.DB) deviceToken := fmt.Sprintf("dev_%s", uuid.New().String()) device, err := db.CreateDevice(context.Background(), database.CreateDeviceParams{ UserID: pgtype.UUID{Bytes: [16]byte(userID), Valid: true}, DeviceName: "Test Device", DeviceType: "koreader", DeviceIdentifier: "test-device-123", AuthToken: deviceToken, SyncEnabled: pgtype.Bool{Bool: true, Valid: true}, AutoSync: pgtype.Bool{Bool: true, Valid: true}, DeviceMetadata: []byte("{}"), }) require.NoError(t, err, "Should create device") deviceIDBytes := device.ID.Bytes[0:16] deviceID, err := uuid.FromBytes(deviceIDBytes) require.NoError(t, err, "Should parse device ID") req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/queue/devices/%s/clear", deviceID.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, "Should clear device queue") } func TestQueueEndpoints_Unauthorized(t *testing.T) { setup := setupTestServer(t) tests := []struct { name string method string endpoint string body []byte expectedStatus int }{ {"ListAllQueueItems", "GET", "/api/queue/items", nil, http.StatusUnauthorized}, {"GetDeviceQueueStats", "GET", "/api/queue/devices/test-device-id/stats", nil, http.StatusUnauthorized}, {"ListDeviceQueueItems", "GET", "/api/queue/devices/test-device-id/items", nil, http.StatusUnauthorized}, {"RetryQueueItem", "POST", "/api/queue/items/test-item-id/retry", nil, http.StatusUnauthorized}, {"DeleteQueueItem", "DELETE", "/api/queue/items/test-item-id", nil, http.StatusUnauthorized}, {"ClearDeviceQueue", "DELETE", "/api/queue/devices/test-device-id/clear", nil, http.StatusUnauthorized}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { req := httptest.NewRequest(tt.method, tt.endpoint, bytes.NewReader(tt.body)) rec := httptest.NewRecorder() setup.Server.Config.Handler.ServeHTTP(rec, req) assert.Equal(t, tt.expectedStatus, rec.Code, "Should require authentication") }) } } func loginAdminUser(t *testing.T, ts *httptest.Server, db *database.Queries) string { passwordHash := "$2a$10$rKvZ.HZx3lLJ6IQCpH1lOukQ/xU8j5cH8mYhPY5YGfXllq5hG8y0Ou" adminUser, err := db.CreateUser(context.Background(), 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}, }) if err == nil { 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!") } user, err := db.GetUserByEmail(context.Background(), "admin@example.com") if err == nil { userUUID, err := uuid.FromBytes(user.ID.Bytes[:]) require.NoError(t, err, "Should parse admin user UUID") return loginUserWithID(t, ts, db, userUUID, "admin@example.com", "Test@Pass123!") } _, err = db.ListUsers(context.Background()) if err == nil { return loginTestUser(t, setup.Server, setup.DB) } loginRequest := map[string]interface{}{ "login": "testuser@example.com", "password": "Test@Pass123!", } body, _ := json.Marshal(loginRequest) req, _ := http.NewRequest("POST", setup.Server.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() 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") return token } 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", setup.Server.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 }