Files
Crussell/backend/handlers/admin/users_test.go
T
2026-03-02 10:16:55 +00:00

464 lines
16 KiB
Go

//go:build test
// +build test
// Package admin contains tests for admin user management endpoints.
//
// Test Coverage:
// - ListAdminUsersHandler: GET /api/admin/users - List all users with pagination
// - GetAdminUserHandler: GET /api/admin/users/{id} - Get single user details
// - GetEligiblePatchTestServicesHandler: GET /api/admin/users/{id}/eligible-patch-tests
// - AddPatchTestHandler: POST /api/admin/users/{id}/patch-tests - Add patch test record
// - RequireAdmin middleware: All endpoints require admin role (403 for non-admins)
//
// Database State: Tests create and clean up users in the users table.
package admin
//go:build test
// +build test
package admin
import (
"context"
"encoding/json"
"net/http"
"testing"
"crussell/db"
"crussell/handlers/user"
"crussell/mw"
)
func TestAdminUsers_List(t *testing.T) {
cleanup := setupTestDB(t)
defer cleanup()
// Create test users
_, err := db.DB.Exec(context.Background(), `
INSERT INTO users (n_first_name, n_last_name, email, phone, date_of_birth, password_hash, account_role, account_type)
VALUES
('Alice', 'Smith', 'alice@test.com', '+447123456789', '1990-01-01', 'hash1', 'admin', 'email'),
('Bob', 'Jones', 'bob@test.com', '+447123456789', '1990-01-01', 'hash2', 'verified_email', 'email'),
('Charlie', 'Brown', 'charlie@test.com', '+447123456789', '1990-01-01', 'hash3', 'verified_email', 'email')
`)
if err != nil {
t.Fatalf("failed to create users: %v", err)
}
handler := http.HandlerFunc(user.ListAdminUsersHandler)
w := makeAdminRequest(handler, "GET", "/api/admin/users", nil)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
}
var response user.UserListResponse
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
t.Fatalf("failed to unmarshal response: %v", err)
}
if response.Total != 3 {
t.Errorf("expected 3 users, got %d", response.Total)
}
if len(response.Users) != 3 {
t.Errorf("expected 3 users in list, got %d", len(response.Users))
}
}
func TestAdminUsers_Get(t *testing.T) {
cleanup := setupTestDB(t)
defer cleanup()
// Create test user
var userID string
err := db.DB.QueryRow(context.Background(), `
INSERT INTO users (n_first_name, n_last_name, email, phone, date_of_birth, password_hash, account_role, account_type)
VALUES ('Test', 'User', 'testuser@test.com', '+447123456789', '1990-01-01', 'hash', 'verified_email', 'email')
RETURNING id
`).Scan(&userID)
if err != nil {
t.Fatalf("failed to create user: %v", err)
}
handler := http.HandlerFunc(user.GetAdminUserHandler)
w := makeAdminRequest(handler, "GET", "/api/admin/users/"+userID, nil)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
}
var response user.AdminUserDetail
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
t.Fatalf("failed to unmarshal response: %v", err)
}
if response.ID != userID {
t.Errorf("expected user ID %s, got %s", userID, response.ID)
}
if response.AccountType != "email" {
t.Errorf("expected account type 'email', got %s", response.AccountType)
}
}
func TestAdminUsers_Get_NotFound(t *testing.T) {
cleanup := setupTestDB(t)
defer cleanup()
handler := http.HandlerFunc(user.GetAdminUserHandler)
// Use 12-char or less ID to avoid CHAR(12) constraint error
w := makeAdminRequest(handler, "GET", "/api/admin/users/nonexist", nil)
if w.Code != http.StatusNotFound {
t.Errorf("expected status 404, got %d", w.Code)
}
}
func TestAdminUsers_PatchTests_Eligible(t *testing.T) {
cleanup := setupTestDB(t)
defer cleanup()
// Create test user
var userID string
err := db.DB.QueryRow(context.Background(), `
INSERT INTO users (n_first_name, n_last_name, email, phone, date_of_birth, password_hash, account_role, account_type)
VALUES ('Test', 'User', 'testuser@test.com', '+447123456789', '1990-01-01', 'hash', 'verified_email', 'email')
RETURNING id
`).Scan(&userID)
if err != nil {
t.Fatalf("failed to create user: %v", err)
}
// Create services - some with patch test, some without
_, err = db.DB.Exec(context.Background(), `
INSERT INTO services (name, description, price, duration_minutes, is_active, minimum_age_required)
VALUES
('Basic Manicure', 'Basic manicure', 25.00, 30, true, 0),
('Gel Polish Full Set', 'Gel polish service', 45.00, 60, true, 16),
('Luxury Gel Manicure', 'Luxury gel', 55.00, 75, true, 16),
('Inactive Service', 'Inactive', 30.00, 30, false, 16)
`)
if err != nil {
t.Fatalf("failed to create services: %v", err)
}
// Get service IDs for patch test services
var gelPolishID, luxuryGelID string
err = db.DB.QueryRow(context.Background(), "SELECT id FROM services WHERE name = 'Gel Polish Full Set'").Scan(&gelPolishID)
if err != nil {
t.Fatalf("failed to get gel polish service ID: %v", err)
}
err = db.DB.QueryRow(context.Background(), "SELECT id FROM services WHERE name = 'Luxury Gel Manicure'").Scan(&luxuryGelID)
if err != nil {
t.Fatalf("failed to get luxury gel service ID: %v", err)
}
// Create patch tests that link to these services
_, err = db.DB.Exec(context.Background(), `
INSERT INTO patch_tests (name, description, notice_duration_hours, expiry_months, service_ids)
VALUES ('Gel Allergy Test', 'Patch test for gel products', 24, 6, $1)
`, []string{gelPolishID})
if err != nil {
t.Fatalf("failed to create patch test for gel polish: %v", err)
}
_, err = db.DB.Exec(context.Background(), `
INSERT INTO patch_tests (name, description, notice_duration_hours, expiry_months, service_ids)
VALUES ('Luxury Gel Test', 'Patch test for luxury gel', 24, 6, $1)
`, []string{luxuryGelID})
if err != nil {
t.Fatalf("failed to create patch test for luxury gel: %v", err)
}
handler := http.HandlerFunc(user.GetEligiblePatchTestServicesHandler)
w := makeAdminRequest(handler, "GET", "/api/admin/users/"+userID+"/patch-tests/eligible", nil)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
}
var response []user.ServiceForPatchTest
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
t.Fatalf("failed to unmarshal response: %v", err)
}
// Should return 2 services (the two with patch tests that are active)
if len(response) != 2 {
t.Errorf("expected 2 eligible services, got %d. body: %s", len(response), w.Body.String())
}
}
func TestAdminUsers_PatchTests_Eligible_WithExisting(t *testing.T) {
cleanup := setupTestDB(t)
defer cleanup()
// Create test user
var userID string
err := db.DB.QueryRow(context.Background(), `
INSERT INTO users (n_first_name, n_last_name, email, phone, date_of_birth, password_hash, account_role, account_type)
VALUES ('Test', 'User', 'testuser@test.com', '+447123456789', '1990-01-01', 'hash', 'verified_email', 'email')
RETURNING id
`).Scan(&userID)
if err != nil {
t.Fatalf("failed to create user: %v", err)
}
// Create services
var serviceID1, serviceID2 string
err = db.DB.QueryRow(context.Background(), `
INSERT INTO services (name, description, price, duration_minutes, is_active, minimum_age_required)
VALUES ('Gel Polish Full Set', 'Gel polish service', 45.00, 60, true, 16)
RETURNING id
`).Scan(&serviceID1)
if err != nil {
t.Fatalf("failed to create service 1: %v", err)
}
err = db.DB.QueryRow(context.Background(), `
INSERT INTO services (name, description, price, duration_minutes, is_active, minimum_age_required)
VALUES ('Luxury Gel Manicure', 'Luxury gel', 55.00, 75, true, 16)
RETURNING id
`).Scan(&serviceID2)
if err != nil {
t.Fatalf("failed to create service 2: %v", err)
}
// Create patch tests
var patchTestID1 string
err = db.DB.QueryRow(context.Background(), `
INSERT INTO patch_tests (name, description, notice_duration_hours, expiry_months, service_ids)
VALUES ('Gel Allergy Test', 'Patch test for gel products', 24, 6, $1)
RETURNING id
`, []string{serviceID1}).Scan(&patchTestID1)
if err != nil {
t.Fatalf("failed to create patch test 1: %v", err)
}
_, err = db.DB.Exec(context.Background(), `
INSERT INTO patch_tests (name, description, notice_duration_hours, expiry_months, service_ids)
VALUES ('Luxury Gel Test', 'Patch test for luxury gel', 24, 6, $1)
`, []string{serviceID2})
if err != nil {
t.Fatalf("failed to create patch test 2: %v", err)
}
// Add one patch test for the user (valid - within expiry)
_, err = db.DB.Exec(context.Background(), `
INSERT INTO user_patch_tests (user_id, patch_test_id, tested_at)
VALUES ($1, $2, NOW() - INTERVAL '2 months')
`, userID, patchTestID1)
if err != nil {
t.Fatalf("failed to add patch test: %v", err)
}
handler := http.HandlerFunc(user.GetEligiblePatchTestServicesHandler)
w := makeAdminRequest(handler, "GET", "/api/admin/users/"+userID+"/patch-tests/eligible", nil)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
}
var response []user.ServiceForPatchTest
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
t.Fatalf("failed to unmarshal response: %v", err)
}
// Should return only 1 service (the one not already added)
if len(response) != 1 {
t.Errorf("expected 1 eligible service, got %d. body: %s", len(response), w.Body.String())
}
if len(response) > 0 && response[0].ID != serviceID2 {
t.Errorf("expected service %s, got %s", serviceID2, response[0].ID)
}
}
func TestAdminUsers_AddPatchTest(t *testing.T) {
cleanup := setupTestDB(t)
defer cleanup()
// Create test user
var userID string
err := db.DB.QueryRow(context.Background(), `
INSERT INTO users (n_first_name, n_last_name, email, phone, date_of_birth, password_hash, account_role, account_type)
VALUES ('Test', 'User', 'testuser@test.com', '+447123456789', '1990-01-01', 'hash', 'verified_email', 'email')
RETURNING id
`).Scan(&userID)
if err != nil {
t.Fatalf("failed to create user: %v", err)
}
// Create a service
var serviceID string
err = db.DB.QueryRow(context.Background(), `
INSERT INTO services (name, description, price, duration_minutes, is_active, minimum_age_required)
VALUES ('Gel Polish Full Set', 'Gel polish service', 45.00, 60, true, 16)
RETURNING id
`).Scan(&serviceID)
if err != nil {
t.Fatalf("failed to create service: %v", err)
}
// Create a patch test that links to this service
var patchTestID string
err = db.DB.QueryRow(context.Background(), `
INSERT INTO patch_tests (name, description, notice_duration_hours, expiry_months, service_ids)
VALUES ('Gel Allergy Test', 'Patch test for gel products', 24, 6, $1)
RETURNING id
`, []string{serviceID}).Scan(&patchTestID)
if err != nil {
t.Fatalf("failed to create patch test: %v", err)
}
handler := http.HandlerFunc(user.AddPatchTestHandler)
reqBody := user.AddPatchTestRequest{PatchTestID: patchTestID}
w := makeAdminRequest(handler, "POST", "/api/admin/users/"+userID+"/patch-tests", reqBody)
if w.Code != http.StatusCreated {
t.Errorf("expected status 201, got %d. body: %s", w.Code, w.Body.String())
}
// Verify patch test was added
var count int
err = db.DB.QueryRow(context.Background(), `
SELECT COUNT(*) FROM user_patch_tests WHERE user_id = $1 AND patch_test_id = $2
`, userID, patchTestID).Scan(&count)
if err != nil {
t.Fatalf("failed to check patch test: %v", err)
}
if count != 1 {
t.Errorf("expected 1 patch test record, got %d", count)
}
}
func TestAdminUsers_AddPatchTest_InvalidPatchTest(t *testing.T) {
cleanup := setupTestDB(t)
defer cleanup()
// Create test user
var userID string
err := db.DB.QueryRow(context.Background(), `
INSERT INTO users (n_first_name, n_last_name, email, phone, date_of_birth, password_hash, account_role, account_type)
VALUES ('Test', 'User', 'testuser@test.com', '+447123456789', '1990-01-01', 'hash', 'verified_email', 'email')
RETURNING id
`).Scan(&userID)
if err != nil {
t.Fatalf("failed to create user: %v", err)
}
handler := http.HandlerFunc(user.AddPatchTestHandler)
// Try to add a non-existent patch test
reqBody := user.AddPatchTestRequest{PatchTestID: "nonexist123"}
w := makeAdminRequest(handler, "POST", "/api/admin/users/"+userID+"/patch-tests", reqBody)
if w.Code != http.StatusBadRequest {
t.Errorf("expected status 400, got %d. body: %s", w.Code, w.Body.String())
}
}
func TestAdminUsers_NonAdmin(t *testing.T) {
cleanup := setupTestDB(t)
defer cleanup()
// Create regular user in DB
_, err := db.DB.Exec(context.Background(), `
INSERT INTO users (n_first_name, n_last_name, email, phone, date_of_birth, password_hash, account_role, account_type)
VALUES ('Regular', 'User', 'user@test.com', '+447123456789', '1990-01-01', 'hash', 'verified_email', 'email')
`)
if err != nil {
t.Fatalf("failed to create user: %v", err)
}
// Create test user for GET
var targetUserID string
err = db.DB.QueryRow(context.Background(), `
INSERT INTO users (n_first_name, n_last_name, email, phone, date_of_birth, password_hash, account_role, account_type)
VALUES ('Target', 'User', 'target@test.com', '+447123456789', '1990-01-01', 'hash', 'verified_email', 'email')
RETURNING id
`).Scan(&targetUserID)
if err != nil {
t.Fatalf("failed to create target user: %v", err)
}
// Test LIST - should get 403 when using middleware
listHandler := mw.RequireAdmin(http.HandlerFunc(user.ListAdminUsersHandler))
w := makeUserRequest(listHandler, "GET", "/api/admin/users", nil)
if w.Code != http.StatusForbidden {
t.Errorf("LIST: expected status 403, got %d", w.Code)
}
// Test GET - should get 403 when using middleware
getHandler := mw.RequireAdmin(http.HandlerFunc(user.GetAdminUserHandler))
w = makeUserRequest(getHandler, "GET", "/api/admin/users/"+targetUserID, nil)
if w.Code != http.StatusForbidden {
t.Errorf("GET: expected status 403, got %d", w.Code)
}
// Test eligible patch tests - should get 403 when using middleware
eligibleHandler := mw.RequireAdmin(http.HandlerFunc(user.GetEligiblePatchTestServicesHandler))
w = makeUserRequest(eligibleHandler, "GET", "/api/admin/users/"+targetUserID+"/patch-tests/eligible", nil)
if w.Code != http.StatusForbidden {
t.Errorf("ELIGIBLE: expected status 403, got %d", w.Code)
}
// Test add patch test - should get 403 when using middleware
addHandler := mw.RequireAdmin(http.HandlerFunc(user.AddPatchTestHandler))
w = makeUserRequest(addHandler, "POST", "/api/admin/users/"+targetUserID+"/patch-tests", map[string]string{"patch_test_id": "some-test-id"})
if w.Code != http.StatusForbidden {
t.Errorf("ADD: expected status 403, got %d", w.Code)
}
}
func TestAdminUsers_Get_Success(t *testing.T) {
cleanup := setupTestDB(t)
defer cleanup()
// Create a test user
var userID string
err := db.DB.QueryRow(context.Background(), `
INSERT INTO users (n_first_name, n_last_name, email, phone, date_of_birth, password_hash, account_role, account_type)
VALUES ('John', 'Doe', 'john.doe@test.com', '+447700900000', '1990-01-01', 'hash', 'verified_email', 'email')
RETURNING id
`).Scan(&userID)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
// Call admin get user endpoint
handler := http.HandlerFunc(user.GetAdminUserHandler)
w := makeAdminRequest(handler, "GET", "/api/admin/users/"+userID, nil)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d", w.Code)
t.Logf("response body: %s", w.Body.String())
return
}
var resp user.AdminUserDetail
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
t.Fatalf("failed to unmarshal response: %v", err)
}
if resp.ID != userID {
t.Errorf("expected user ID %s, got %s", userID, resp.ID)
}
if resp.FirstName != "John" {
t.Errorf("expected first name 'John', got '%s'", resp.FirstName)
}
if resp.LastName != "Doe" {
t.Errorf("expected last name 'Doe', got '%s'", resp.LastName)
}
if resp.Email == nil || *resp.Email != "john.doe@test.com" {
t.Errorf("expected email 'john.doe@test.com', got '%v'", resp.Email)
}
if resp.AccountRole != "verified_email" {
t.Errorf("expected account_role 'verified_email', got '%s'", resp.AccountRole)
}
}