Test docstrings
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user