Files
bookhoard/cmd/server/tests/queue_test.go
T
john-okeefe 8e054bd149 fix: update test files for token handling and response parsing
- Update callers of createTestMediaItemID to not pass token
- Fix loginAdminUser to delete/recreate admin user for consistent state
- Fix TestListAllQueueItems_Admin to parse response as map with 'items' key
- Remove unused token variables from tests
- Update device_test.go with admin password hash constant
2026-02-14 00:12:28 -05:00

260 lines
8.7 KiB
Go

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)
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 map[string]interface{}
json.Unmarshal(rec.Body.Bytes(), &response)
assert.NotNil(t, response["items"], "Should have queue items")
}
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 := setup.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 := setup.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 := setup.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 {
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
}