Refactor patch test system and add booking edit requests
- Replace patch_test_duration_hours on services with separate patch_tests table - Add user_patch_tests table to track user patch test records - Add booking edit request system: users can request time changes - Add admin handlers to list, approve, and reject edit requests - Add validation to prevent editing completed/cancelled bookings - Add overlap and closed-day checks for booking edits
This commit is contained in:
@@ -30,12 +30,11 @@ func TestAdminServices_Create(t *testing.T) {
|
||||
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,
|
||||
Name: "Test Manicure",
|
||||
Description: stringPtr("A test manicure service"),
|
||||
Price: 35.00,
|
||||
DurationMinutes: 45,
|
||||
MinimumAgeRequired: 16,
|
||||
}
|
||||
|
||||
w := makeAdminRequest(handler, "POST", "/api/admin/services", createReq)
|
||||
@@ -66,11 +65,11 @@ func TestAdminServices_List(t *testing.T) {
|
||||
|
||||
// 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)
|
||||
INSERT INTO services (name, description, price, duration_minutes, is_active, 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)
|
||||
('Manicure', 'Basic manicure', 25.00, 30, true, 0),
|
||||
('Pedicure', 'Basic pedicure', 30.00, 45, false, 0),
|
||||
('Gel Polish', 'Gel polish service', 40.00, 60, true, 16)
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create services: %v", err)
|
||||
@@ -116,8 +115,8 @@ func TestAdminServices_Toggle(t *testing.T) {
|
||||
// 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)
|
||||
INSERT INTO services (name, description, price, duration_minutes, is_active, minimum_age_required)
|
||||
VALUES ('Test Service', 'A test service', 50.00, 60, true, 16)
|
||||
RETURNING id
|
||||
`).Scan(&serviceID)
|
||||
if err != nil {
|
||||
@@ -164,8 +163,8 @@ func TestAdminServices_Delete(t *testing.T) {
|
||||
// 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)
|
||||
INSERT INTO services (name, description, price, duration_minutes, is_active, minimum_age_required)
|
||||
VALUES ('Test Service', 'A test service', 50.00, 60, true, 16)
|
||||
RETURNING id
|
||||
`).Scan(&serviceID)
|
||||
if err != nil {
|
||||
@@ -206,12 +205,11 @@ func TestAdminServices_NonAdmin(t *testing.T) {
|
||||
// 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,
|
||||
Name: "Test Service",
|
||||
Description: stringPtr("Test"),
|
||||
Price: 50.00,
|
||||
DurationMinutes: 60,
|
||||
MinimumAgeRequired: 16,
|
||||
}
|
||||
w := makeUserRequest(createHandler, "POST", "/api/admin/services", createReq)
|
||||
if w.Code != http.StatusForbidden {
|
||||
@@ -228,8 +226,8 @@ func TestAdminServices_NonAdmin(t *testing.T) {
|
||||
// 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)
|
||||
INSERT INTO services (name, description, price, duration_minutes, is_active, minimum_age_required)
|
||||
VALUES ('Test Service', 'A test service', 50.00, 60, true, 16)
|
||||
RETURNING id
|
||||
`).Scan(&serviceID)
|
||||
if err != nil {
|
||||
|
||||
@@ -117,17 +117,45 @@ func TestAdminUsers_PatchTests_Eligible(t *testing.T) {
|
||||
|
||||
// Create services - some with patch test, some without
|
||||
_, err = db.DB.Exec(context.Background(), `
|
||||
INSERT INTO services (name, description, price, duration_minutes, is_active, patch_test_duration_hours, minimum_age_required)
|
||||
INSERT INTO services (name, description, price, duration_minutes, is_active, minimum_age_required)
|
||||
VALUES
|
||||
('Basic Manicure', 'Basic manicure', 25.00, 30, true, 0, 0),
|
||||
('Gel Polish Full Set', 'Gel polish service', 45.00, 60, true, 48, 16),
|
||||
('Luxury Gel Manicure', 'Luxury gel', 55.00, 75, true, 48, 16),
|
||||
('Inactive Service', 'Inactive', 30.00, 30, false, 48, 16)
|
||||
('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)
|
||||
|
||||
@@ -140,7 +168,7 @@ func TestAdminUsers_PatchTests_Eligible(t *testing.T) {
|
||||
t.Fatalf("failed to unmarshal response: %v", err)
|
||||
}
|
||||
|
||||
// Should return 2 services (the two with patch test duration > 0 that are active)
|
||||
// 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())
|
||||
}
|
||||
@@ -161,11 +189,11 @@ func TestAdminUsers_PatchTests_Eligible_WithExisting(t *testing.T) {
|
||||
t.Fatalf("failed to create user: %v", err)
|
||||
}
|
||||
|
||||
// Create services with patch test
|
||||
// Create services
|
||||
var serviceID1, serviceID2 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 ('Gel Polish Full Set', 'Gel polish service', 45.00, 60, true, 48, 16)
|
||||
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 {
|
||||
@@ -173,19 +201,38 @@ func TestAdminUsers_PatchTests_Eligible_WithExisting(t *testing.T) {
|
||||
}
|
||||
|
||||
err = db.DB.QueryRow(context.Background(), `
|
||||
INSERT INTO services (name, description, price, duration_minutes, is_active, patch_test_duration_hours, minimum_age_required)
|
||||
VALUES ('Luxury Gel Manicure', 'Luxury gel', 55.00, 75, true, 48, 16)
|
||||
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)
|
||||
}
|
||||
|
||||
// Add one patch test for the user
|
||||
// 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 user_service_patch_tests (user_id, service_id, last_time)
|
||||
VALUES ($1, $2, NOW())
|
||||
`, userID, serviceID1)
|
||||
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)
|
||||
}
|
||||
@@ -227,20 +274,31 @@ func TestAdminUsers_AddPatchTest(t *testing.T) {
|
||||
t.Fatalf("failed to create user: %v", err)
|
||||
}
|
||||
|
||||
// Create service with patch test
|
||||
// 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 ('Gel Polish Full Set', 'Gel polish service', 45.00, 60, true, 48, 16)
|
||||
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{ServiceID: serviceID}
|
||||
reqBody := user.AddPatchTestRequest{PatchTestID: patchTestID}
|
||||
w := makeAdminRequest(handler, "POST", "/api/admin/users/"+userID+"/patch-tests", reqBody)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
@@ -250,8 +308,8 @@ func TestAdminUsers_AddPatchTest(t *testing.T) {
|
||||
// Verify patch test was added
|
||||
var count int
|
||||
err = db.DB.QueryRow(context.Background(), `
|
||||
SELECT COUNT(*) FROM user_service_patch_tests WHERE user_id = $1 AND service_id = $2
|
||||
`, userID, serviceID).Scan(&count)
|
||||
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)
|
||||
}
|
||||
@@ -261,7 +319,7 @@ func TestAdminUsers_AddPatchTest(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminUsers_AddPatchTest_InvalidService(t *testing.T) {
|
||||
func TestAdminUsers_AddPatchTest_InvalidPatchTest(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
@@ -276,20 +334,10 @@ func TestAdminUsers_AddPatchTest_InvalidService(t *testing.T) {
|
||||
t.Fatalf("failed to create user: %v", err)
|
||||
}
|
||||
|
||||
// Create service without patch test requirement
|
||||
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 ('Basic Manicure', 'Basic manicure', 25.00, 30, true, 0, 0)
|
||||
RETURNING id
|
||||
`).Scan(&serviceID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create service: %v", err)
|
||||
}
|
||||
|
||||
handler := http.HandlerFunc(user.AddPatchTestHandler)
|
||||
|
||||
reqBody := user.AddPatchTestRequest{ServiceID: serviceID}
|
||||
// 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 {
|
||||
@@ -344,7 +392,7 @@ func TestAdminUsers_NonAdmin(t *testing.T) {
|
||||
|
||||
// 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{"service_id": "some-service-id"})
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user