- Add TestMain to set test env vars and testdb.TruncateTables for test isolation - Add chi routing context to test helpers for path parameter extraction - Fix SQL error handling to use errors.Is() instead of == - Add validators package with ID validation - Fix admin test middleware chain (RequireAdmin wrapper) - Update test user inserts to include phone and date_of_birth fields - Update service delete test to check soft-delete (is_active=false) - Update holiday hours test to use new schema (weekday, is_open) - Add phone number validation tests for UK mobile numbers
256 lines
7.9 KiB
Go
256 lines
7.9 KiB
Go
//go:build test
|
|
// +build test
|
|
|
|
package admin
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"crussell/db"
|
|
"crussell/handlers/services"
|
|
"crussell/mw"
|
|
)
|
|
|
|
func TestAdminServices_Create(t *testing.T) {
|
|
cleanup := setupTestDB(t)
|
|
defer cleanup()
|
|
|
|
// Create admin user in DB first
|
|
_, 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 ('Admin', 'User', 'admin@test.com', '+447123456789', '1990-01-01', 'hash', 'admin', 'email')
|
|
`)
|
|
if err != nil {
|
|
t.Fatalf("failed to create admin user: %v", err)
|
|
}
|
|
|
|
handler := http.HandlerFunc(services.CreateServiceHandler)
|
|
|
|
createReq := services.CreateServiceRequest{
|
|
Name: "Test Manicure",
|
|
Description: stringPtr("A test manicure service"),
|
|
Price: 35.00,
|
|
DurationMinutes: 45,
|
|
PatchTestDurationHours: 0,
|
|
MinimumAgeRequired: 16,
|
|
}
|
|
|
|
w := makeAdminRequest(handler, "POST", "/api/admin/services", createReq)
|
|
|
|
if w.Code != http.StatusCreated {
|
|
t.Errorf("expected status 201, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var response services.Service
|
|
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
|
|
t.Fatalf("failed to unmarshal response: %v", err)
|
|
}
|
|
|
|
if response.Name != "Test Manicure" {
|
|
t.Errorf("expected name 'Test Manicure', got %s", response.Name)
|
|
}
|
|
if response.Price != 35.00 {
|
|
t.Errorf("expected price 35.00, got %f", response.Price)
|
|
}
|
|
if !response.IsActive {
|
|
t.Error("expected new service to be active by default")
|
|
}
|
|
}
|
|
|
|
func TestAdminServices_List(t *testing.T) {
|
|
cleanup := setupTestDB(t)
|
|
defer cleanup()
|
|
|
|
// Insert test services
|
|
_, err := db.DB.Exec(context.Background(), `
|
|
INSERT INTO services (name, description, price, duration_minutes, is_active, patch_test_duration_hours, minimum_age_required)
|
|
VALUES
|
|
('Manicure', 'Basic manicure', 25.00, 30, true, 0, 0),
|
|
('Pedicure', 'Basic pedicure', 30.00, 45, false, 0, 0),
|
|
('Gel Polish', 'Gel polish service', 40.00, 60, true, 48, 16)
|
|
`)
|
|
if err != nil {
|
|
t.Fatalf("failed to create services: %v", err)
|
|
}
|
|
|
|
handler := http.HandlerFunc(services.AllServicesHandler)
|
|
w := makeAdminRequest(handler, "GET", "/api/admin/services", nil)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var response []services.Service
|
|
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
|
|
t.Fatalf("failed to unmarshal response: %v", err)
|
|
}
|
|
|
|
if len(response) != 3 {
|
|
t.Errorf("expected 3 services, got %d", len(response))
|
|
}
|
|
|
|
// Verify all services including inactive are returned
|
|
found := map[string]bool{}
|
|
for _, s := range response {
|
|
found[s.Name] = true
|
|
}
|
|
|
|
if !found["Manicure"] {
|
|
t.Error("expected Manicure in response")
|
|
}
|
|
if !found["Pedicure"] {
|
|
t.Error("expected Pedicure in response (including inactive)")
|
|
}
|
|
if !found["Gel Polish"] {
|
|
t.Error("expected Gel Polish in response")
|
|
}
|
|
}
|
|
|
|
func TestAdminServices_Toggle(t *testing.T) {
|
|
cleanup := setupTestDB(t)
|
|
defer cleanup()
|
|
|
|
// Create a service
|
|
var serviceID string
|
|
err := db.DB.QueryRow(context.Background(), `
|
|
INSERT INTO services (name, description, price, duration_minutes, is_active, patch_test_duration_hours, minimum_age_required)
|
|
VALUES ('Test Service', 'A test service', 50.00, 60, true, 0, 16)
|
|
RETURNING id
|
|
`).Scan(&serviceID)
|
|
if err != nil {
|
|
t.Fatalf("failed to create service: %v", err)
|
|
}
|
|
|
|
handler := http.HandlerFunc(services.ToggleService)
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/services/"+serviceID+"/toggle", nil)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
// Verify service is now inactive
|
|
var isActive bool
|
|
err = db.DB.QueryRow(context.Background(), "SELECT is_active FROM services WHERE id = $1", serviceID).Scan(&isActive)
|
|
if err != nil {
|
|
t.Fatalf("failed to check service: %v", err)
|
|
}
|
|
if isActive {
|
|
t.Error("expected service to be inactive after toggle")
|
|
}
|
|
|
|
// Toggle again
|
|
w = makeAdminRequest(handler, "PUT", "/api/admin/services/"+serviceID+"/toggle", nil)
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200 on second toggle, got %d", w.Code)
|
|
}
|
|
|
|
// Verify service is active again
|
|
err = db.DB.QueryRow(context.Background(), "SELECT is_active FROM services WHERE id = $1", serviceID).Scan(&isActive)
|
|
if err != nil {
|
|
t.Fatalf("failed to check service: %v", err)
|
|
}
|
|
if !isActive {
|
|
t.Error("expected service to be active after second toggle")
|
|
}
|
|
}
|
|
|
|
func TestAdminServices_Delete(t *testing.T) {
|
|
cleanup := setupTestDB(t)
|
|
defer cleanup()
|
|
|
|
// Create a service
|
|
var serviceID string
|
|
err := db.DB.QueryRow(context.Background(), `
|
|
INSERT INTO services (name, description, price, duration_minutes, is_active, patch_test_duration_hours, minimum_age_required)
|
|
VALUES ('Test Service', 'A test service', 50.00, 60, true, 0, 16)
|
|
RETURNING id
|
|
`).Scan(&serviceID)
|
|
if err != nil {
|
|
t.Fatalf("failed to create service: %v", err)
|
|
}
|
|
|
|
handler := http.HandlerFunc(services.DeleteServiceHandler)
|
|
w := makeAdminRequest(handler, "DELETE", "/api/admin/services/"+serviceID, nil)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
// Verify service is soft deleted (is_active = false)
|
|
var isActive bool
|
|
err = db.DB.QueryRow(context.Background(), "SELECT is_active FROM services WHERE id = $1", serviceID).Scan(&isActive)
|
|
if err != nil {
|
|
t.Fatalf("failed to check service: %v", err)
|
|
}
|
|
if isActive {
|
|
t.Error("expected service to be soft deleted (is_active = false)")
|
|
}
|
|
}
|
|
|
|
func TestAdminServices_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)
|
|
}
|
|
|
|
// Test CREATE - should get 403 when using middleware
|
|
createHandler := mw.RequireAdmin(http.HandlerFunc(services.CreateServiceHandler))
|
|
createReq := services.CreateServiceRequest{
|
|
Name: "Test Service",
|
|
Description: stringPtr("Test"),
|
|
Price: 50.00,
|
|
DurationMinutes: 60,
|
|
PatchTestDurationHours: 0,
|
|
MinimumAgeRequired: 16,
|
|
}
|
|
w := makeUserRequest(createHandler, "POST", "/api/admin/services", createReq)
|
|
if w.Code != http.StatusForbidden {
|
|
t.Errorf("CREATE: expected status 403, got %d", w.Code)
|
|
}
|
|
|
|
// Test LIST - should get 403 when using middleware
|
|
listHandler := mw.RequireAdmin(http.HandlerFunc(services.AllServicesHandler))
|
|
w = makeUserRequest(listHandler, "GET", "/api/admin/services", nil)
|
|
if w.Code != http.StatusForbidden {
|
|
t.Errorf("LIST: expected status 403, got %d", w.Code)
|
|
}
|
|
|
|
// Test TOGGLE - should get 403 when using middleware
|
|
var serviceID string
|
|
err = db.DB.QueryRow(context.Background(), `
|
|
INSERT INTO services (name, description, price, duration_minutes, is_active, patch_test_duration_hours, minimum_age_required)
|
|
VALUES ('Test Service', 'A test service', 50.00, 60, true, 0, 16)
|
|
RETURNING id
|
|
`).Scan(&serviceID)
|
|
if err != nil {
|
|
t.Fatalf("failed to create service: %v", err)
|
|
}
|
|
|
|
toggleHandler := mw.RequireAdmin(http.HandlerFunc(services.ToggleService))
|
|
w = makeUserRequest(toggleHandler, "PUT", "/api/admin/services/"+serviceID+"/toggle", nil)
|
|
if w.Code != http.StatusForbidden {
|
|
t.Errorf("TOGGLE: expected status 403, got %d", w.Code)
|
|
}
|
|
|
|
// Test DELETE - should get 403 when using middleware
|
|
deleteHandler := mw.RequireAdmin(http.HandlerFunc(services.DeleteServiceHandler))
|
|
w = makeUserRequest(deleteHandler, "DELETE", "/api/admin/services/"+serviceID, nil)
|
|
if w.Code != http.StatusForbidden {
|
|
t.Errorf("DELETE: expected status 403, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func stringPtr(s string) *string {
|
|
return &s
|
|
}
|