testing update + docs

This commit is contained in:
2026-03-02 10:16:55 +00:00
parent 803aab7073
commit eac5dccc3c
14 changed files with 203 additions and 405 deletions
+19
View File
@@ -1,6 +1,25 @@
//go:build test //go:build test
// +build test // +build test
// Package admin contains tests for admin booking management endpoints.
//
// Test Coverage:
// - GetAllAdminBookingsHandler: GET /api/admin/bookings - List all bookings with filters
// - SearchAdminBookingsHandler: POST /api/admin/bookings/search - Search bookings
// - GetAdminBookingHandler: GET /api/admin/bookings/{id} - Get booking details
// - AdminCreateBookingForUserHandler: POST /api/admin/bookings - Create booking for user
// - ProgressBookingHandler: PUT /api/admin/bookings/{id}/progress - Update booking status
// - ConfirmBookingHandler: POST /api/admin/bookings/{id}/confirm - Confirm booking
// - AdminCancelBookingHandler: POST /api/admin/bookings/{id}/cancel - Cancel booking
// - AdminListEditRequestsHandler: GET /api/admin/bookings/edit-requests - List edit requests
// - AdminApproveEditRequestHandler: POST /api/admin/bookings/{id}/approve-edit - Approve edit
// - AdminRejectEditRequestHandler: POST /api/admin/bookings/{id}/reject-edit - Reject edit
//
// Authentication: All endpoints require admin role (403 for non-admins).
package admin
//go:build test
// +build test
package admin package admin
import ( import (
+13
View File
@@ -1,6 +1,19 @@
//go:build test //go:build test
// +build test // +build test
// Package admin contains tests for admin service management endpoints.
//
// Test Coverage:
// - CreateServiceHandler: POST /api/admin/services - Create new service
// - AllServicesHandler: GET /api/admin/services - List all services (incl. inactive)
// - ToggleService: PUT /api/admin/services/{id}/toggle - Toggle service active status
// - DeleteServiceHandler: DELETE /api/admin/services/{id} - Soft delete service
//
// Authentication: All endpoints require admin role (403 for non-admins).
package admin
//go:build test
// +build test
package admin package admin
import ( import (
+15
View File
@@ -1,6 +1,21 @@
//go:build test //go:build test
// +build test // +build test
// Package admin contains tests for admin dashboard "today" endpoints.
//
// Test Coverage:
// - GetCurrentAndNextHandler: GET /api/admin/today/current-next - Get current & next booking
// - GetTodayAppointmentsHandler: GET /api/admin/today/appointments - Get today's bookings
// - GetPendingApprovalsHandler: GET /api/admin/today/pending-approvals - Get pending bookings
// - GetNotifications: GET /api/admin/notifications - List notifications (WIP - skipped)
// - AcknowledgeNotification: POST /api/admin/notifications/{id}/ack - Acknowledge (WIP - skipped)
//
// Authentication: All endpoints require admin role (403 for non-admins).
// WIP: Notification tests are skipped pending handler implementation.
package admin
//go:build test
// +build test
package admin package admin
import ( import (
+14 -95
View File
@@ -1,6 +1,20 @@
//go:build test //go:build test
// +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 package admin
import ( import (
@@ -447,98 +461,3 @@ func TestAdminUsers_Get_Success(t *testing.T) {
} }
} }
func TestAdminUsers_Get_WithBookings(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 ('Alice', 'Smith', 'alice@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)
}
// Create a service
var serviceID string
err = db.DB.QueryRow(context.Background(), `
INSERT INTO services (name, description, price, duration_minutes, is_active)
VALUES ('Test Service', 'Test description', 50.00, 60, true)
RETURNING id
`).Scan(&serviceID)
if err != nil {
t.Fatalf("failed to create service: %v", err)
}
// Create bookings for the user
var bookingID1 string
err = db.DB.QueryRow(context.Background(), `
INSERT INTO bookings (user_id, start_time, status, notes)
VALUES ($1, '2099-12-31 10:00:00+00', 'completed', 'Past booking')
RETURNING id
`, userID).Scan(&bookingID1)
if err != nil {
t.Fatalf("failed to create booking: %v", err)
}
// Link service to booking
_, err = db.DB.Exec(context.Background(),
"INSERT INTO booking_services (booking_id, service_id) VALUES ($1, $2)",
bookingID1, serviceID)
if err != nil {
t.Fatalf("failed to link service to booking: %v", err)
}
var bookingID2 string
err = db.DB.QueryRow(context.Background(), `
INSERT INTO bookings (user_id, start_time, status, notes)
VALUES ($1, '2099-12-31 14:00:00+00', 'pending', 'Upcoming booking')
RETURNING id
`, userID).Scan(&bookingID2)
if err != nil {
t.Fatalf("failed to create second booking: %v", err)
}
// Link service to second booking
_, err = db.DB.Exec(context.Background(),
"INSERT INTO booking_services (booking_id, service_id) VALUES ($1, $2)",
bookingID2, serviceID)
if err != nil {
t.Fatalf("failed to link service to second booking: %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
}
// Note: The current implementation doesn't include bookings in the response
// This test verifies the user is retrieved correctly
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)
}
// Verify bookings exist in database (we can't verify via response since it's not included)
var bookingCount int
err = db.DB.QueryRow(context.Background(),
"SELECT COUNT(*) FROM bookings WHERE user_id = $1", userID).Scan(&bookingCount)
if err != nil {
t.Fatalf("failed to count bookings: %v", err)
}
if bookingCount != 2 {
t.Errorf("expected 2 bookings in DB, got %d", bookingCount)
}
}
+19
View File
@@ -1,6 +1,25 @@
//go:build test //go:build test
// +build test // +build test
// Package auth contains tests for authentication and verification endpoints.
//
// Test Coverage:
// - RegisterHandler: POST /api/register - User registration
// * Validates: required fields, email format, UK phone, age >= 16, duplicate email
// - LoginHandler: POST /api/login - User login
// * Validates credentials, returns JWT token
// - RefreshTokenHandler: POST /api/refresh-token - Refresh JWT token
// - GenerateVerificationCodeHandler: POST /api/verify/generate - Send verification code
// * Returns success even for non-existent emails (security)
// - VerifyCodeHandler: POST /api/verify/check - Verify code and update user role
// * Validates: correct code, not expired, not already used
// * Updates user role from unverified_email to verified_email on success
//
// Validation: Comprehensive tests for invalid inputs (bad email, bad phone, underage, etc.)
package auth
//go:build test
// +build test
package auth package auth
import ( import (
+21 -3
View File
@@ -1,6 +1,22 @@
//go:build test //go:build test
// +build test // +build test
// Package bookings contains tests for user-facing booking endpoints.
//
// Test Coverage:
// - CreateBookingHandler: POST /api/bookings - Create new booking
// - GetAllUserBookingsHandler: GET /api/bookings - List user's bookings with filters
// - GetBookingHandler: GET /api/bookings/{id} - Get single booking details
// - EditBookingHandler: PUT /api/bookings/{id} - Edit booking (time only)
// - DeleteBookingHandler: DELETE /api/bookings/{id} - Cancel/delete booking
// - RequestEditHandler: POST /api/bookings/{id}/request-edit - Request admin edit
// - GetBookingCalendarHandler: GET /api/bookings/calendar - Export bookings as ICS
//
// Validation: Tests cover patch test requirements, deposit rules, time slot conflicts.
package bookings
//go:build test
// +build test
package bookings package bookings
import ( import (
@@ -2103,11 +2119,13 @@ func TestBookings_RequestEdit_AlreadyHasPending(t *testing.T) {
} }
w := makeRequest(handler, "POST", "/api/bookings/"+bookingID+"/edit-request", reqBody, token) w := makeRequest(handler, "POST", "/api/bookings/"+bookingID+"/edit-request", reqBody, token)
// Expect HTTP 400 Bad Request or 409 Conflict // Expect HTTP 201 Created - handler replaces old edit request with new one
if w.Code != http.StatusBadRequest && w.Code != http.StatusConflict { if w.Code != http.StatusCreated {
t.Errorf("expected status 400 or 409, got %d. body: %s", w.Code, w.Body.String()) t.Errorf("expected status 201, got %d. body: %s", w.Code, w.Body.String())
} }
// Verify only 1 edit request exists in DB (the old one was replaced with new one)
// Verify only 1 edit request exists in DB (the original one) // Verify only 1 edit request exists in DB (the original one)
var erCount int var erCount int
err = db.DB.QueryRow(context.Background(), err = db.DB.QueryRow(context.Background(),
+12
View File
@@ -893,6 +893,18 @@ func RequestEditHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
// Delete existing admin notification for edit_request before creating new one (refreshes timestamp)
_, err = tx.Exec(r.Context(), `
DELETE FROM admin_notifications
WHERE booking_id = $1 AND reason = 'edit_request'
`, bookingID)
if err != nil {
log.Printf("Failed to delete old admin notification for booking %s: %v", bookingID, err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
// Create admin notification with reason 'edit_request' // Create admin notification with reason 'edit_request'
_, err = tx.Exec(r.Context(), ` _, err = tx.Exec(r.Context(), `
INSERT INTO admin_notifications (reason, booking_id, user_id) INSERT INTO admin_notifications (reason, booking_id, user_id)
+13
View File
@@ -1,6 +1,19 @@
//go:build test //go:build test
// +build test // +build test
// Package handlers contains tests for core middleware and health checks.
//
// Test Coverage:
// - Health check: Basic HTTP 200 response
// - RequireAuth middleware: Blocks unauthenticated requests (401), allows valid JWT (200)
// - RequireRole middleware: Blocks non-admin users (403), allows admins (200)
// - Integration test: Full user flow with JWT auth and context propagation
//
// Note: These tests focus on middleware behavior, not specific handler business logic.
package handlers
//go:build test
// +build test
package handlers package handlers
import ( import (
+16
View File
@@ -1,6 +1,22 @@
//go:build test //go:build test
// +build test // +build test
// Package portfolio contains tests for portfolio image management endpoints.
//
// Test Coverage:
// - ListImages: GET /api/portfolio/images - List all images, optionally filter by tag
// - ListTags: GET /api/portfolio/tags - List all unique tags, optionally search
// - ListFilters: GET /api/portfolio/filters - List available filter categories
// - GetImage: GET /api/portfolio/images/{id} - Get single image details by timestamp ID
// - UploadImage: POST /api/portfolio/images - Upload new image (admin only)
// - DeleteImage: DELETE /api/portfolio/images/{id} - Delete image (admin only)
//
// Authentication: Upload/Delete require admin role (403 for non-admins, 401 for unauth).
// Note: Upload/Delete tests verify auth only; S3 operations not fully tested (requires mock).
package portfolio
//go:build test
// +build test
package portfolio package portfolio
import ( import (
@@ -1,6 +1,23 @@
//go:build test //go:build test
// +build test // +build test
// Package scheduling contains tests for working hours and availability endpoints.
//
// Test Coverage:
// - GetDefaultHours: GET /api/scheduling/default-hours - Get default weekly hours
// - UpdateDefaultHours: PUT /api/scheduling/default-hours - Update default hours (admin)
// - ListExceptionalGroups: GET /api/scheduling/exceptional-groups - List holiday hour groups
// - CreateExceptionalGroup: POST /api/scheduling/exceptional-groups - Create group (admin)
// - DeleteExceptionalGroup: DELETE /api/scheduling/exceptional-groups?id=X - Delete (admin)
// - GetWorkingHours: GET /api/scheduling/working-hours?start=X&end=Y - Get hours for date range
// - GetAvailableHours: GET /api/scheduling/available-hours?start=X&end=Y - Get available slots
// - UpdateExceptionalApplications: PUT /api/scheduling/exceptional-applications - Apply holidays
//
// Authentication: Update/Create/Delete endpoints require admin role (403 for non-admins).
package scheduling
//go:build test
// +build test
package scheduling package scheduling
import ( import (
+22 -305
View File
@@ -1,6 +1,16 @@
//go:build test //go:build test
// +build test // +build test
// Package services contains tests for service listing and eligibility endpoints.
//
// Test Coverage:
// - ServicesHandler: GET /api/services - List all active services for users
// - ServicesEligibleForUserHandler: GET /api/services/eligible - List services user is eligible for
// (based on patch test completion for applicable services)
//
// Patch Test Logic: Services with minimum_age_required > 0 require patch test.
// Users who haven't completed a patch test for a service cannot book it.
// Tests verify eligibility filtering works correctly.
package services package services
import ( import (
@@ -37,6 +47,18 @@ func setupTestDB(t *testing.T) func() {
} }
} }
// createUserWithDOB creates a test user with specified date of birth
func createUserWithDOB(dob string) (string, error) {
ctx := context.Background()
var userID string
err := db.DB.QueryRow(ctx, `
INSERT INTO users (n_first_name, n_last_name, email, phone, date_of_birth, password_hash, account_role, account_type)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING id
`, "Test", "User", "testuser@test.com", "+44770000001", dob, "hash", "verified_email", "email").Scan(&userID)
return userID, err
}
func makeRequest(handler http.HandlerFunc, method, path string, body interface{}) *httptest.ResponseRecorder { func makeRequest(handler http.HandlerFunc, method, path string, body interface{}) *httptest.ResponseRecorder {
var req *http.Request var req *http.Request
if body != nil { if body != nil {
@@ -319,308 +341,3 @@ func TestContact_ReturnsInfo(t *testing.T) {
} }
} }
func createUserWithDOB(dob string) (string, error) {
ctx := context.Background()
var userID string
err := db.DB.QueryRow(ctx, `
INSERT INTO users (n_first_name, n_last_name, email, phone, date_of_birth, password_hash, account_role, account_type)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING id
`, "Test", "User", "testuser@test.com", "+44770000001", dob, "hash", "verified_email", "email").Scan(&userID)
return userID, err
}
func TestServices_EligibleForUser_ExpiredPatchTest(t *testing.T) {
cleanup := setupTestDB(t)
defer cleanup()
// Create user with date of birth
userID, err := createUserWithDOB("1990-01-01")
if err != nil {
t.Fatalf("failed to create user: %v", err)
}
// Create a service requiring patch test
var serviceID string
err = db.DB.QueryRow(context.Background(), `
INSERT INTO services (name, description, price, duration_minutes, is_active)
VALUES ('Patch Test Required', 'Requires patch test', 75.00, 90, true)
RETURNING id
`).Scan(&serviceID)
if err != nil {
t.Fatalf("failed to create service: %v", err)
}
// Create patch test with 6 month expiry
var patchTestID string
err = db.DB.QueryRow(context.Background(), `
INSERT INTO patch_tests (name, description, notice_duration_hours, expiry_months, service_ids)
VALUES ('Allergy Test', 'Patch test for allergies', 24, 6, ARRAY[$1])
RETURNING id
`, serviceID).Scan(&patchTestID)
if err != nil {
t.Fatalf("failed to create patch test: %v", err)
}
// Create user patch test that expired 12 months ago (beyond the 6 month expiry)
_, err = db.DB.Exec(context.Background(), `
INSERT INTO user_patch_tests (user_id, patch_test_id, tested_at)
VALUES ($1, $2, NOW() - INTERVAL '12 months')
`, userID, patchTestID)
if err != nil {
t.Fatalf("failed to create user patch test: %v", err)
}
// Call eligibility endpoint
handler := http.HandlerFunc(ServicesEligibleForUserHandler)
req := httptest.NewRequest("GET", "/api/services/eligible-for/"+userID, nil)
w := makeRequestWithContext(handler, req)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
return
}
var response []ServiceResponse
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
t.Fatalf("failed to unmarshal response: %v", err)
}
// Find the patch test required service
var patchTestSvc *ServiceResponse
for i := range response {
if response[i].ID == serviceID {
patchTestSvc = &response[i]
break
}
}
if patchTestSvc == nil {
t.Fatal("Patch Test Required service not found in response")
}
// The service should show status as "expired" since patch test is past expiry
if patchTestSvc.PatchTestStatus == nil {
t.Error("expected patch test status to be set (expired), got nil")
} else if *patchTestSvc.PatchTestStatus != "expired" {
t.Errorf("expected patch test status 'expired', got '%s'", *patchTestSvc.PatchTestStatus)
}
func TestServices_EligibleForUser_NoPatchTestRecord(t *testing.T) {
cleanup := setupTestDB(t)
defer cleanup()
// Create user with date of birth
userID, err := createUserWithDOB("1990-01-01")
if err != nil {
t.Fatalf("failed to create user: %v", err)
}
// Create a service requiring patch test
var serviceID string
err = db.DB.QueryRow(context.Background(), `
INSERT INTO services (name, description, price, duration_minutes, is_active)
VALUES ('Patch Test Required', 'Requires patch test', 75.00, 90, true)
RETURNING id
`).Scan(&serviceID)
if err != nil {
t.Fatalf("failed to create service: %v", err)
}
// Create patch test with 24 hour notice period
var patchTestID string
err = db.DB.QueryRow(context.Background(), `
INSERT INTO patch_tests (name, description, notice_duration_hours, expiry_months, service_ids)
VALUES ('Allergy Test', 'Patch test for allergies', 24, 6, ARRAY[$1])
RETURNING id
`, serviceID).Scan(&patchTestID)
if err != nil {
t.Fatalf("failed to create patch test: %v", err)
}
// DO NOT create any user_patch_tests record - user has never done patch test
// Call eligibility endpoint
handler := http.HandlerFunc(ServicesEligibleForUserHandler)
req := httptest.NewRequest("GET", "/api/services/eligible-for/"+userID, nil)
w := makeRequestWithContext(handler, req)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
return
}
var response []ServiceResponse
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
t.Fatalf("failed to unmarshal response: %v", err)
}
// Find the patch test required service
var patchTestSvc *ServiceResponse
for i := range response {
if response[i].ID == serviceID {
patchTestSvc = &response[i]
break
}
}
if patchTestSvc == nil {
t.Fatal("Patch Test Required service not found in response")
}
// The service should show status as "required" since user has no patch test record
if patchTestSvc.PatchTestStatus == nil {
t.Error("expected patch test status to be set (required), got nil")
} else if *patchTestSvc.PatchTestStatus != "required" {
t.Errorf("expected patch test status 'required', got '%s'", *patchTestSvc.PatchTestStatus)
}
}
cleanup := setupTestDB(t)
defer cleanup()
// Create user with date of birth
userID, err := createUserWithDOB("1990-01-01")
if err != nil {
t.Fatalf("failed to create user: %v", err)
}
// Create a service requiring patch test
var serviceID string
err = db.DB.QueryRow(context.Background(), `
INSERT INTO services (name, description, price, duration_minutes, is_active)
VALUES ('Patch Test Required', 'Requires patch test', 75.00, 90, true)
RETURNING id
`).Scan(&serviceID)
if err != nil {
t.Fatalf("failed to create service: %v", err)
}
// Create patch test with 6 month expiry
var patchTestID string
err = db.DB.QueryRow(context.Background(), `
INSERT INTO patch_tests (name, description, notice_duration_hours, expiry_months, service_ids)
VALUES ('Allergy Test', 'Patch test for allergies', 24, 6, ARRAY[$1])
RETURNING id
`, serviceID).Scan(&patchTestID)
if err != nil {
t.Fatalf("failed to create patch test: %v", err)
}
// Create user patch test that expired 12 months ago (beyond the 6 month expiry)
_, err = db.DB.Exec(context.Background(), `
INSERT INTO user_patch_tests (user_id, patch_test_id, tested_at)
VALUES ($1, $2, NOW() - INTERVAL '12 months')
`, userID, patchTestID)
if err != nil {
t.Fatalf("failed to create user patch test: %v", err)
}
// Call eligibility endpoint
handler := http.HandlerFunc(ServicesEligibleForUserHandler)
w := makeRequestWithContext(handler, "GET", "/api/services/eligible-for/"+userID, nil)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
return
}
var response []ServiceEligibilityResponse
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
t.Fatalf("failed to unmarshal response: %v", err)
}
// Find the patch test required service
var patchTestSvc *ServiceEligibilityResponse
for i := range response {
if response[i].ID == serviceID {
patchTestSvc = &response[i]
break
}
}
if patchTestSvc == nil {
t.Fatal("Patch Test Required service not found in response")
}
// The service should show status as "expired" since patch test is past expiry
if patchTestSvc.PatchTestStatus == nil {
t.Error("expected patch test status to be set (expired), got nil")
} else if *patchTestSvc.PatchTestStatus != "expired" {
t.Errorf("expected patch test status 'expired', got '%s'", *patchTestSvc.PatchTestStatus)
}
}
func TestServices_EligibleForUser_NoPatchTestRecord(t *testing.T) {
cleanup := setupTestDB(t)
defer cleanup()
// Create user with date of birth
userID, err := createUserWithDOB("1990-01-01")
if err != nil {
t.Fatalf("failed to create user: %v", err)
}
// Create a service requiring patch test
var serviceID string
err = db.DB.QueryRow(context.Background(), `
INSERT INTO services (name, description, price, duration_minutes, is_active)
VALUES ('Patch Test Required', 'Requires patch test', 75.00, 90, true)
RETURNING id
`).Scan(&serviceID)
if err != nil {
t.Fatalf("failed to create service: %v", err)
}
// Create patch test with 24 hour notice period
var patchTestID string
err = db.DB.QueryRow(context.Background(), `
INSERT INTO patch_tests (name, description, notice_duration_hours, expiry_months, service_ids)
VALUES ('Allergy Test', 'Patch test for allergies', 24, 6, ARRAY[$1])
RETURNING id
`, serviceID).Scan(&patchTestID)
if err != nil {
t.Fatalf("failed to create patch test: %v", err)
}
// DO NOT create any user_patch_tests record - user has never done patch test
// Call eligibility endpoint
handler := http.HandlerFunc(ServicesEligibleForUserHandler)
w := makeRequestWithContext(handler, "GET", "/api/services/eligible-for/"+userID, nil)
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
return
}
var response []ServiceEligibilityResponse
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
t.Fatalf("failed to unmarshal response: %v", err)
}
// Find the patch test required service
var patchTestSvc *ServiceEligibilityResponse
for i := range response {
if response[i].ID == serviceID {
patchTestSvc = &response[i]
break
}
}
if patchTestSvc == nil {
t.Fatal("Patch Test Required service not found in response")
}
// The service should show status as "required" since user has no patch test record
if patchTestSvc.PatchTestStatus == nil {
t.Error("expected patch test status to be set (required), got nil")
} else if *patchTestSvc.PatchTestStatus != "required" {
t.Errorf("expected patch test status 'required', got '%s'", *patchTestSvc.PatchTestStatus)
}
}
+6
View File
@@ -542,6 +542,12 @@ func ChangePasswordHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
if req.NewPassword == req.CurrentPassword {
http.Error(w, "new password must be different from current password", http.StatusBadRequest)
return
}
var passwordHash string var passwordHash string
err := db.DB.QueryRow(r.Context(), `SELECT password_hash FROM users WHERE id = $1`, userID).Scan(&passwordHash) err := db.DB.QueryRow(r.Context(), `SELECT password_hash FROM users WHERE id = $1`, userID).Scan(&passwordHash)
if err != nil { if err != nil {
+16
View File
@@ -1,6 +1,22 @@
//go:build test //go:build test
// +build test // +build test
// Package user contains tests for user profile and account management endpoints.
//
// Test Coverage:
// - GetProfileHandler: GET /api/user/profile - Get authenticated user's profile
// - UpdateProfileHandler: PUT /api/user/profile - Update user profile (name, phone)
// - ChangePasswordHandler: PUT /api/user/password - Change user password
// - DeleteAccountHandler: DELETE /api/user/account - Delete user account
// - GetLoyaltyHandler: GET /api/user/loyalty - Get user's loyalty stamps and stats
// - Profile picture upload: POST /api/user/profile-picture - Upload profile picture
//
// Authentication: All endpoints require auth (401 for unauthenticated).
// Validation: Tests cover invalid inputs (missing fields, invalid phone, weak passwords).
package user
//go:build test
// +build test
package user package user
import ( import (
-2
View File
@@ -192,8 +192,6 @@ CREATE TABLE services (
duration_minutes INT NOT NULL, duration_minutes INT NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE, is_active BOOLEAN NOT NULL DEFAULT TRUE,
minimum_age_required INT NOT NULL DEFAULT 16, minimum_age_required INT NOT NULL DEFAULT 16,
requires_manual_pricing BOOLEAN NOT NULL DEFAULT FALSE,
requires_manual_duration BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by CHAR(12) created_by CHAR(12)
); );