From 817d5dd02172dd34eec7860052003431dea353ce Mon Sep 17 00:00:00 2001 From: Stephen Adamson Date: Mon, 2 Mar 2026 11:37:31 +0000 Subject: [PATCH] Test docstrings --- backend/handlers/admin/bookings_test.go | 48 +++++++- backend/handlers/admin/services_test.go | 19 ++- backend/handlers/admin/today_test.go | 18 ++- backend/handlers/admin/users_test.go | 26 ++-- backend/handlers/auth/auth_test.go | 77 ++++++++---- backend/handlers/bookings/bookings_test.go | 113 +++++++++++++----- backend/handlers/handlers_test.go | 19 ++- backend/handlers/portfolio/images_test.go | 16 +++ .../handlers/scheduling/scheduling_test.go | 42 +++++-- backend/handlers/services/services_test.go | 8 ++ backend/handlers/user/profile_test.go | 14 +++ 11 files changed, 304 insertions(+), 96 deletions(-) diff --git a/backend/handlers/admin/bookings_test.go b/backend/handlers/admin/bookings_test.go index b046ea3..90cfe0f 100644 --- a/backend/handlers/admin/bookings_test.go +++ b/backend/handlers/admin/bookings_test.go @@ -55,7 +55,8 @@ import ( // List Admin Bookings Tests // ============================================================================= -func TestAdminBookings_List(t *testing.T) { +// TestAdminBookings_List verifies that an admin can list all bookings in the +// system with pagination support. cleanup := setupTestDB(t) defer cleanup() @@ -110,7 +111,8 @@ func TestAdminBookings_List(t *testing.T) { } } -func TestAdminBookings_List_FilterByStatus(t *testing.T) { +// TestAdminBookings_List_FilterByStatus tests that an admin can filter +// bookings by status (e.g., pending, confirmed, completed). cleanup := setupTestDB(t) defer cleanup() @@ -173,7 +175,8 @@ func TestAdminBookings_List_FilterByStatus(t *testing.T) { // Admin Create Booking Tests // ============================================================================= -func TestAdminBookings_Create(t *testing.T) { +// TestAdminBookings_Create verifies that an admin can create a booking +// on behalf of a user. The booking is created with 'confirmed' status. cleanup := setupTestDB(t) defer cleanup() @@ -229,7 +232,9 @@ func TestAdminBookings_Create(t *testing.T) { } } -func TestAdminBookings_Create_InvalidInput(t *testing.T) { +// TestAdminBookings_Create_InvalidInput verifies that admin booking +// creation fails with HTTP 400 when required fields (userID, startTime, serviceIDs) +// are missing or invalid. cleanup := setupTestDB(t) defer cleanup() @@ -296,7 +301,8 @@ func TestAdminBookings_Create_InvalidInput(t *testing.T) { // Search Admin Bookings Tests // ============================================================================= -func TestAdminBookings_Search(t *testing.T) { +// TestAdminBookings_Search tests that an admin can search bookings by +// notes, customer name, or other text fields. cleanup := setupTestDB(t) defer cleanup() @@ -347,7 +353,8 @@ func TestAdminBookings_Search(t *testing.T) { } } -func TestAdminBookings_Search_MissingQuery(t *testing.T) { +// TestAdminBookings_Search_MissingQuery verifies that searching without +// a query parameter returns HTTP 400 Bad Request. cleanup := setupTestDB(t) defer cleanup() @@ -369,6 +376,8 @@ func TestAdminBookings_Search_MissingQuery(t *testing.T) { // Get Single Admin Booking Tests // ============================================================================= +// TestAdminBookings_Get verifies that an admin can retrieve a single booking by ID and that +// the response includes populated user and services relationships. func TestAdminBookings_Get(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -422,6 +431,7 @@ func TestAdminBookings_Get(t *testing.T) { } } +// TestAdminBookings_Get_NotFound verifies that requesting a non-existent booking returns 404. func TestAdminBookings_Get_NotFound(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -444,6 +454,7 @@ func TestAdminBookings_Get_NotFound(t *testing.T) { // Get User's Bookings Tests (Admin view) // ============================================================================= +// TestAdminBookings_GetUserBookings verifies that an admin can retrieve all bookings for a specific user. func TestAdminBookings_GetUserBookings(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -503,6 +514,8 @@ func TestAdminBookings_GetUserBookings(t *testing.T) { // Progress Booking Tests // ============================================================================= +// TestAdminBookings_Progress verifies that an admin can change a booking's status (e.g., pending to confirmed), +// and that the status is correctly updated in both the response and database. func TestAdminBookings_Progress(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -562,6 +575,7 @@ func TestAdminBookings_Progress(t *testing.T) { } } +// TestAdminBookings_Progress_InvalidStatus verifies that providing an invalid status value returns 400 Bad Request. func TestAdminBookings_Progress_InvalidStatus(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -602,6 +616,7 @@ func TestAdminBookings_Progress_InvalidStatus(t *testing.T) { } } +// TestAdminBookings_Progress_NotFound verifies that attempting to progress a non-existent booking returns 404. func TestAdminBookings_Progress_NotFound(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -628,6 +643,7 @@ func TestAdminBookings_Progress_NotFound(t *testing.T) { // Confirm Booking Tests // ============================================================================= +// TestAdminBookings_Confirm verifies that an admin can confirm a pending booking, updating its status to 'confirmed'. func TestAdminBookings_Confirm(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -675,6 +691,8 @@ func TestAdminBookings_Confirm(t *testing.T) { } } +// TestAdminBookings_Confirm_AlreadyConfirmed verifies idempotency - attempting to confirm +// an already-confirmed booking returns 404 Not Found. func TestAdminBookings_Confirm_AlreadyConfirmed(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -723,6 +741,7 @@ func TestAdminBookings_Confirm_AlreadyConfirmed(t *testing.T) { // Cancel Booking Tests // ============================================================================= +// TestAdminBookings_Cancel verifies that an admin can cancel a booking, updating its status to 'we_cancelled'. func TestAdminBookings_Cancel(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -775,6 +794,7 @@ func TestAdminBookings_Cancel(t *testing.T) { } } +// TestAdminBookings_Cancel_NotFound verifies that attempting to cancel a non-existent booking returns 404. func TestAdminBookings_Cancel_NotFound(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -797,6 +817,8 @@ func TestAdminBookings_Cancel_NotFound(t *testing.T) { // Non-Admin Tests // ============================================================================= +// TestAdminBookings_NonAdmin verifies that regular users receive 403 Forbidden when attempting +// to access any admin booking endpoints (list, get, create, progress, confirm, cancel, search). func TestAdminBookings_NonAdmin(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -871,6 +893,8 @@ func TestAdminBookings_NonAdmin(t *testing.T) { // Admin Booking Holiday Conflict Tests // ============================================================================= +// TestAdminBookings_Create_DuringHolidayHours_Rejected verifies that an admin cannot create a booking +// during hours marked as closed in the exceptional working hours (holiday) system. func TestAdminBookings_Create_DuringHolidayHours_Rejected(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -953,6 +977,8 @@ func TestAdminBookings_Create_DuringHolidayHours_Rejected(t *testing.T) { // Search Edge Case Tests // ============================================================================= +// TestAdminBookings_Search_CaseInsensitive verifies that the admin booking search is case-insensitive, +// matching booking notes regardless of uppercase/lowercase differences. func TestAdminBookings_Search_CaseInsensitive(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -1016,6 +1042,8 @@ func TestAdminBookings_Search_CaseInsensitive(t *testing.T) { } } +// TestAdminBookings_Search_NoResults verifies that searching with a query that matches no bookings +// returns an empty list with total count of 0. func TestAdminBookings_Search_NoResults(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -1067,6 +1095,8 @@ func TestAdminBookings_Search_NoResults(t *testing.T) { } } +// TestAdminBookings_Search_MultipleResults verifies that search returns all bookings whose notes +// contain the search query, with correct total count. func TestAdminBookings_Search_MultipleResults(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -1141,6 +1171,8 @@ func TestAdminBookings_Search_MultipleResults(t *testing.T) { // Admin List Edit Requests Tests // ============================================================================= +// TestAdminBookings_ListEditRequests verifies that an admin can list all pending edit requests +// for a specific booking, and that the response includes the correct count and request details. func TestAdminBookings_ListEditRequests(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -1231,6 +1263,8 @@ func TestAdminBookings_ListEditRequests(t *testing.T) { // Admin Deny Edit Request Tests // ============================================================================= +// TestAdminBookings_DenyEditRequest verifies that denying an edit request deletes the request +// while keeping the original booking time unchanged, and returns success. func TestAdminBookings_DenyEditRequest(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -1334,6 +1368,8 @@ func TestAdminBookings_DenyEditRequest(t *testing.T) { } } +// TestAdminBookings_ApproveEditRequest verifies that approving an edit request updates the booking's +// start_time to the requested time, deletes the edit request, and acknowledges the admin notification. func TestAdminBookings_ApproveEditRequest(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() diff --git a/backend/handlers/admin/services_test.go b/backend/handlers/admin/services_test.go index 920c6a0..7a09a52 100644 --- a/backend/handlers/admin/services_test.go +++ b/backend/handlers/admin/services_test.go @@ -27,7 +27,9 @@ import ( "crussell/mw" ) -func TestAdminServices_Create(t *testing.T) { +// TestAdminServices_Create verifies that an admin can create a new service +// with name, description, price, duration, and minimum age requirements. The new +// service is active by default and stored in the database. cleanup := setupTestDB(t) defer cleanup() @@ -72,7 +74,8 @@ func TestAdminServices_Create(t *testing.T) { } } -func TestAdminServices_List(t *testing.T) { +// TestAdminServices_List tests that an admin can retrieve all services, +// including inactive ones. This is useful for managing the full service catalog. cleanup := setupTestDB(t) defer cleanup() @@ -121,7 +124,9 @@ func TestAdminServices_List(t *testing.T) { } } -func TestAdminServices_Toggle(t *testing.T) { +// TestAdminServices_Toggle verifies that an admin can toggle a service's +// active status on/off. This is used to temporarily disable a service without +// deleting it from the system. cleanup := setupTestDB(t) defer cleanup() @@ -169,7 +174,9 @@ func TestAdminServices_Toggle(t *testing.T) { } } -func TestAdminServices_Delete(t *testing.T) { +// TestAdminServices_Delete tests that an admin can soft-delete a service +// by setting is_active to false. The service record remains but is hidden from +// customers. cleanup := setupTestDB(t) defer cleanup() @@ -202,7 +209,9 @@ func TestAdminServices_Delete(t *testing.T) { } } -func TestAdminServices_NonAdmin(t *testing.T) { +// TestAdminServices_NonAdmin verifies that non-admin users receive HTTP 403 +// Forbidden when attempting to create, list, toggle, or delete services. This +// ensures proper role-based access control. cleanup := setupTestDB(t) defer cleanup() diff --git a/backend/handlers/admin/today_test.go b/backend/handlers/admin/today_test.go index f4359ea..867482f 100644 --- a/backend/handlers/admin/today_test.go +++ b/backend/handlers/admin/today_test.go @@ -30,7 +30,8 @@ import ( "crussell/mw" ) -func TestAdminToday_CurrentNext(t *testing.T) { +// TestAdminToday_CurrentNext verifies that an admin can retrieve the currently +// in-progress booking and the next upcoming booking for the dashboard. cleanup := setupTestDB(t) defer cleanup() @@ -104,7 +105,8 @@ func TestAdminToday_CurrentNext(t *testing.T) { } } -func TestAdminToday_Appointments(t *testing.T) { +// TestAdminToday_Appointments tests that an admin can get a list of all +// bookings scheduled for today with their details. cleanup := setupTestDB(t) defer cleanup() @@ -178,7 +180,8 @@ func TestAdminToday_Appointments(t *testing.T) { } } -func TestAdminToday_PendingApprovals(t *testing.T) { +// TestAdminToday_PendingApprovals verifies that an admin can see all pending +// bookings that require approval/confirmation. cleanup := setupTestDB(t) defer cleanup() @@ -252,15 +255,18 @@ func TestAdminToday_PendingApprovals(t *testing.T) { } } -func TestAdminNotifications_List(t *testing.T) { +// TestAdminNotifications_List is skipped (WIP) - tests that an admin +// can list all their notifications. t.Skip("Skipping - WIP handler") } -func TestAdminNotifications_Acknowledge(t *testing.T) { +// TestAdminNotifications_Acknowledge is skipped (WIP) - tests that an +// admin can acknowledge a notification. t.Skip("Skipping - WIP handler") } -func TestAdminToday_NonAdmin(t *testing.T) { +// TestAdminToday_NonAdmin verifies that non-admin users receive HTTP 403 +// when accessing today's dashboard endpoints. cleanup := setupTestDB(t) defer cleanup() diff --git a/backend/handlers/admin/users_test.go b/backend/handlers/admin/users_test.go index 46dcfc0..eb4c654 100644 --- a/backend/handlers/admin/users_test.go +++ b/backend/handlers/admin/users_test.go @@ -28,7 +28,8 @@ import ( "crussell/mw" ) -func TestAdminUsers_List(t *testing.T) { +// TestAdminUsers_List verifies that an admin can list all users in the +// system with their details including account role and type. cleanup := setupTestDB(t) defer cleanup() @@ -65,7 +66,8 @@ func TestAdminUsers_List(t *testing.T) { } } -func TestAdminUsers_Get(t *testing.T) { +// TestAdminUsers_Get tests that an admin can retrieve detailed information +// about a specific user including their profile and account settings. cleanup := setupTestDB(t) defer cleanup() @@ -101,7 +103,8 @@ func TestAdminUsers_Get(t *testing.T) { } } -func TestAdminUsers_Get_NotFound(t *testing.T) { +// TestAdminUsers_Get_NotFound verifies that requesting details for a +// non-existent user returns HTTP 404 Not Found. cleanup := setupTestDB(t) defer cleanup() @@ -114,7 +117,9 @@ func TestAdminUsers_Get_NotFound(t *testing.T) { } } -func TestAdminUsers_PatchTests_Eligible(t *testing.T) { +// TestAdminUsers_PatchTests_Eligible tests that the system correctly +// identifies which services require patch tests and returns only those services +// the user is eligible for based on age requirements. cleanup := setupTestDB(t) defer cleanup() @@ -188,7 +193,9 @@ func TestAdminUsers_PatchTests_Eligible(t *testing.T) { } } -func TestAdminUsers_PatchTests_Eligible_WithExisting(t *testing.T) { +// TestAdminUsers_PatchTests_Eligible_WithExisting verifies that when a +// user already has a valid patch test on file, that service is filtered out +// from the eligible list (since they've already completed it). cleanup := setupTestDB(t) defer cleanup() @@ -273,7 +280,8 @@ func TestAdminUsers_PatchTests_Eligible_WithExisting(t *testing.T) { } } -func TestAdminUsers_AddPatchTest(t *testing.T) { +// TestAdminUsers_AddPatchTest verifies that an admin can record a patch +// test completion for a user, creating a user_patch_tests record. cleanup := setupTestDB(t) defer cleanup() @@ -359,7 +367,8 @@ func TestAdminUsers_AddPatchTest_InvalidPatchTest(t *testing.T) { } } -func TestAdminUsers_NonAdmin(t *testing.T) { +// TestAdminUsers_NonAdmin verifies that non-admin users receive HTTP 403 +// Forbidden when attempting to list users, get user details, or manage patch tests. cleanup := setupTestDB(t) defer cleanup() @@ -414,7 +423,8 @@ func TestAdminUsers_NonAdmin(t *testing.T) { -func TestAdminUsers_Get_Success(t *testing.T) { +// TestAdminUsers_Get_Success is an additional test verifying admin can +// retrieve user details including ID, name, email, and account role. cleanup := setupTestDB(t) defer cleanup() diff --git a/backend/handlers/auth/auth_test.go b/backend/handlers/auth/auth_test.go index ab46d81..c6d1e1b 100644 --- a/backend/handlers/auth/auth_test.go +++ b/backend/handlers/auth/auth_test.go @@ -92,7 +92,10 @@ func parseResponseBody(w *httptest.ResponseRecorder, dest interface{}) error { // Register Handler Tests // ============================================================================= -func TestRegister_Success(t *testing.T) { +// TestRegister_Success verifies that a new user can successfully register with +// valid credentials. It tests the happy path: valid name, email, password, +// UK phone number, date of birth, and policy agreement. The test confirms +// the user is created in the database with status 201. cleanup := setupTestDB(t) defer cleanup() @@ -126,7 +129,9 @@ func TestRegister_Success(t *testing.T) { db.DB.Exec(context.Background(), "DELETE FROM users WHERE id = $1", userID) } -func TestRegister_InvalidInput_MissingFields(t *testing.T) { +// TestRegister_InvalidInput_MissingFields tests that registration fails with +// HTTP 400 when required fields are missing. It covers missing firstName, +// lastName, email, phone, dateOfBirth, and when policy agreement is not given. cleanup := setupTestDB(t) defer cleanup() @@ -172,7 +177,8 @@ func TestRegister_InvalidInput_MissingFields(t *testing.T) { } } -func TestRegister_InvalidInput_InvalidEmail(t *testing.T) { +// TestRegister_InvalidInput_InvalidEmail verifies that registration fails +// with HTTP 400 when an invalid email format is provided (e.g., "not-an-email"). cleanup := setupTestDB(t) defer cleanup() @@ -195,7 +201,8 @@ func TestRegister_InvalidInput_InvalidEmail(t *testing.T) { } } -func TestRegister_InvalidInput_InvalidPhone(t *testing.T) { +// TestRegister_InvalidInput_InvalidPhone tests that registration fails +// with HTTP 400 when an invalid UK phone number is provided (e.g., too short). cleanup := setupTestDB(t) defer cleanup() @@ -219,7 +226,8 @@ func TestRegister_InvalidInput_InvalidPhone(t *testing.T) { } // TestRegister_ValidUKPhoneNumbers tests all valid UK mobile phone formats -func TestRegister_ValidUKPhoneNumbers(t *testing.T) { +// TestRegister_ValidUKPhoneNumbers verifies that registration accepts all +// valid UK mobile phone formats including 07x numbers and E.164 format (+447...). cleanup := setupTestDB(t) defer cleanup() @@ -263,7 +271,9 @@ func TestRegister_ValidUKPhoneNumbers(t *testing.T) { } // TestRegister_InvalidPhoneNumbers tests various invalid phone formats -func TestRegister_InvalidPhoneNumbers(t *testing.T) { +// TestRegister_InvalidPhoneNumbers verifies that registration rejects +// invalid phone numbers including too short, invalid formats, US numbers, and +// numbers with special characters. cleanup := setupTestDB(t) defer cleanup() @@ -303,7 +313,8 @@ func TestRegister_InvalidPhoneNumbers(t *testing.T) { } } -func TestRegister_InvalidInput_Under16(t *testing.T) { +// TestRegister_InvalidInput_Under16 tests that users under 16 years old cannot +// register. The system enforces a minimum age of 16 for account creation. cleanup := setupTestDB(t) defer cleanup() @@ -329,7 +340,8 @@ func TestRegister_InvalidInput_Under16(t *testing.T) { } } -func TestRegister_DuplicateEmail(t *testing.T) { +// TestRegister_DuplicateEmail verifies that attempting to register with +// an email that already exists returns HTTP 409 Conflict. cleanup := setupTestDB(t) defer cleanup() @@ -366,7 +378,8 @@ func TestRegister_DuplicateEmail(t *testing.T) { // Login Handler Tests // ============================================================================= -func TestLogin_Success(t *testing.T) { +// TestLogin_Success tests that an existing user can successfully log in +// with correct email and password, receiving a JWT token in the response. cleanup := setupTestDB(t) defer cleanup() @@ -402,7 +415,8 @@ func TestLogin_Success(t *testing.T) { } } -func TestLogin_InvalidCredentials_WrongPassword(t *testing.T) { +// TestLogin_InvalidCredentials_WrongPassword verifies that login fails with +// HTTP 401 when the correct email exists but the password is incorrect. cleanup := setupTestDB(t) defer cleanup() @@ -427,7 +441,8 @@ func TestLogin_InvalidCredentials_WrongPassword(t *testing.T) { } } -func TestLogin_InvalidCredentials_NonExistentEmail(t *testing.T) { +// TestLogin_InvalidCredentials_NonExistentEmail verifies that login fails +// with HTTP 401 when the email does not exist in the database. cleanup := setupTestDB(t) defer cleanup() @@ -449,7 +464,8 @@ func TestLogin_InvalidCredentials_NonExistentEmail(t *testing.T) { // Refresh Token Handler Tests // ============================================================================= -func TestRefreshToken_Success(t *testing.T) { +// TestRefreshToken_Success tests that a valid JWT token can be refreshed +// to obtain a new token with extended expiry. cleanup := setupTestDB(t) defer cleanup() @@ -493,7 +509,8 @@ func TestRefreshToken_Success(t *testing.T) { } } -func TestRefreshToken_Unauthorized_NoToken(t *testing.T) { +// TestRefreshToken_Unauthorized_NoToken verifies that attempting to refresh +// a token without providing one results in HTTP 401 Unauthorized. cleanup := setupTestDB(t) defer cleanup() @@ -515,7 +532,9 @@ func TestRefreshToken_Unauthorized_NoToken(t *testing.T) { // Verify Generate Handler Tests // ============================================================================= -func TestVerifyGenerate_ValidEmail(t *testing.T) { +// TestVerifyGenerate_ValidEmail tests that a verification code can be +// generated for an existing user email. The code is stored in the database +// for subsequent verification. cleanup := setupTestDB(t) defer cleanup() @@ -559,7 +578,9 @@ func TestVerifyGenerate_ValidEmail(t *testing.T) { db.DB.Exec(context.Background(), "DELETE FROM verification_codes WHERE user_id = $1", userID) } -func TestVerifyGenerate_NonExistentEmail(t *testing.T) { +// TestVerifyGenerate_NonExistentEmail verifies that the verification code +// generation endpoint returns HTTP 200 even for non-existent emails. This is +// a security measure to prevent email enumeration attacks. cleanup := setupTestDB(t) defer cleanup() @@ -591,7 +612,9 @@ func TestVerifyGenerate_NonExistentEmail(t *testing.T) { // Verify Check Handler Tests // ============================================================================= -func TestVerifyCheck_ValidCode(t *testing.T) { +// TestVerifyCheck_ValidCode tests that a valid, non-expired, unused +// verification code successfully verifies a user's email and updates their +// account role from unverified_email to verified_email. cleanup := setupTestDB(t) defer cleanup() @@ -643,7 +666,8 @@ func TestVerifyCheck_ValidCode(t *testing.T) { } } -func TestVerifyCheck_InvalidCode(t *testing.T) { +// TestVerifyCheck_InvalidCode verifies that attempting to verify with +// a non-existent code returns HTTP 400 Bad Request. cleanup := setupTestDB(t) defer cleanup() @@ -660,7 +684,8 @@ func TestVerifyCheck_InvalidCode(t *testing.T) { } } -func TestVerifyCheck_ExpiredCode(t *testing.T) { +// TestVerifyCheck_ExpiredCode tests that verification fails with HTTP 400 +// when the code has expired (past its expires_at timestamp). cleanup := setupTestDB(t) defer cleanup() @@ -699,7 +724,8 @@ func TestVerifyCheck_ExpiredCode(t *testing.T) { // Additional Edge Case Tests // ============================================================================= -func TestLogin_InvalidRequest(t *testing.T) { +// TestLogin_InvalidRequest verifies that sending malformed JSON to the login +// endpoint returns HTTP 400 Bad Request. cleanup := setupTestDB(t) defer cleanup() @@ -716,7 +742,8 @@ func TestLogin_InvalidRequest(t *testing.T) { } } -func TestRegister_NameTooLong(t *testing.T) { +// TestRegister_NameTooLong tests that registration fails when the first name +// exceeds 50 characters (the maximum allowed length). cleanup := setupTestDB(t) defer cleanup() @@ -741,7 +768,8 @@ func TestRegister_NameTooLong(t *testing.T) { } } -func TestRegister_InvalidNameCharacters(t *testing.T) { +// TestRegister_InvalidNameCharacters verifies that registration fails when +// names contain invalid characters (e.g., numbers). cleanup := setupTestDB(t) defer cleanup() @@ -765,7 +793,8 @@ func TestRegister_InvalidNameCharacters(t *testing.T) { } } -func TestVerifyCheck_AlreadyUsed(t *testing.T) { +// TestVerifyCheck_AlreadyUsed tests that attempting to verify with a code +// that has already been used returns HTTP 403 Forbidden. cleanup := setupTestDB(t) defer cleanup() @@ -805,7 +834,9 @@ func TestVerifyCheck_AlreadyUsed(t *testing.T) { } } -func TestVerifyCheck_RoleChangeToVerified(t *testing.T) { +// TestVerifyCheck_RoleChangeToVerified confirms that after a successful +// verification, the user's account_role changes from unverified_email to +// verified_email, granting them full account access. cleanup := setupTestDB(t) defer cleanup() diff --git a/backend/handlers/bookings/bookings_test.go b/backend/handlers/bookings/bookings_test.go index 1511349..48882e8 100644 --- a/backend/handlers/bookings/bookings_test.go +++ b/backend/handlers/bookings/bookings_test.go @@ -207,7 +207,9 @@ func parseResponseBody(w *httptest.ResponseRecorder, dest interface{}) error { // Create Booking Tests // ============================================================================= -func TestBookings_Create(t *testing.T) { +// TestBookings_Create tests that a user can successfully create a new booking +// with a valid future time and at least one service. The test verifies the +// booking is created in the database and associated with the correct user. cleanup := setupTestDB(t) defer cleanup() @@ -269,7 +271,8 @@ func TestBookings_Create(t *testing.T) { } } -func TestBookings_Create_InvalidInput(t *testing.T) { +// TestBookings_Create_InvalidInput verifies that booking creation fails +// with HTTP 400 when required fields are missing: start time or service IDs. cleanup := setupTestDB(t) defer cleanup() @@ -330,7 +333,9 @@ func TestBookings_Create_InvalidInput(t *testing.T) { // List Bookings Tests // ============================================================================= -func TestBookings_List(t *testing.T) { +// TestBookings_List tests that a user can retrieve their list of bookings. +// The test verifies the response includes the correct total count and that +// bookings are properly returned. cleanup := setupTestDB(t) defer cleanup() @@ -383,7 +388,9 @@ func TestBookings_List(t *testing.T) { } } -func TestBookings_List_FilterByStatus(t *testing.T) { +// TestBookings_List_FilterByStatus tests that booking list can be filtered +// by status (e.g., pending, completed). It verifies that non-matching statuses +// return empty results. cleanup := setupTestDB(t) defer cleanup() @@ -452,7 +459,8 @@ func TestBookings_List_FilterByStatus(t *testing.T) { // Get Single Booking Tests // ============================================================================= -func TestBookings_Get(t *testing.T) { +// TestBookings_Get tests that a user can retrieve a single booking by its ID. +// The test verifies the booking details including services are returned. cleanup := setupTestDB(t) defer cleanup() @@ -505,7 +513,8 @@ func TestBookings_Get(t *testing.T) { } } -func TestBookings_Get_NotFound(t *testing.T) { +// TestBookings_Get_NotFound verifies that requesting a non-existent booking +// returns HTTP 404 Not Found. cleanup := setupTestDB(t) defer cleanup() @@ -532,7 +541,9 @@ func TestBookings_Get_NotFound(t *testing.T) { } } -func TestBookings_Get_AccessDenied(t *testing.T) { +// TestBookings_Get_AccessDenied tests that a user cannot access another user's +// booking. The test creates two users, one creates a booking, and the other +// attempts to access it - expecting HTTP 404 (not found/access denied). cleanup := setupTestDB(t) defer cleanup() @@ -578,7 +589,9 @@ func TestBookings_Get_AccessDenied(t *testing.T) { // Get Calendar Tests // ============================================================================= -func TestBookings_GetCalendar(t *testing.T) { +// TestBookings_GetCalendar tests that a user can export their booking +// as an ICS calendar file. It verifies the response has the correct +// text/calendar Content-Type and contains ICS-formatted data. cleanup := setupTestDB(t) defer cleanup() @@ -638,7 +651,8 @@ func TestBookings_GetCalendar(t *testing.T) { } } -func TestBookings_GetCalendar_NotFound(t *testing.T) { +// TestBookings_GetCalendar_NotFound verifies that attempting to export +// a non-existent booking to calendar returns HTTP 404. cleanup := setupTestDB(t) defer cleanup() @@ -669,7 +683,8 @@ func TestBookings_GetCalendar_NotFound(t *testing.T) { // Edit Booking Tests // ============================================================================= -func TestBookings_Edit(t *testing.T) { +// TestBookings_Edit tests that a user can modify the start time of +// their existing booking. The test verifies the time is updated in the DB. cleanup := setupTestDB(t) defer cleanup() @@ -733,7 +748,8 @@ func TestBookings_Edit(t *testing.T) { } } -func TestBookings_Edit_InvalidInput(t *testing.T) { +// TestBookings_Edit_InvalidInput verifies that editing fails with HTTP 400 +// when the start time is missing or is in the past. cleanup := setupTestDB(t) defer cleanup() @@ -793,7 +809,8 @@ func TestBookings_Edit_InvalidInput(t *testing.T) { } } -func TestBookings_Edit_NotFound(t *testing.T) { +// TestBookings_Edit_NotFound verifies that editing a non-existent +// booking returns HTTP 404. cleanup := setupTestDB(t) defer cleanup() @@ -828,7 +845,9 @@ func TestBookings_Edit_NotFound(t *testing.T) { // Delete Booking Tests // ============================================================================= -func TestBookings_Delete(t *testing.T) { +// TestBookings_Delete tests that a user can delete (cancel) their booking. +// For bookings without payments, it performs a hard delete. The test verifies +// the booking is removed from the database. cleanup := setupTestDB(t) defer cleanup() @@ -879,7 +898,10 @@ func TestBookings_Delete(t *testing.T) { } } -func TestBookings_Delete_WithReason(t *testing.T) { +// TestBookings_Delete_WithReason verifies that cancelling a booking with +// an associated payment requires a reason (client_cancelled). Without a reason, +// the request fails with HTTP 400. With a reason, the booking is soft-deleted +// (status changed to client_cancelled). cleanup := setupTestDB(t) defer cleanup() @@ -947,7 +969,8 @@ func TestBookings_Delete_WithReason(t *testing.T) { } } -func TestBookings_Delete_NotFound(t *testing.T) { +// TestBookings_Delete_NotFound verifies that deleting a non-existent +// booking returns HTTP 404. cleanup := setupTestDB(t) defer cleanup() @@ -978,7 +1001,9 @@ func TestBookings_Delete_NotFound(t *testing.T) { // Unauthorized Tests // ============================================================================= -func TestBookings_Unauthorized(t *testing.T) { +// TestBookings_Unauthorized tests that all booking endpoints require +// authentication. It verifies that requests without a token are rejected with +// HTTP 401 for protected endpoints. cleanup := setupTestDB(t) defer cleanup() @@ -1093,7 +1118,8 @@ func TestBookings_Unauthorized(t *testing.T) { // Additional Edge Case Tests // ============================================================================= -func TestBookings_List_Empty(t *testing.T) { +// TestBookings_List_Empty tests that listing bookings for a user with no +// bookings returns an empty list with total 0. cleanup := setupTestDB(t) defer cleanup() @@ -1133,7 +1159,8 @@ func TestBookings_List_Empty(t *testing.T) { } } -func TestBookings_Get_InvalidBookingID(t *testing.T) { +// TestBookings_Get_InvalidBookingID verifies that using an invalid +// booking ID format returns HTTP 404 or 400. cleanup := setupTestDB(t) defer cleanup() @@ -1161,7 +1188,8 @@ func TestBookings_Get_InvalidBookingID(t *testing.T) { } } -func TestBookings_Create_PastDate(t *testing.T) { +// TestBookings_Create_PastDate verifies that creating a booking with a +// past start time fails with HTTP 400 Bad Request. cleanup := setupTestDB(t) defer cleanup() @@ -1199,7 +1227,9 @@ func TestBookings_Create_PastDate(t *testing.T) { } } -func TestBookings_Create_Within48HourDepositRequired(t *testing.T) { +// TestBookings_Create_Within48HourDepositRequired tests that when a booking +// is made within 48 hours and the user has deposits_required > 0, the booking +// should have deposit_required=true. With deposits_required=0, no deposit needed. cleanup := setupTestDB(t) defer cleanup() @@ -1246,7 +1276,9 @@ func TestBookings_Create_Within48HourDepositRequired(t *testing.T) { } } -func TestBookings_Create_MultipleServices(t *testing.T) { +// TestBookings_Create_MultipleServices verifies that a booking can include +// multiple services at once, and all services are properly associated with +// the booking in the database. cleanup := setupTestDB(t) defer cleanup() @@ -1309,7 +1341,8 @@ var _ = mw.UserIDKey // Auth and Security Tests // ============================================================================= -func TestBookings_Get_NoAuthHeader(t *testing.T) { +// TestBookings_Get_NoAuthHeader confirms that accessing a booking without +// an Authorization header returns HTTP 401 Unauthorized. cleanup := setupTestDB(t) defer cleanup() @@ -1350,7 +1383,9 @@ func TestBookings_Get_NoAuthHeader(t *testing.T) { // Calendar Export Tests - ICS Format Validation // ============================================================================= -func TestBookings_GetCalendar_ValidICS(t *testing.T) { +// TestBookings_GetCalendar_ValidICS validates that the ICS calendar export +// contains all required fields: BEGIN:VCALENDAR, END:VCALENDAR, BEGIN:VEVENT, +// END:VEVENT, DTSTART, DTEND, and SUMMARY. cleanup := setupTestDB(t) defer cleanup() @@ -1421,7 +1456,9 @@ func TestBookings_GetCalendar_ValidICS(t *testing.T) { // Cancellation and Notification Tests // ============================================================================= -func TestUserCancelBooking_ConfirmedCreatesNotification(t *testing.T) { +// TestUserCancelBooking_ConfirmedCreatesNotification verifies that when a +// user cancels a confirmed booking (one with payments), an admin notification +// is created to alert staff of the cancellation. cleanup := setupTestDB(t) defer cleanup() @@ -1490,7 +1527,9 @@ func TestUserCancelBooking_ConfirmedCreatesNotification(t *testing.T) { } } -func TestUserCancelBooking_PendingNoNotification(t *testing.T) { +// TestUserCancelBooking_PendingNoNotification verifies that cancelling a +// pending booking (one without payments) does NOT create an admin notification, +// as pending cancellations don't require staff attention. cleanup := setupTestDB(t) defer cleanup() @@ -1556,7 +1595,9 @@ func TestUserCancelBooking_PendingNoNotification(t *testing.T) { // TestUserCancelBooking_TransactionIntegrity verifies that if any part of the // cancellation transaction fails, the booking status is NOT changed (rollback behavior) -func TestUserCancelBooking_TransactionIntegrity(t *testing.T) { +// TestUserCancelBooking_TransactionIntegrity tests that the cancellation +// transaction properly commits - verifying the booking status actually changes +// after a successful cancellation request. cleanup := setupTestDB(t) defer cleanup() @@ -1630,7 +1671,9 @@ func TestUserCancelBooking_TransactionIntegrity(t *testing.T) { // TestCreateEditRequest tests that creating an edit request creates an admin notification -func TestCreateEditRequest(t *testing.T) { +// TestCreateEditRequest verifies that a user can request an edit to their +// confirmed booking (e.g., change time). This creates a booking_edit_request record +// and generates an admin notification for staff review. cleanup := setupTestDB(t) defer cleanup() @@ -1704,7 +1747,8 @@ func TestCreateEditRequest(t *testing.T) { } // TestDeleteEditRequest tests that user deleting their edit request deletes the admin notification -func TestDeleteEditRequest(t *testing.T) { +// TestDeleteEditRequest tests that an admin can delete/remove a pending +// edit request from a booking without affecting the original booking data. cleanup := setupTestDB(t) defer cleanup() @@ -1795,7 +1839,9 @@ func TestDeleteEditRequest(t *testing.T) { } // TestAdminApproveEditRequest tests that admin approving acknowledges the notification (not deletes) -func TestAdminApproveEditRequest(t *testing.T) { +// TestAdminApproveEditRequest verifies that an admin can approve a user's +// edit request. This updates the booking's start time to the requested time and +// marks the edit request as handled. cleanup := setupTestDB(t) defer cleanup() @@ -1912,7 +1958,8 @@ func TestAdminApproveEditRequest(t *testing.T) { } // TestAdminRejectEditRequest tests that admin rejecting acknowledges the notification (not deletes) -func TestAdminRejectEditRequest(t *testing.T) { +// TestAdminRejectEditRequest tests that an admin can reject an edit request. +// The original booking remains unchanged and the edit request is deleted. cleanup := setupTestDB(t) defer cleanup() @@ -2026,7 +2073,8 @@ func TestAdminRejectEditRequest(t *testing.T) { // TestBookings_RequestEdit_BookingNotFound tests that requesting an edit for a non-existent booking returns 404 -func TestBookings_RequestEdit_BookingNotFound(t *testing.T) { +// TestBookings_RequestEdit_BookingNotFound verifies that requesting an edit +// for a non-existent booking returns HTTP 404 Not Found. cleanup := setupTestDB(t) defer cleanup() @@ -2057,7 +2105,8 @@ func TestBookings_RequestEdit_BookingNotFound(t *testing.T) { } // TestBookings_RequestEdit_AlreadyHasPending tests that a user cannot create a second edit request while one already exists -func TestBookings_RequestEdit_AlreadyHasPending(t *testing.T) { +// TestBookings_RequestEdit_AlreadyHasPending tests that a user cannot create +// a new edit request if one is already pending for the same booking. cleanup := setupTestDB(t) defer cleanup() diff --git a/backend/handlers/handlers_test.go b/backend/handlers/handlers_test.go index f15bb07..7ef034e 100644 --- a/backend/handlers/handlers_test.go +++ b/backend/handlers/handlers_test.go @@ -29,7 +29,9 @@ import ( "crussell/testutils/testdb" ) -func TestHealthCheck(t *testing.T) { +// TestHealthCheck verifies the health check endpoint returns HTTP 200 OK. +// This test ensures the basic HTTP server is responding and the health +// check handler is properly wired up to return a status response. handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(`{"status":"ok"}`)) @@ -49,7 +51,10 @@ func TestHealthCheck(t *testing.T) { } } -func TestRequireAuthMiddleware(t *testing.T) { +// TestRequireAuthMiddleware verifies the JWT authentication middleware correctly +// blocks unauthenticated requests and allows valid JWT tokens through. +// It tests three scenarios: missing auth header (401), valid token (200 with +// user context), and invalid token (401). handler := mw.RequireAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { userID, _ := r.Context().Value(mw.UserIDKey).(string) role, _ := r.Context().Value(mw.UserRoleKey).(string) @@ -104,7 +109,10 @@ func TestRequireAuthMiddleware(t *testing.T) { }) } -func TestRequireRoleMiddleware(t *testing.T) { +// TestRequireRoleMiddleware verifies the role-based access control middleware +// correctly blocks non-admin users (403) and allows admin users (200) to access +// protected resources. It chains RequireAuth before RequireRole to populate +// the role in the request context. // Chain RequireAuth before RequireRole to set the role in context // RequireRole expects role to be in context, but that's only set by RequireAuth adminOnlyHandler := mw.RequireAuth(mw.RequireRole("admin")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -142,7 +150,10 @@ func TestRequireRoleMiddleware(t *testing.T) { }) } -func TestIntegration_UserFlow(t *testing.T) { +// TestIntegration_UserFlow tests the full authentication flow end-to-end, +// verifying that JWT tokens are properly validated, user ID is extracted from +// the token, and context values are correctly propagated to handlers. +// This is an integration test that validates the complete middleware chain. if testing.Short() { t.Skip("skipping integration test in short mode") } diff --git a/backend/handlers/portfolio/images_test.go b/backend/handlers/portfolio/images_test.go index f9ffa44..8169b4d 100644 --- a/backend/handlers/portfolio/images_test.go +++ b/backend/handlers/portfolio/images_test.go @@ -94,6 +94,7 @@ func makeRequestWithContext(handler http.HandlerFunc, method, path string, body // List Images Tests // ============================================================================= +// TestPortfolio_ListImages verifies that listing portfolio images returns all images in the database. func TestPortfolio_ListImages(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -126,6 +127,7 @@ func TestPortfolio_ListImages(t *testing.T) { } } +// TestPortfolio_ListImages_WithTagFilter verifies that images can be filtered by tag using the 'tag' query parameter. func TestPortfolio_ListImages_WithTagFilter(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -158,6 +160,7 @@ func TestPortfolio_ListImages_WithTagFilter(t *testing.T) { } } +// TestPortfolio_ListImages_Empty verifies that an empty database returns an empty images array (not an error). func TestPortfolio_ListImages_Empty(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -183,6 +186,7 @@ func TestPortfolio_ListImages_Empty(t *testing.T) { // List Tags Tests // ============================================================================= +// TestPortfolio_ListTags verifies that listing tags returns all unique tags from portfolio images. func TestPortfolio_ListTags(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -216,6 +220,7 @@ func TestPortfolio_ListTags(t *testing.T) { } } +// TestPortfolio_ListTags_WithQuery verifies that tags can be filtered by a query string. func TestPortfolio_ListTags_WithQuery(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -249,6 +254,7 @@ func TestPortfolio_ListTags_WithQuery(t *testing.T) { } } +// TestPortfolio_ListTags_Empty verifies that an empty database returns an empty tags array. func TestPortfolio_ListTags_Empty(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -274,6 +280,7 @@ func TestPortfolio_ListTags_Empty(t *testing.T) { // List Filters Tests // ============================================================================= +// TestPortfolio_ListFilters verifies that filter categories are derived from tags (e.g., 'nature', 'color' from 'nature:forest'). func TestPortfolio_ListFilters(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -306,6 +313,7 @@ func TestPortfolio_ListFilters(t *testing.T) { } } +// TestPortfolio_ListFilters_Empty verifies that an empty database returns an empty filters array. func TestPortfolio_ListFilters_Empty(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -331,6 +339,7 @@ func TestPortfolio_ListFilters_Empty(t *testing.T) { // Get Image Tests // ============================================================================= +// TestPortfolio_GetImage verifies that a single image can be retrieved by its timestamp ID. func TestPortfolio_GetImage(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -374,6 +383,7 @@ func TestPortfolio_GetImage(t *testing.T) { } } +// TestPortfolio_GetImage_NotFound verifies that requesting a non-existent image returns 404. func TestPortfolio_GetImage_NotFound(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -390,6 +400,7 @@ func TestPortfolio_GetImage_NotFound(t *testing.T) { // Upload Image Tests (Admin Only) // ============================================================================= +// TestPortfolio_Upload_Admin verifies that an admin user passes the authentication check for image upload. func TestPortfolio_Upload_Admin(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -408,6 +419,7 @@ func TestPortfolio_Upload_Admin(t *testing.T) { } } +// TestPortfolio_Upload_NonAdmin verifies that non-admin users receive 403 Forbidden on image upload attempts. func TestPortfolio_Upload_NonAdmin(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -420,6 +432,7 @@ func TestPortfolio_Upload_NonAdmin(t *testing.T) { } } +// TestPortfolio_Upload_Unauthenticated verifies that unauthenticated requests receive 401 Unauthorized on image upload. func TestPortfolio_Upload_Unauthenticated(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -436,6 +449,7 @@ func TestPortfolio_Upload_Unauthenticated(t *testing.T) { // Delete Image Tests (Admin Only) // ============================================================================= +// TestPortfolio_Delete_Admin verifies that an admin user passes the authentication check for image deletion. func TestPortfolio_Delete_Admin(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -461,6 +475,7 @@ func TestPortfolio_Delete_Admin(t *testing.T) { } } +// TestPortfolio_Delete_NonAdmin verifies that non-admin users receive 403 Forbidden on image deletion attempts. func TestPortfolio_Delete_NonAdmin(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -484,6 +499,7 @@ func TestPortfolio_Delete_NonAdmin(t *testing.T) { } } +// TestPortfolio_Delete_Unauthenticated verifies that unauthenticated requests receive 401 Unauthorized on image deletion. func TestPortfolio_Delete_Unauthenticated(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() diff --git a/backend/handlers/scheduling/scheduling_test.go b/backend/handlers/scheduling/scheduling_test.go index 4dd25d1..c021c65 100644 --- a/backend/handlers/scheduling/scheduling_test.go +++ b/backend/handlers/scheduling/scheduling_test.go @@ -122,7 +122,9 @@ func makeAuthRequest(handler http.Handler, method, path, token string, body inte // --- Tests for GetDefaultHours --- -func TestScheduling_GetDefaultHours(t *testing.T) { +// TestScheduling_GetDefaultHours verifies that the default weekly working hours +// can be retrieved. The test checks that all 7 days are returned with correct +// opening times, closing times, and is_open status. cleanup := setupTestDB(t) defer cleanup() @@ -167,7 +169,9 @@ func TestScheduling_GetDefaultHours(t *testing.T) { // --- Tests for UpdateDefaultHours --- -func TestScheduling_UpdateDefaultHours_Admin(t *testing.T) { +// TestScheduling_UpdateDefaultHours_Admin tests that an admin can update +// the default weekly working hours. The new schedule is persisted to the +// database and returned on subsequent requests. cleanup := setupTestDB(t) defer cleanup() @@ -211,7 +215,8 @@ func TestScheduling_UpdateDefaultHours_Admin(t *testing.T) { } } -func TestScheduling_UpdateDefaultHours_NonAdmin(t *testing.T) { +// TestScheduling_UpdateDefaultHours_NonAdmin verifies that non-admin users +// receive HTTP 403 Forbidden when attempting to update default hours. cleanup := setupTestDB(t) defer cleanup() @@ -237,7 +242,8 @@ func TestScheduling_UpdateDefaultHours_NonAdmin(t *testing.T) { // --- Tests for ListExceptionalGroups --- -func TestScheduling_ListExceptionalGroups(t *testing.T) { +// TestScheduling_ListExceptionalGroups verifies that admins can list all +// exceptional working hours groups (holidays, special events). cleanup := setupTestDB(t) defer cleanup() @@ -273,7 +279,8 @@ func TestScheduling_ListExceptionalGroups(t *testing.T) { // --- Tests for CreateExceptionalGroup --- -func TestScheduling_CreateExceptionalGroup_Admin(t *testing.T) { +// TestScheduling_CreateExceptionalGroup_Admin tests that an admin can +// create a new exceptional working hours group with specific hours for each day. cleanup := setupTestDB(t) defer cleanup() @@ -314,7 +321,8 @@ func TestScheduling_CreateExceptionalGroup_Admin(t *testing.T) { } } -func TestScheduling_CreateExceptionalGroup_NonAdmin(t *testing.T) { +// TestScheduling_CreateExceptionalGroup_NonAdmin verifies that non-admin +// users receive HTTP 403 when attempting to create exceptional groups. cleanup := setupTestDB(t) defer cleanup() @@ -344,7 +352,9 @@ func TestScheduling_CreateExceptionalGroup_NonAdmin(t *testing.T) { // --- Tests for DeleteExceptionalGroup --- -func TestScheduling_DeleteExceptionalGroup_Admin(t *testing.T) { +// TestScheduling_DeleteExceptionalGroup_Admin tests that an admin can delete +// an exceptional working hours group. This removes the group and its associated +// hours from the system. cleanup := setupTestDB(t) defer cleanup() @@ -383,7 +393,8 @@ func TestScheduling_DeleteExceptionalGroup_Admin(t *testing.T) { } } -func TestScheduling_DeleteExceptionalGroup_NonAdmin(t *testing.T) { +// TestScheduling_DeleteExceptionalGroup_NonAdmin verifies that non-admin +// users receive HTTP 403 when attempting to delete exceptional groups. cleanup := setupTestDB(t) defer cleanup() @@ -402,7 +413,9 @@ func TestScheduling_DeleteExceptionalGroup_NonAdmin(t *testing.T) { // --- Tests for GetWorkingHours --- -func TestScheduling_GetWorkingHours(t *testing.T) { +// TestScheduling_GetWorkingHours verifies that working hours can be +// retrieved for a given date range. The response includes whether hours come +// from default schedule or exceptional groups. cleanup := setupTestDB(t) defer cleanup() @@ -435,7 +448,9 @@ func TestScheduling_GetWorkingHours(t *testing.T) { // --- Tests for GetAvailableHours --- -func TestScheduling_GetAvailableHours(t *testing.T) { +// TestScheduling_GetAvailableHours tests that available appointment +// slots can be calculated for a date range based on working hours and service +// durations. cleanup := setupTestDB(t) defer cleanup() @@ -470,7 +485,9 @@ func TestScheduling_GetAvailableHours(t *testing.T) { // --- Tests for UpdateExceptionalApplications --- -func TestScheduling_UpdateExceptionalApplications_Admin(t *testing.T) { +// TestScheduling_UpdateExceptionalApplications_Admin verifies that an +// admin can apply an exceptional hours group to specific weeks, activating +// holiday schedules for those periods. cleanup := setupTestDB(t) defer cleanup() @@ -513,7 +530,8 @@ func TestScheduling_UpdateExceptionalApplications_Admin(t *testing.T) { } } -func TestScheduling_UpdateExceptionalApplications_NonAdmin(t *testing.T) { +// TestScheduling_UpdateExceptionalApplications_NonAdmin verifies that +// non-admin users receive HTTP 403 when attempting to apply exceptional hours. cleanup := setupTestDB(t) defer cleanup() diff --git a/backend/handlers/services/services_test.go b/backend/handlers/services/services_test.go index f52c77b..7de4954 100644 --- a/backend/handlers/services/services_test.go +++ b/backend/handlers/services/services_test.go @@ -111,6 +111,8 @@ func findLastSegment(path, prefix string) int { return -1 } +// TestServices_ListAll verifies that listing all services returns only active services, +// filtering out inactive services from the response. func TestServices_ListAll(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -157,6 +159,8 @@ func TestServices_ListAll(t *testing.T) { } } +// TestServices_EligibleForUser_AgeFilter verifies that eligible services are filtered based on the user's age, +// excluding services with minimum_age_required higher than the user's age. func TestServices_EligibleForUser_AgeFilter(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -210,6 +214,8 @@ func TestServices_EligibleForUser_AgeFilter(t *testing.T) { } } +// TestServices_EligibleForUser_PatchTest verifies that services requiring patch tests include a +// PatchTestStatus field set to 'ok' when the user has completed a valid patch test for that service. func TestServices_EligibleForUser_PatchTest(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() @@ -303,6 +309,8 @@ func TestServices_EligibleForUser_PatchTest(t *testing.T) { } } +// TestContact_ReturnsInfo verifies that the contact info endpoint returns the salon's contact +// details (name, phone, email, role) from the first admin user in the database. func TestContact_ReturnsInfo(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() diff --git a/backend/handlers/user/profile_test.go b/backend/handlers/user/profile_test.go index 62b7d06..e681dce 100644 --- a/backend/handlers/user/profile_test.go +++ b/backend/handlers/user/profile_test.go @@ -70,6 +70,7 @@ func setupTest(t *testing.T) (func(), *pgxpool.Pool) { }, pool } +// TestProfile_Get verifies that an authenticated user can retrieve their own profile data. func TestProfile_Get(t *testing.T) { cleanup, pool := setupTest(t) defer cleanup() @@ -103,6 +104,7 @@ func TestProfile_Get(t *testing.T) { } } +// TestProfile_Get_NoAuth verifies that an unauthenticated request to get profile returns 401 Unauthorized. func TestProfile_Get_NoAuth(t *testing.T) { cleanup, _ := setupTest(t) defer cleanup() @@ -116,6 +118,7 @@ func TestProfile_Get_NoAuth(t *testing.T) { } } +// TestProfile_Update verifies that a user can update their profile with valid first name, last name, and phone. func TestProfile_Update(t *testing.T) { cleanup, pool := setupTest(t) defer cleanup() @@ -148,6 +151,7 @@ func TestProfile_Update(t *testing.T) { } } +// TestPasswordChange_Success verifies that a user can successfully change their password with valid credentials. func TestPasswordChange_Success(t *testing.T) { cleanup, pool := setupTest(t) defer cleanup() @@ -179,6 +183,7 @@ func TestPasswordChange_Success(t *testing.T) { } } +// TestPasswordChange_WrongOld verifies that providing an incorrect current password returns 401 Unauthorized. func TestPasswordChange_WrongOld(t *testing.T) { cleanup, pool := setupTest(t) defer cleanup() @@ -209,6 +214,8 @@ func TestPasswordChange_WrongOld(t *testing.T) { } } +// TestPasswordChange_InvalidNewPassword verifies that invalid new passwords (too short or too long for bcrypt) +// are rejected with 400 Bad Request. func TestPasswordChange_InvalidNewPassword(t *testing.T) { cleanup, pool := setupTest(t) defer cleanup() @@ -252,6 +259,7 @@ func TestPasswordChange_InvalidNewPassword(t *testing.T) { } } +// TestAccount_Delete verifies that a user can delete their own account, returning 204 No Content. func TestAccount_Delete(t *testing.T) { cleanup, pool := setupTest(t) defer cleanup() @@ -276,6 +284,7 @@ func TestAccount_Delete(t *testing.T) { } } +// TestLoyalty_Get verifies that a user can retrieve their loyalty stamps count and referral code. func TestLoyalty_Get(t *testing.T) { cleanup, pool := setupTest(t) defer cleanup() @@ -320,6 +329,8 @@ func TestLoyalty_Get(t *testing.T) { } +// TestProfile_Update_InvalidInput verifies that profile update validation rejects invalid inputs: +// missing first name, missing last name, missing phone, invalid phone format, invalid characters in name, name too long. func TestProfile_Update_InvalidInput(t *testing.T) { cleanup, pool := setupTest(t) defer cleanup() @@ -388,6 +399,7 @@ func TestProfile_Update_InvalidInput(t *testing.T) { } } +// TestProfile_Update_Success verifies that a valid profile update succeeds and the changes are persisted in the database. func TestProfile_Update_Success(t *testing.T) { cleanup, pool := setupTest(t) defer cleanup() @@ -439,6 +451,7 @@ func TestProfile_Update_Success(t *testing.T) { } } +// TestPasswordChange_SameAsOld verifies that attempting to change password to the same value returns 400 Bad Request. func TestPasswordChange_SameAsOld(t *testing.T) { cleanup, pool := setupTest(t) defer cleanup() @@ -473,6 +486,7 @@ func TestPasswordChange_SameAsOld(t *testing.T) { } +// TestProfile_UploadPicture verifies that a user can upload a profile picture. May return 500 if S3 is not configured. func TestProfile_UploadPicture(t *testing.T) { cleanup, pool := setupTest(t) defer cleanup()