Files
Crussell/backend/handlers/admin/services_test.go
T
popertots 44cac94f64 Fix test setup and middleware chain - Handler tests now passing
- Fix TestRequireRoleMiddleware by chaining RequireAuth before RequireRole (role context requirement)
- Remove unused 'strings' import from testdb.go
- Create crussell_test database in Docker setup
- Tests now properly initialize authentication context for role-based tests

Result: handlers test suite passes (13/13 tests)
Remaining failures in admin/auth/bookings/portfolio/scheduling/services/user packages need further investigation (environment setup, database constraints, endpoint initialization)
2026-02-21 23:50:17 +00:00

256 lines
7.7 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, password_hash, account_role, account_type)
VALUES ('Admin', 'User', 'admin@test.com', '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 deleted
var count int
err = db.DB.QueryRow(context.Background(), "SELECT COUNT(*) FROM services WHERE id = $1", serviceID).Scan(&count)
if err != nil {
t.Fatalf("failed to check service: %v", err)
}
if count != 0 {
t.Error("expected service to be deleted")
}
}
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, password_hash, account_role, account_type)
VALUES ('Regular', 'User', 'user@test.com', '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
}