refactor(backend): replace resetTestData with SetupTestDB and add new tests
Migrate all test files from resetTestData(t) to testutils.SetupTestDB(t) for isolated per-package test databases. - Add new feature tests: name history assertions, referral discount preview, time blockers, email validation, GDPR export, loyalty manual redemption - Update existing tests to use batch queries and SetupTestDB - Remove test_helpers.go resetTestData infrastructure - Add comprehensive user profile tests (442 new lines) Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -10,13 +10,14 @@ import (
|
||||
"time"
|
||||
|
||||
"crussell/db"
|
||||
"crussell/testutils"
|
||||
"crussell/handlers/bookings"
|
||||
"crussell/testutils/fixtures"
|
||||
)
|
||||
|
||||
// TestGetOverlappingBookingsByTime verifies the new overlapping bookings endpoint
|
||||
func TestGetOverlappingBookingsByTime(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -62,7 +63,7 @@ func TestGetOverlappingBookingsByTime(t *testing.T) {
|
||||
|
||||
// TestGetBookingsByDateRange verifies the new bookings by date range endpoint
|
||||
func TestGetBookingsByDateRange(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -108,7 +109,7 @@ func TestGetBookingsByDateRange(t *testing.T) {
|
||||
|
||||
// TestAdminRescheduleBooking verifies the new reschedule endpoint
|
||||
func TestAdminRescheduleBooking(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
|
||||
@@ -9,13 +9,14 @@ import (
|
||||
"testing"
|
||||
|
||||
"crussell/db"
|
||||
"crussell/testutils"
|
||||
"crussell/handlers/bookings"
|
||||
"crussell/testutils/fixtures"
|
||||
)
|
||||
|
||||
// TestAdminBookings_Get_EnrichedFields verifies that CreatedByName and User.DateOfBirth are populated.
|
||||
func TestAdminBookings_Get_EnrichedFields(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
"time"
|
||||
|
||||
"crussell/db"
|
||||
"crussell/testutils"
|
||||
"crussell/handlers/bookings"
|
||||
"crussell/mw"
|
||||
"crussell/testutils/fixtures"
|
||||
@@ -78,7 +79,7 @@ func seedDefaultWorkingHours(t *testing.T) {
|
||||
// TestAdminBookings_List verifies that an admin can list all bookings in the
|
||||
// system with pagination support.
|
||||
func TestAdminBookings_List(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -98,6 +99,15 @@ func TestAdminBookings_List(t *testing.T) {
|
||||
}
|
||||
defer fixtures.DeleteService(db.DB, serviceID)
|
||||
|
||||
// Insert name history for the user
|
||||
_, err = db.DB.Exec(context.Background(), `
|
||||
INSERT INTO name_history (user_id, previous_first_name, previous_last_name)
|
||||
VALUES ($1, 'OldFirst', 'OldLast')
|
||||
`, userID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to insert name_history: %v", err)
|
||||
}
|
||||
|
||||
bookingID1, err := fixtures.CreateTestBooking(db.DB, userID, serviceID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create test booking 1: %v", err)
|
||||
@@ -129,13 +139,26 @@ func TestAdminBookings_List(t *testing.T) {
|
||||
if resp.Total != 2 {
|
||||
t.Errorf("expected total 2, got %d", resp.Total)
|
||||
}
|
||||
|
||||
// Verify name history appears in booking user info
|
||||
for _, b := range resp.Bookings {
|
||||
if b.User == nil {
|
||||
continue
|
||||
}
|
||||
if b.User.PreviousFirstName == nil || *b.User.PreviousFirstName != "OldFirst" {
|
||||
t.Errorf("expected previousFirstName 'OldFirst' in booking, got %v", b.User.PreviousFirstName)
|
||||
}
|
||||
if b.User.PreviousLastName == nil || *b.User.PreviousLastName != "OldLast" {
|
||||
t.Errorf("expected previousLastName 'OldLast' in booking, got %v", b.User.PreviousLastName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdminBookings_List_PerPageCap tests that per_page parameter up to 500
|
||||
// is accepted (was previously capped at 100, causing per_page=500 to fall
|
||||
// back to default 10 and miss bookings on page 2+).
|
||||
func TestAdminBookings_List_PerPageCap(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -204,7 +227,7 @@ func TestAdminBookings_List_PerPageCap(t *testing.T) {
|
||||
// TestAdminBookings_List_FilterByStatus tests that an admin can filter
|
||||
// bookings by status (e.g., pending, confirmed, completed).
|
||||
func TestAdminBookings_List_FilterByStatus(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -268,7 +291,7 @@ func TestAdminBookings_List_FilterByStatus(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.
|
||||
func TestAdminBookings_Create(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -336,7 +359,7 @@ func TestAdminBookings_Create(t *testing.T) {
|
||||
// creation fails with HTTP 400 when required fields (userID, startTime, serviceIDs)
|
||||
// are missing or invalid.
|
||||
func TestAdminBookings_Create_InvalidInput(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -404,7 +427,7 @@ func TestAdminBookings_Create_InvalidInput(t *testing.T) {
|
||||
// TestAdminBookings_Search tests that an admin can search bookings by
|
||||
// notes, customer name, or other text fields.
|
||||
func TestAdminBookings_Search(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -456,7 +479,7 @@ func TestAdminBookings_Search(t *testing.T) {
|
||||
// TestAdminBookings_Search_MissingQuery verifies that searching without
|
||||
// a query parameter returns HTTP 400 Bad Request.
|
||||
func TestAdminBookings_Search_MissingQuery(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -479,7 +502,7 @@ func TestAdminBookings_Search_MissingQuery(t *testing.T) {
|
||||
// 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) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -532,7 +555,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) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -554,7 +577,7 @@ func TestAdminBookings_Get_NotFound(t *testing.T) {
|
||||
|
||||
// TestAdminBookings_GetUserBookings verifies that an admin can retrieve all bookings for a specific user.
|
||||
func TestAdminBookings_GetUserBookings(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -614,7 +637,7 @@ func TestAdminBookings_GetUserBookings(t *testing.T) {
|
||||
// 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) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -673,7 +696,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) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -713,7 +736,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) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -739,7 +762,7 @@ func TestAdminBookings_Progress_NotFound(t *testing.T) {
|
||||
|
||||
// TestAdminBookings_Confirm verifies that an admin can confirm a pending booking, updating its status to 'confirmed'.
|
||||
func TestAdminBookings_Confirm(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -787,7 +810,7 @@ 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) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -835,7 +858,7 @@ func TestAdminBookings_Confirm_AlreadyConfirmed(t *testing.T) {
|
||||
|
||||
// TestAdminBookings_Cancel verifies that an admin can cancel a booking, updating its status to 'we_cancelled'.
|
||||
func TestAdminBookings_Cancel(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -887,7 +910,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) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -906,7 +929,7 @@ func TestAdminBookings_Cancel_NotFound(t *testing.T) {
|
||||
// TestAdminBookings_Cancel_PendingStatus verifies that admin cancellations of pending bookings
|
||||
// do NOT create admin notifications (pending cancellations don't require staff attention).
|
||||
func TestAdminBookings_Cancel_PendingStatus(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -969,7 +992,7 @@ func TestAdminBookings_Cancel_PendingStatus(t *testing.T) {
|
||||
// TestAdminBookings_Cancel_ConfirmedCreatesNotification verifies that cancelling a confirmed
|
||||
// booking creates an admin notification for staff awareness.
|
||||
func TestAdminBookings_Cancel_ConfirmedCreatesNotification(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -1035,7 +1058,7 @@ func TestAdminBookings_Cancel_ConfirmedCreatesNotification(t *testing.T) {
|
||||
|
||||
// TestAdminBookings_Cancel_InProgressStatus verifies cancellation of in-progress bookings.
|
||||
func TestAdminBookings_Cancel_InProgressStatus(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -1090,7 +1113,7 @@ func TestAdminBookings_Cancel_InProgressStatus(t *testing.T) {
|
||||
// TestAdminBookings_Cancel_AlreadyCancelledRejectsCancellation verifies that attempting to
|
||||
// cancel an already-cancelled booking returns 404 Not Found (idempotency guard).
|
||||
func TestAdminBookings_Cancel_AlreadyCancelledRejectsCancellation(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -1135,7 +1158,7 @@ func TestAdminBookings_Cancel_AlreadyCancelledRejectsCancellation(t *testing.T)
|
||||
// TestAdminBookings_Cancel_CompletedRejectsCancellation verifies that attempting to cancel
|
||||
// a completed booking returns 404 Not Found (cannot cancel finished appointments).
|
||||
func TestAdminBookings_Cancel_CompletedRejectsCancellation(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -1184,7 +1207,7 @@ func TestAdminBookings_Cancel_CompletedRejectsCancellation(t *testing.T) {
|
||||
// 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) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
_, err := fixtures.CreateTestUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -1259,7 +1282,7 @@ func TestAdminBookings_NonAdmin(t *testing.T) {
|
||||
// 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) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -1344,7 +1367,7 @@ func TestAdminBookings_Create_DuringHolidayHours_Rejected(t *testing.T) {
|
||||
// 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) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -1408,7 +1431,7 @@ 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) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -1460,7 +1483,7 @@ 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) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -1536,7 +1559,7 @@ func TestAdminBookings_Search_MultipleResults(t *testing.T) {
|
||||
// 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) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -1626,7 +1649,7 @@ func TestAdminBookings_ListEditRequests(t *testing.T) {
|
||||
// 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) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -1726,7 +1749,7 @@ 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) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
// Create admin user
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
@@ -1864,7 +1887,7 @@ func TestAdminBookings_ApproveEditRequest(t *testing.T) {
|
||||
// deposit-related fields (deposit_required, deposit_amount, deposit_paid, deposit_deadline)
|
||||
// for bookings that have deposit_required=true.
|
||||
func TestAdminBookings_Get_DepositFields(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -1935,7 +1958,7 @@ func TestAdminBookings_Get_DepositFields(t *testing.T) {
|
||||
// TestAdminBookings_List_DepositFields verifies that admin booking list returns
|
||||
// deposit-related fields for each booking.
|
||||
func TestAdminBookings_List_DepositFields(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -2012,7 +2035,7 @@ func TestAdminBookings_List_DepositFields(t *testing.T) {
|
||||
// create bookings that overlap with time blockers, but receive a warning.
|
||||
// The booking is still created (201 Created), unlike regular users who get 409.
|
||||
func TestAdminBookings_Create_OverlappingBlocker_WithWarning(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -2092,7 +2115,7 @@ func TestAdminBookings_Create_OverlappingBlocker_WithWarning(t *testing.T) {
|
||||
// edit bookings to overlap with time blockers, but receive a warning.
|
||||
// The booking is still updated (200 OK with warnings), unlike regular users who get 409.
|
||||
func TestAdminBookings_Edit_OverlappingBlocker_WithWarning(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -2181,7 +2204,7 @@ func TestAdminBookings_Edit_OverlappingBlocker_WithWarning(t *testing.T) {
|
||||
// TestAdminBookings_Create_EnforceDeposits_Bypass tests that admin can create bookings
|
||||
// for users with outstanding deposits by setting enforce_deposits=false.
|
||||
func TestAdminBookings_Create_EnforceDeposits_Bypass(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -2228,7 +2251,7 @@ func TestAdminBookings_Create_EnforceDeposits_Bypass(t *testing.T) {
|
||||
// TestAdminBookings_Create_EnforceDeposits_Enforced tests that by default (or when enforce_deposits=true),
|
||||
// admin bookings respect the deposit requirement rules.
|
||||
func TestAdminBookings_Create_EnforceDeposits_Enforced(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -2290,7 +2313,7 @@ func TestAdminBookings_Create_EnforceDeposits_Enforced(t *testing.T) {
|
||||
// TestAdminBookings_Create_WalkIn tests that admins can create walk-in bookings
|
||||
// (no advance time requirement), including immediate/past times if needed.
|
||||
func TestAdminBookings_Create_WalkIn(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -2336,7 +2359,7 @@ func TestAdminBookings_Create_WalkIn(t *testing.T) {
|
||||
// TestAdminBookings_Create_WalkInWithDeposits tests that admins can create walk-ins
|
||||
// even when user has outstanding deposits and enforce_deposits=false.
|
||||
func TestAdminBookings_Create_WalkInWithDeposits(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -2394,7 +2417,7 @@ func TestAdminBookings_Create_WalkInWithDeposits(t *testing.T) {
|
||||
// confirmed booking is progressed to completed with at least one payment, the user's
|
||||
// deposits_required is reduced by 1.
|
||||
func TestAdminBookings_Confirm_CompletesWithPayment_ReducesDeposits(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -2479,7 +2502,7 @@ func TestAdminBookings_Confirm_CompletesWithPayment_ReducesDeposits(t *testing.T
|
||||
// TestAdminBookings_Confirm_CompletesWithoutPayment_NoReduction verifies that when a
|
||||
// booking is completed without any payments, the user's deposits_required is NOT reduced.
|
||||
func TestAdminBookings_Confirm_CompletesWithoutPayment_NoReduction(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -2558,7 +2581,7 @@ func TestAdminBookings_Confirm_CompletesWithoutPayment_NoReduction(t *testing.T)
|
||||
// enforce_deposits is set to false, the admin can create a second booking for a user
|
||||
// who already has an active booking, bypassing the one-active-booking limit.
|
||||
func TestAdminBookings_Create_EnforceDepositsFalse_BypassesLimit(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -2623,7 +2646,7 @@ func TestAdminBookings_Create_EnforceDepositsFalse_BypassesLimit(t *testing.T) {
|
||||
// enforce_deposits is set to false, the admin can create a booking within 24 hours
|
||||
// for a user with outstanding deposits.
|
||||
func TestAdminBookings_Create_EnforceDepositsFalse_Within24h(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -2680,7 +2703,7 @@ func TestAdminBookings_Create_EnforceDepositsFalse_Within24h(t *testing.T) {
|
||||
// TestAdminBookings_Create_WalkInGuestUser verifies that an admin can create a booking
|
||||
// for a guest user (created via fixtures.CreateTestGuestUser).
|
||||
func TestAdminBookings_Create_WalkInGuestUser(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -2742,7 +2765,7 @@ func TestAdminBookings_Create_WalkInGuestUser(t *testing.T) {
|
||||
// update a booking's time to fall within a closed exceptional hours period, receiving
|
||||
// a warning but proceeding with the update.
|
||||
func TestAdminUpdateBooking_ClosedExceptionalHours_WarningOnly(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -2854,7 +2877,7 @@ func TestAdminUpdateBooking_ClosedExceptionalHours_WarningOnly(t *testing.T) {
|
||||
// cannot create a booking for a user during hours marked as closed in the exceptional
|
||||
// working hours (holiday) system.
|
||||
func TestAdminCreateBookingForUser_ClosedExceptionalHours_Rejected(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -2934,7 +2957,7 @@ func TestAdminCreateBookingForUser_ClosedExceptionalHours_Rejected(t *testing.T)
|
||||
// TestGetBookingsByCreatedRange verifies that the endpoint returns bookings
|
||||
// created within the specified created_at range.
|
||||
func TestGetBookingsByCreatedRange(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
@@ -3022,7 +3045,7 @@ func TestGetBookingsByCreatedRange(t *testing.T) {
|
||||
// TestGetBookingsByCreatedRange_Empty verifies that the endpoint returns an
|
||||
// empty array when no bookings fall within the created_at range.
|
||||
func TestGetBookingsByCreatedRange_Empty(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
@@ -3051,7 +3074,7 @@ func TestGetBookingsByCreatedRange_Empty(t *testing.T) {
|
||||
// TestGetBookingsByCreatedRange_MissingParams verifies that the endpoint
|
||||
// returns 400 when start or end query parameters are missing.
|
||||
func TestGetBookingsByCreatedRange_MissingParams(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -3083,7 +3106,7 @@ func TestGetBookingsByCreatedRange_MissingParams(t *testing.T) {
|
||||
// TestGetBookingsByCreatedRange_InvalidFormat verifies that the endpoint
|
||||
// returns 400 when the date format is invalid.
|
||||
func TestGetBookingsByCreatedRange_InvalidFormat(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -3102,7 +3125,7 @@ func TestGetBookingsByCreatedRange_InvalidFormat(t *testing.T) {
|
||||
// TestGetBookingsByCreatedRange_OrderedByCreatedAt verifies that results
|
||||
// are returned in ascending order by created_at.
|
||||
func TestGetBookingsByCreatedRange_OrderedByCreatedAt(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
@@ -3195,7 +3218,7 @@ func TestGetBookingsByCreatedRange_OrderedByCreatedAt(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetAdminBooking_WithDiscounts(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
userID, err := fixtures.CreateTestUser(db.DB)
|
||||
@@ -3289,7 +3312,7 @@ func createCompletedBookingWithTimeForAdmin(t *testing.T, userID, serviceID stri
|
||||
// It checks that the booking_custom_services entry is created and the custom
|
||||
// service usage_count is incremented.
|
||||
func TestAdminBookings_CreateWithCustomServices(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
@@ -3369,7 +3392,7 @@ func TestAdminBookings_CreateWithCustomServices(t *testing.T) {
|
||||
// It checks that entries are created in both booking_services and
|
||||
// booking_custom_services.
|
||||
func TestAdminBookings_CreateWithCustomAndRegularServices(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
@@ -3456,7 +3479,7 @@ func TestAdminBookings_CreateWithCustomAndRegularServices(t *testing.T) {
|
||||
// rejects requests with neither service_ids nor custom_service_ids, testing
|
||||
// both nil and empty arrays.
|
||||
func TestAdminBookings_Create_CustomServiceValidation(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -3526,7 +3549,7 @@ func TestAdminBookings_Create_CustomServiceValidation(t *testing.T) {
|
||||
// It checks that the override_price and override_duration_minutes are stored
|
||||
// in booking_custom_services.
|
||||
func TestAdminBookings_Confirm_WithCustomOverrides(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -3617,7 +3640,7 @@ func TestAdminBookings_Confirm_WithCustomOverrides(t *testing.T) {
|
||||
// can create a booking with custom services and apply price/duration overrides
|
||||
// at creation time via AdminCreateBookingForUserHandler.
|
||||
func TestAdminBookings_CreateWithCustomServicesAndOverrides(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
@@ -3699,7 +3722,7 @@ func TestAdminBookings_CreateWithCustomServicesAndOverrides(t *testing.T) {
|
||||
// It verifies the response duration matches the custom service duration and
|
||||
// that the time_blocker is created reflecting the custom service duration.
|
||||
func TestAdminBookings_AdminReserve_WithCustomServices(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"crussell/db"
|
||||
"crussell/testutils"
|
||||
"crussell/mw"
|
||||
"crussell/testutils/fixtures"
|
||||
|
||||
@@ -74,7 +75,7 @@ func makeCustomServiceRequest(handler http.Handler, method, path string, body in
|
||||
// TestCustomServices_List verifies that an admin can list all custom services
|
||||
// with pagination metadata.
|
||||
func TestCustomServices_List(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -118,7 +119,7 @@ func TestCustomServices_List(t *testing.T) {
|
||||
// TestCustomServices_List_Search verifies search filtering via the q parameter,
|
||||
// including case-insensitive matching and no-results scenarios.
|
||||
func TestCustomServices_List_Search(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -182,7 +183,7 @@ func TestCustomServices_List_Search(t *testing.T) {
|
||||
// TestCustomServices_List_Popular verifies the popular flag returns custom services
|
||||
// ordered by usage_count, limited to the specified number.
|
||||
func TestCustomServices_List_Popular(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -236,7 +237,7 @@ func TestCustomServices_List_Popular(t *testing.T) {
|
||||
|
||||
// TestCustomServices_List_Pagination verifies page and per_page query parameters.
|
||||
func TestCustomServices_List_Pagination(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -295,7 +296,7 @@ func TestCustomServices_List_Pagination(t *testing.T) {
|
||||
// TestCustomServices_Create verifies that an admin can create a new custom service
|
||||
// with name, description, price, duration, minimum age, and notes.
|
||||
func TestCustomServices_Create(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -353,7 +354,7 @@ func TestCustomServices_Create(t *testing.T) {
|
||||
// TestCustomServices_Create_Validation verifies that validation errors return
|
||||
// HTTP 400 for various invalid inputs.
|
||||
func TestCustomServices_Create_Validation(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -434,7 +435,7 @@ func TestCustomServices_Create_Validation(t *testing.T) {
|
||||
|
||||
// TestCustomServices_Get verifies that an admin can retrieve a single custom service by ID.
|
||||
func TestCustomServices_Get(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -476,7 +477,7 @@ func TestCustomServices_Get(t *testing.T) {
|
||||
|
||||
// TestCustomServices_Get_NotFound verifies that requesting a non-existent custom service returns 404.
|
||||
func TestCustomServices_Get_NotFound(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -499,7 +500,7 @@ func TestCustomServices_Get_NotFound(t *testing.T) {
|
||||
|
||||
// TestCustomServices_Update verifies that an admin can update a custom service's fields.
|
||||
func TestCustomServices_Update(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -550,7 +551,7 @@ func TestCustomServices_Update(t *testing.T) {
|
||||
|
||||
// TestCustomServices_Update_NotFound verifies that updating a non-existent custom service returns 404.
|
||||
func TestCustomServices_Update_NotFound(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -575,7 +576,7 @@ func TestCustomServices_Update_NotFound(t *testing.T) {
|
||||
// TestCustomServices_Update_NoFields verifies that sending an update with no fields
|
||||
// returns 400 Bad Request.
|
||||
func TestCustomServices_Update_NoFields(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -607,7 +608,7 @@ func TestCustomServices_Update_NoFields(t *testing.T) {
|
||||
// TestCustomServices_Promote verifies that promoting a custom service creates a
|
||||
// regular service, migrates data, and deletes the original custom service.
|
||||
func TestCustomServices_Promote(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -670,7 +671,7 @@ func TestCustomServices_Promote(t *testing.T) {
|
||||
|
||||
// TestCustomServices_Promote_NotFound verifies that promoting a non-existent custom service returns 404.
|
||||
func TestCustomServices_Promote_NotFound(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -693,7 +694,7 @@ func TestCustomServices_Promote_NotFound(t *testing.T) {
|
||||
|
||||
// TestCustomServices_Delete verifies that an admin can delete an unused custom service.
|
||||
func TestCustomServices_Delete(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -737,7 +738,7 @@ func TestCustomServices_Delete(t *testing.T) {
|
||||
|
||||
// TestCustomServices_Delete_NotFound verifies that deleting a non-existent custom service returns 404.
|
||||
func TestCustomServices_Delete_NotFound(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -757,7 +758,7 @@ func TestCustomServices_Delete_NotFound(t *testing.T) {
|
||||
// TestCustomServices_Delete_Conflict verifies that deleting a custom service with
|
||||
// usage_count > 0 returns 409 Conflict.
|
||||
func TestCustomServices_Delete_Conflict(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -794,7 +795,7 @@ func TestCustomServices_Delete_Conflict(t *testing.T) {
|
||||
// TestCustomServices_NonAdmin verifies that non-admin users receive HTTP 403
|
||||
// Forbidden when attempting to access any admin custom services endpoint.
|
||||
func TestCustomServices_NonAdmin(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
_, err := fixtures.CreateTestUser(db.DB)
|
||||
if err != nil {
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"time"
|
||||
|
||||
"crussell/db"
|
||||
"crussell/testutils"
|
||||
"crussell/mw"
|
||||
"crussell/testutils/fixtures"
|
||||
|
||||
@@ -90,7 +91,7 @@ func insertMilestoneCampaign(t *testing.T, name string, discount float64, status
|
||||
// =============================================================================
|
||||
|
||||
func TestGetDiscountCampaigns_Empty(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create admin: %v", err)
|
||||
@@ -114,7 +115,7 @@ func TestGetDiscountCampaigns_Empty(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetDiscountCampaigns_WithCampaigns(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create admin: %v", err)
|
||||
@@ -152,7 +153,7 @@ func TestGetDiscountCampaigns_WithCampaigns(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetDiscountCampaigns_FilterByStatus(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create admin: %v", err)
|
||||
@@ -191,7 +192,7 @@ func TestGetDiscountCampaigns_FilterByStatus(t *testing.T) {
|
||||
// =============================================================================
|
||||
|
||||
func TestCreateDiscountCampaign_TimeBased(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create admin: %v", err)
|
||||
@@ -230,7 +231,7 @@ func TestCreateDiscountCampaign_TimeBased(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCreateDiscountCampaign_Milestone(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create admin: %v", err)
|
||||
@@ -269,7 +270,7 @@ func TestCreateDiscountCampaign_Milestone(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCreateDiscountCampaign_ValidationErrors(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create admin: %v", err)
|
||||
@@ -373,7 +374,7 @@ func TestCreateDiscountCampaign_ValidationErrors(t *testing.T) {
|
||||
// =============================================================================
|
||||
|
||||
func TestUpdateDiscountCampaign_UpdateName(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create admin: %v", err)
|
||||
@@ -401,7 +402,7 @@ func TestUpdateDiscountCampaign_UpdateName(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUpdateDiscountCampaign_NotFound(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create admin: %v", err)
|
||||
@@ -419,7 +420,7 @@ func TestUpdateDiscountCampaign_NotFound(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUpdateDiscountCampaign_InvalidStatus(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create admin: %v", err)
|
||||
@@ -443,7 +444,7 @@ func TestUpdateDiscountCampaign_InvalidStatus(t *testing.T) {
|
||||
// =============================================================================
|
||||
|
||||
func TestDeleteDiscountCampaign_HappyPath(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create admin: %v", err)
|
||||
@@ -471,7 +472,7 @@ func TestDeleteDiscountCampaign_HappyPath(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDeleteDiscountCampaign_NotFound(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create admin: %v", err)
|
||||
@@ -491,7 +492,7 @@ func TestDeleteDiscountCampaign_NotFound(t *testing.T) {
|
||||
// =============================================================================
|
||||
|
||||
func TestGetCampaignStats_NoUsage(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create admin: %v", err)
|
||||
@@ -523,7 +524,7 @@ func TestGetCampaignStats_NoUsage(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetCampaignStats_NotFound(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create admin: %v", err)
|
||||
|
||||
@@ -8,11 +8,12 @@ import (
|
||||
"testing"
|
||||
|
||||
"crussell/db"
|
||||
"crussell/testutils"
|
||||
"crussell/testutils/fixtures"
|
||||
)
|
||||
|
||||
func TestPatchTests_CRUD(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
// Create a service to link
|
||||
serviceID, err := fixtures.CreateTestService(db.DB)
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"crussell/db"
|
||||
"crussell/testutils"
|
||||
"crussell/handlers/services"
|
||||
"crussell/mw"
|
||||
)
|
||||
@@ -28,7 +29,7 @@ import (
|
||||
// with name, description, price, duration, and minimum age requirements. The new
|
||||
// service is active by default and stored in the database.
|
||||
func TestAdminServices_Create(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
// Create admin user in DB first
|
||||
_, err := db.DB.Exec(context.Background(), `
|
||||
@@ -74,7 +75,7 @@ func TestAdminServices_Create(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.
|
||||
func TestAdminServices_List(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
// Insert test services
|
||||
_, err := db.DB.Exec(context.Background(), `
|
||||
@@ -125,7 +126,7 @@ func TestAdminServices_List(t *testing.T) {
|
||||
// active status on/off. This is used to temporarily disable a service without
|
||||
// deleting it from the system.
|
||||
func TestAdminServices_Toggle(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
// Create a service
|
||||
var serviceID string
|
||||
@@ -175,7 +176,7 @@ func TestAdminServices_Toggle(t *testing.T) {
|
||||
// by setting is_active to false. The service record remains but is hidden from
|
||||
// customers.
|
||||
func TestAdminServices_Delete(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
// Create a service
|
||||
var serviceID string
|
||||
@@ -210,7 +211,7 @@ func TestAdminServices_Delete(t *testing.T) {
|
||||
// Forbidden when attempting to create, list, toggle, or delete services. This
|
||||
// ensures proper role-based access control.
|
||||
func TestAdminServices_NonAdmin(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
// Create regular user in DB
|
||||
_, err := db.DB.Exec(context.Background(), `
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"crussell/db"
|
||||
"crussell/testutils"
|
||||
)
|
||||
|
||||
func intPtr(i int) *int { return &i }
|
||||
@@ -29,7 +30,7 @@ func seedBusinessSettings(t *testing.T) {
|
||||
// TestGetBusinessSettings verifies that GET /api/admin/settings returns the
|
||||
// current business settings row.
|
||||
func TestGetBusinessSettings(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedBusinessSettings(t)
|
||||
|
||||
handler := http.HandlerFunc(GetBusinessSettings)
|
||||
@@ -64,7 +65,7 @@ func TestGetBusinessSettings(t *testing.T) {
|
||||
// TestUpdateBusinessSettings verifies that updating a single field via
|
||||
// PUT /api/admin/settings returns 200 with the updated settings.
|
||||
func TestUpdateBusinessSettings(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedBusinessSettings(t)
|
||||
|
||||
handler := http.HandlerFunc(UpdateBusinessSettings)
|
||||
@@ -90,7 +91,7 @@ func TestUpdateBusinessSettings(t *testing.T) {
|
||||
// TestUpdateBusinessSettings_MultipleFields verifies that updating several
|
||||
// fields at once works correctly.
|
||||
func TestUpdateBusinessSettings_MultipleFields(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedBusinessSettings(t)
|
||||
|
||||
handler := http.HandlerFunc(UpdateBusinessSettings)
|
||||
@@ -128,7 +129,7 @@ func TestUpdateBusinessSettings_MultipleFields(t *testing.T) {
|
||||
// TestUpdateBusinessSettings_InvalidVoucherType verifies that an invalid
|
||||
// voucher_type value returns 400.
|
||||
func TestUpdateBusinessSettings_InvalidVoucherType(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedBusinessSettings(t)
|
||||
|
||||
handler := http.HandlerFunc(UpdateBusinessSettings)
|
||||
@@ -148,7 +149,7 @@ func TestUpdateBusinessSettings_InvalidVoucherType(t *testing.T) {
|
||||
// TestUpdateBusinessSettings_NegativeExpiryMonths verifies that a
|
||||
// gift_card_expiry_months value less than 1 returns 400.
|
||||
func TestUpdateBusinessSettings_NegativeExpiryMonths(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedBusinessSettings(t)
|
||||
|
||||
handler := http.HandlerFunc(UpdateBusinessSettings)
|
||||
@@ -168,7 +169,7 @@ func TestUpdateBusinessSettings_NegativeExpiryMonths(t *testing.T) {
|
||||
// TestUpdateBusinessSettings_InvalidVATRate verifies that a default_vat_rate
|
||||
// outside the 0-100 range returns 400.
|
||||
func TestUpdateBusinessSettings_InvalidVATRate(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedBusinessSettings(t)
|
||||
|
||||
handler := http.HandlerFunc(UpdateBusinessSettings)
|
||||
@@ -200,7 +201,7 @@ func TestUpdateBusinessSettings_InvalidVATRate(t *testing.T) {
|
||||
// TestUpdateBusinessSettings_NoFields verifies that an empty request body
|
||||
// (no fields to update) returns 400.
|
||||
func TestUpdateBusinessSettings_NoFields(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedBusinessSettings(t)
|
||||
|
||||
handler := http.HandlerFunc(UpdateBusinessSettings)
|
||||
@@ -217,7 +218,7 @@ func TestUpdateBusinessSettings_NoFields(t *testing.T) {
|
||||
// TestUpdateBusinessSettings_PartialUpdate verifies that updating a single field
|
||||
// leaves other fields unchanged.
|
||||
func TestUpdateBusinessSettings_PartialUpdate(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedBusinessSettings(t)
|
||||
|
||||
handler := http.HandlerFunc(UpdateBusinessSettings)
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"time"
|
||||
|
||||
"crussell/db"
|
||||
"crussell/testutils"
|
||||
"crussell/handlers/notifications"
|
||||
"crussell/handlers/today"
|
||||
"crussell/mw"
|
||||
@@ -33,7 +34,7 @@ import (
|
||||
// TestAdminToday_CurrentNext verifies that an admin can retrieve the currently
|
||||
// in-progress booking and the next upcoming booking for the dashboard.
|
||||
func TestAdminToday_CurrentNext(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
// Create test user
|
||||
var userID string
|
||||
@@ -108,7 +109,7 @@ func TestAdminToday_CurrentNext(t *testing.T) {
|
||||
// TestAdminToday_CurrentNext_ClosingTime verifies that the current-next endpoint
|
||||
// returns the closing time for today.
|
||||
func TestAdminToday_CurrentNext_ClosingTime(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
// Seed working hours for today (DB uses 0=Monday, 6=Sunday)
|
||||
todayWeekday := int(time.Now().Weekday())
|
||||
@@ -154,7 +155,7 @@ func TestAdminToday_CurrentNext_ClosingTime(t *testing.T) {
|
||||
// TestAdminToday_Appointments tests that an admin can get a list of all
|
||||
// bookings scheduled for today with their details.
|
||||
func TestAdminToday_Appointments(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
// Create test user
|
||||
var userID string
|
||||
@@ -229,7 +230,7 @@ func TestAdminToday_Appointments(t *testing.T) {
|
||||
// TestAdminToday_PendingApprovals verifies that an admin can see all pending
|
||||
// bookings that require approval/confirmation.
|
||||
func TestAdminToday_PendingApprovals(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
// Create test user
|
||||
var userID string
|
||||
@@ -311,7 +312,7 @@ func TestAdminToday_PendingApprovals(t *testing.T) {
|
||||
//
|
||||
// The transition happens silently in the background during GET requests, not via cron.
|
||||
func TestAdminToday_AutoTransition_ConfirmedToInProgress(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
// Create test user
|
||||
var userID string
|
||||
@@ -384,7 +385,7 @@ func TestAdminToday_AutoTransition_ConfirmedToInProgress(t *testing.T) {
|
||||
//
|
||||
// The transition happens silently in the background during GET requests, not via cron.
|
||||
func TestAdminToday_AutoTransition_InProgressToCompleted(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
// Create test user
|
||||
var userID string
|
||||
@@ -454,7 +455,7 @@ func TestAdminToday_AutoTransition_InProgressToCompleted(t *testing.T) {
|
||||
// TestAdminToday_NoAutoTransition_BeforeStartTime verifies that a confirmed
|
||||
// booking that hasn't started yet is NOT transitioned to in_progress.
|
||||
func TestAdminToday_NoAutoTransition_BeforeStartTime(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
// Create test user
|
||||
var userID string
|
||||
@@ -523,7 +524,7 @@ func TestAdminToday_NoAutoTransition_BeforeStartTime(t *testing.T) {
|
||||
// TestAdminToday_AutoTransition_CurrentNextHandler verifies that auto-transition
|
||||
// also works when calling GetCurrentAndNextHandler (not just appointments handler)
|
||||
func TestAdminToday_AutoTransition_CurrentNextHandler(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
// Create test user
|
||||
var userID string
|
||||
@@ -611,7 +612,7 @@ func TestAdminToday_AutoTransition_CurrentNextHandler(t *testing.T) {
|
||||
// - total_bookings counts non-cancelled bookings, excluding cancelled/no_show
|
||||
// - The range includes bookings from both the closed day and prior open days
|
||||
func TestAdminToday_ClosedDay_Summary(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
ctx := context.Background()
|
||||
now := time.Now()
|
||||
@@ -732,7 +733,7 @@ func TestAdminToday_ClosedDay_Summary(t *testing.T) {
|
||||
// - summary_scope = "day" (today's summary)
|
||||
// - week_summary is present with summary_scope = "week"
|
||||
func TestAdminToday_WeekSummary_TomorrowClosed(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
ctx := context.Background()
|
||||
now := time.Now()
|
||||
@@ -823,7 +824,7 @@ func TestAdminToday_WeekSummary_TomorrowClosed(t *testing.T) {
|
||||
// a closed day, even when default working_hours says today is open.
|
||||
// This tests the column name fix: monday_week_start → week_start.
|
||||
func TestAdminToday_ExceptionalHours_ClosedDay(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
ctx := context.Background()
|
||||
now := time.Now()
|
||||
@@ -963,7 +964,7 @@ func TestAdminNotifications_Acknowledge(t *testing.T) {
|
||||
// TestAdminToday_NonAdmin verifies that non-admin users receive HTTP 403
|
||||
// when accessing today's dashboard endpoints.
|
||||
func TestAdminToday_NonAdmin(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
// Test current-next endpoint
|
||||
currentNextHandler := mw.RequireAdmin(http.HandlerFunc(today.GetCurrentAndNextHandler))
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"time"
|
||||
|
||||
"crussell/db"
|
||||
"crussell/testutils"
|
||||
"crussell/handlers/bookings"
|
||||
"crussell/testutils/fixtures"
|
||||
)
|
||||
@@ -76,7 +77,7 @@ func createSecondService(t *testing.T, name string, durationMinutes int, price f
|
||||
// TestAdminBookings_UpdateServices_ReplaceServices verifies that an admin can
|
||||
// replace all services on a booking with a new set of services.
|
||||
func TestAdminBookings_UpdateServices_ReplaceServices(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
@@ -129,7 +130,7 @@ func TestAdminBookings_UpdateServices_ReplaceServices(t *testing.T) {
|
||||
// TestAdminBookings_UpdateServices_AddService verifies that an admin can add
|
||||
// additional services to an existing booking.
|
||||
func TestAdminBookings_UpdateServices_AddService(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
@@ -179,7 +180,7 @@ func TestAdminBookings_UpdateServices_AddService(t *testing.T) {
|
||||
// TestAdminBookings_UpdateServices_RemoveService verifies that an admin can
|
||||
// remove services from a booking by providing fewer service IDs.
|
||||
func TestAdminBookings_UpdateServices_RemoveService(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
@@ -241,7 +242,7 @@ func TestAdminBookings_UpdateServices_RemoveService(t *testing.T) {
|
||||
// TestAdminBookings_UpdateServices_WithPriceOverride verifies that an admin can
|
||||
// apply a price override to a service.
|
||||
func TestAdminBookings_UpdateServices_WithPriceOverride(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
@@ -297,7 +298,7 @@ func TestAdminBookings_UpdateServices_WithPriceOverride(t *testing.T) {
|
||||
// TestAdminBookings_UpdateServices_WithDurationOverride verifies that an admin can
|
||||
// apply a duration override to a service.
|
||||
func TestAdminBookings_UpdateServices_WithDurationOverride(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
@@ -353,7 +354,7 @@ func TestAdminBookings_UpdateServices_WithDurationOverride(t *testing.T) {
|
||||
// TestAdminBookings_UpdateServices_WithBothOverrides verifies that an admin can
|
||||
// apply both price and duration overrides simultaneously.
|
||||
func TestAdminBookings_UpdateServices_WithBothOverrides(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
@@ -410,7 +411,7 @@ func TestAdminBookings_UpdateServices_WithBothOverrides(t *testing.T) {
|
||||
// TestAdminBookings_UpdateServices_UpdateNotes verifies that an admin can update
|
||||
// the booking notes along with services.
|
||||
func TestAdminBookings_UpdateServices_UpdateNotes(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
@@ -458,7 +459,7 @@ func TestAdminBookings_UpdateServices_UpdateNotes(t *testing.T) {
|
||||
// TestAdminBookings_UpdateServices_MultipleOverrides verifies that an admin can
|
||||
// apply overrides to multiple services in a single request.
|
||||
func TestAdminBookings_UpdateServices_MultipleOverrides(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
@@ -536,7 +537,7 @@ func TestAdminBookings_UpdateServices_MultipleOverrides(t *testing.T) {
|
||||
// TestAdminBookings_UpdateServices_InvalidBookingID verifies that an invalid
|
||||
// booking ID returns 404.
|
||||
func TestAdminBookings_UpdateServices_InvalidBookingID(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -564,7 +565,7 @@ func TestAdminBookings_UpdateServices_InvalidBookingID(t *testing.T) {
|
||||
// TestAdminBookings_UpdateServices_BookingNotFound verifies that a valid-format
|
||||
// but non-existent booking ID returns 404.
|
||||
func TestAdminBookings_UpdateServices_BookingNotFound(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -592,7 +593,7 @@ func TestAdminBookings_UpdateServices_BookingNotFound(t *testing.T) {
|
||||
// TestAdminBookings_UpdateServices_EmptyServiceIDs verifies that an empty
|
||||
// service_ids array returns 400.
|
||||
func TestAdminBookings_UpdateServices_EmptyServiceIDs(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -629,7 +630,7 @@ func TestAdminBookings_UpdateServices_EmptyServiceIDs(t *testing.T) {
|
||||
// TestAdminBookings_UpdateServices_InvalidServiceID verifies that an invalid
|
||||
// service ID in the list returns 400.
|
||||
func TestAdminBookings_UpdateServices_InvalidServiceID(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -666,7 +667,7 @@ func TestAdminBookings_UpdateServices_InvalidServiceID(t *testing.T) {
|
||||
// TestAdminBookings_UpdateServices_ServiceNotFound verifies that a valid-format
|
||||
// but non-existent service ID returns 400.
|
||||
func TestAdminBookings_UpdateServices_ServiceNotFound(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -703,7 +704,7 @@ func TestAdminBookings_UpdateServices_ServiceNotFound(t *testing.T) {
|
||||
// TestAdminBookings_UpdateServices_NegativePriceOverride verifies that a negative
|
||||
// price override returns 400.
|
||||
func TestAdminBookings_UpdateServices_NegativePriceOverride(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -746,7 +747,7 @@ func TestAdminBookings_UpdateServices_NegativePriceOverride(t *testing.T) {
|
||||
// TestAdminBookings_UpdateServices_ZeroDurationOverride verifies that a zero or
|
||||
// negative duration override returns 400.
|
||||
func TestAdminBookings_UpdateServices_ZeroDurationOverride(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -793,7 +794,7 @@ func TestAdminBookings_UpdateServices_ZeroDurationOverride(t *testing.T) {
|
||||
// TestAdminBookings_UpdateServices_CompletedBookingRejected verifies that
|
||||
// updating services on a completed booking returns 403.
|
||||
func TestAdminBookings_UpdateServices_CompletedBookingRejected(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -830,7 +831,7 @@ func TestAdminBookings_UpdateServices_CompletedBookingRejected(t *testing.T) {
|
||||
// TestAdminBookings_UpdateServices_CancelledBookingRejected verifies that
|
||||
// updating services on a cancelled booking returns 403.
|
||||
func TestAdminBookings_UpdateServices_CancelledBookingRejected(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -867,7 +868,7 @@ func TestAdminBookings_UpdateServices_CancelledBookingRejected(t *testing.T) {
|
||||
// TestAdminBookings_UpdateServices_NoShowBookingRejected verifies that
|
||||
// updating services on a no-show booking returns 403.
|
||||
func TestAdminBookings_UpdateServices_NoShowBookingRejected(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -904,7 +905,7 @@ func TestAdminBookings_UpdateServices_NoShowBookingRejected(t *testing.T) {
|
||||
// TestAdminBookings_UpdateServices_WeCancelledBookingRejected verifies that
|
||||
// updating services on a we_cancelled booking returns 403.
|
||||
func TestAdminBookings_UpdateServices_WeCancelledBookingRejected(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
@@ -945,7 +946,7 @@ func TestAdminBookings_UpdateServices_WeCancelledBookingRejected(t *testing.T) {
|
||||
// TestAdminBookings_UpdateServices_OverlapWithNextBooking verifies that extending
|
||||
// a booking's duration to overlap with the next booking returns 409.
|
||||
func TestAdminBookings_UpdateServices_OverlapWithNextBooking(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
@@ -996,7 +997,7 @@ func TestAdminBookings_UpdateServices_OverlapWithNextBooking(t *testing.T) {
|
||||
// TestAdminBookings_UpdateServices_NoOverlapSucceeds verifies that a service update
|
||||
// that does not overlap with the next booking succeeds.
|
||||
func TestAdminBookings_UpdateServices_NoOverlapSucceeds(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
@@ -1046,7 +1047,7 @@ func TestAdminBookings_UpdateServices_NoOverlapSucceeds(t *testing.T) {
|
||||
// TestAdminBookings_UpdateServices_NoNextBookingSucceeds verifies that a service
|
||||
// update succeeds when there is no next booking (no overlap possible).
|
||||
func TestAdminBookings_UpdateServices_NoNextBookingSucceeds(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
@@ -1092,7 +1093,7 @@ func TestAdminBookings_UpdateServices_NoNextBookingSucceeds(t *testing.T) {
|
||||
// TestAdminBookings_UpdateServices_ResponseShape verifies that the response
|
||||
// contains all expected fields after a successful update.
|
||||
func TestAdminBookings_UpdateServices_ResponseShape(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
@@ -1163,7 +1164,7 @@ func TestAdminBookings_UpdateServices_ResponseShape(t *testing.T) {
|
||||
// TestAdminBookings_UpdateServices_PendingBooking verifies that services can be
|
||||
// updated on a pending booking.
|
||||
func TestAdminBookings_UpdateServices_PendingBooking(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
@@ -1201,7 +1202,7 @@ func TestAdminBookings_UpdateServices_PendingBooking(t *testing.T) {
|
||||
// TestAdminBookings_UpdateServices_InProgressBooking verifies that services can be
|
||||
// updated on an in_progress booking.
|
||||
func TestAdminBookings_UpdateServices_InProgressBooking(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
@@ -1239,7 +1240,7 @@ func TestAdminBookings_UpdateServices_InProgressBooking(t *testing.T) {
|
||||
// TestAdminBookings_UpdateServices_ClearNotes verifies that setting notes to an
|
||||
// empty string updates the booking notes accordingly.
|
||||
func TestAdminBookings_UpdateServices_ClearNotes(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
|
||||
@@ -17,11 +17,13 @@ package admin
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"crussell/db"
|
||||
"crussell/testutils"
|
||||
"crussell/handlers/user"
|
||||
"crussell/mw"
|
||||
)
|
||||
@@ -29,20 +31,48 @@ import (
|
||||
// TestAdminUsers_List verifies that an admin can list all users in the
|
||||
// system with their details including account role and type.
|
||||
func TestAdminUsers_List(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
// Create test users
|
||||
_, err := db.DB.Exec(context.Background(), `
|
||||
// Create test users with name history
|
||||
var ninaID, bobID string
|
||||
err := db.DB.QueryRow(context.Background(), `
|
||||
INSERT INTO users (n_first_name, n_last_name, email, phone, date_of_birth, password_hash, account_role, account_type)
|
||||
VALUES
|
||||
('Nina', 'Smith', 'nina@test.com', '+447123456789', '1990-01-01', 'hash1', 'admin', 'email'),
|
||||
('Bob', 'Jones', 'bob@test.com', '+447123456789', '1990-01-01', 'hash2', 'verified_email', 'email'),
|
||||
('Charlie', 'Brown', 'charlie@test.com', '+447123456789', '1990-01-01', 'hash3', 'verified_email', 'email')
|
||||
`)
|
||||
VALUES ('Nina', 'Smith', 'nina@test.com', '+447123456789', '1990-01-01', 'hash1', 'admin', 'email')
|
||||
RETURNING id
|
||||
`).Scan(&ninaID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create users: %v", err)
|
||||
t.Fatalf("failed to create nina: %v", err)
|
||||
}
|
||||
|
||||
err = db.DB.QueryRow(context.Background(), `
|
||||
INSERT INTO users (n_first_name, n_last_name, email, phone, date_of_birth, password_hash, account_role, account_type)
|
||||
VALUES ('Bob', 'Jones', 'bob@test.com', '+447123456789', '1990-01-01', 'hash2', 'verified_email', 'email')
|
||||
RETURNING id
|
||||
`).Scan(&bobID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create bob: %v", err)
|
||||
}
|
||||
|
||||
// Create a completed booking for Nina so she has a completed_count
|
||||
_, err = db.DB.Exec(context.Background(), `
|
||||
INSERT INTO bookings (user_id, start_time, status) VALUES ($1, NOW(), 'completed')
|
||||
`, ninaID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create booking for nina: %v", err)
|
||||
}
|
||||
|
||||
// Insert name history for Bob (previous name that differs from current)
|
||||
_, err = db.DB.Exec(context.Background(), `
|
||||
INSERT INTO name_history (user_id, previous_first_name, previous_last_name)
|
||||
VALUES ($1, 'Bobby', 'Jones')
|
||||
`, bobID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to insert name_history for bob: %v", err)
|
||||
}
|
||||
|
||||
userID, _ := json.Marshal(bobID)
|
||||
_ = userID
|
||||
|
||||
handler := http.HandlerFunc(user.ListAdminUsersHandler)
|
||||
w := makeAdminRequest(handler, "GET", "/api/admin/users", nil)
|
||||
|
||||
@@ -55,19 +85,82 @@ func TestAdminUsers_List(t *testing.T) {
|
||||
t.Fatalf("failed to unmarshal response: %v", err)
|
||||
}
|
||||
|
||||
if response.Total != 3 {
|
||||
t.Errorf("expected 3 users, got %d", response.Total)
|
||||
if response.Total != 2 {
|
||||
t.Errorf("expected 2 users, got %d", response.Total)
|
||||
}
|
||||
|
||||
if len(response.Users) != 3 {
|
||||
t.Errorf("expected 3 users in list, got %d", len(response.Users))
|
||||
if len(response.Users) != 2 {
|
||||
t.Errorf("expected 2 users in list, got %d", len(response.Users))
|
||||
}
|
||||
|
||||
// Verify name history and completed_count in user list
|
||||
var bobFound bool
|
||||
for _, u := range response.Users {
|
||||
if u.ID == bobID {
|
||||
bobFound = true
|
||||
if u.PreviousFirstName == nil || *u.PreviousFirstName != "Bobby" {
|
||||
t.Errorf("expected bob previousFirstName 'Bobby', got %v", u.PreviousFirstName)
|
||||
}
|
||||
if u.PreviousLastName == nil || *u.PreviousLastName != "Jones" {
|
||||
t.Errorf("expected bob previousLastName 'Jones', got %v", u.PreviousLastName)
|
||||
}
|
||||
}
|
||||
if u.ID == ninaID {
|
||||
if u.CompletedCount != 1 {
|
||||
t.Errorf("expected nina completed_count 1, got %d", u.CompletedCount)
|
||||
}
|
||||
// Nina has no name history — should be nil
|
||||
if u.PreviousFirstName != nil {
|
||||
t.Errorf("expected nina previousFirstName nil, got %v", *u.PreviousFirstName)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !bobFound {
|
||||
t.Error("expected bob in user list")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdminUsers_List_Page verifies the page parameter is echoed in the response.
|
||||
// Note: The user list uses cursor-based pagination, not offset-based, so page
|
||||
// is metadata only — actual page navigation is driven by the next_cursor field.
|
||||
func TestAdminUsers_List_Page(t *testing.T) {
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
_, err := db.DB.Exec(context.Background(), `
|
||||
INSERT INTO users (n_first_name, n_last_name, email, phone, date_of_birth, password_hash, account_role, account_type)
|
||||
VALUES ('User', $1, $2, '+447123456789', '1990-01-01', 'hash', 'verified_email', 'email')
|
||||
`, fmt.Sprintf("LastName_%d", i), fmt.Sprintf("user%d@test.com", i))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create user %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
handler := http.HandlerFunc(user.ListAdminUsersHandler)
|
||||
|
||||
w := makeAdminRequest(handler, "GET", "/api/admin/users?page=2", nil)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
var resp user.UserListResponse
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("unmarshal: %v", err)
|
||||
}
|
||||
if resp.Page != 2 {
|
||||
t.Errorf("expected page 2 echoed back, got %d", resp.Page)
|
||||
}
|
||||
if resp.Total != 5 {
|
||||
t.Errorf("expected total 5, got %d", resp.Total)
|
||||
}
|
||||
if resp.PerPage == 0 {
|
||||
t.Error("expected per_page to be set")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdminUsers_Get tests that an admin can retrieve detailed information
|
||||
// about a specific user including their profile and account settings.
|
||||
func TestAdminUsers_Get(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
// Create test user
|
||||
var userID string
|
||||
@@ -104,7 +197,7 @@ func TestAdminUsers_Get(t *testing.T) {
|
||||
// TestAdminUsers_Get_NotFound verifies that requesting details for a
|
||||
// non-existent user returns HTTP 404 Not Found.
|
||||
func TestAdminUsers_Get_NotFound(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
handler := http.HandlerFunc(user.GetAdminUserHandler)
|
||||
// Use 12-char or less ID to avoid CHAR(12) constraint error
|
||||
@@ -119,7 +212,7 @@ func TestAdminUsers_Get_NotFound(t *testing.T) {
|
||||
// identifies which services require patch tests and returns only those services
|
||||
// the user is eligible for based on age requirements.
|
||||
func TestAdminUsers_PatchTests_Eligible(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
// Create test user
|
||||
var userID string
|
||||
@@ -195,7 +288,7 @@ func TestAdminUsers_PatchTests_Eligible(t *testing.T) {
|
||||
// user already has a valid patch test on file, that service is filtered out
|
||||
// from the eligible list (since they've already completed it).
|
||||
func TestAdminUsers_PatchTests_Eligible_WithExisting(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
// Create test user
|
||||
var userID string
|
||||
@@ -281,7 +374,7 @@ func TestAdminUsers_PatchTests_Eligible_WithExisting(t *testing.T) {
|
||||
// TestAdminUsers_AddPatchTest verifies that an admin can record a patch
|
||||
// test completion for a user, creating a user_patch_tests record.
|
||||
func TestAdminUsers_AddPatchTest(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
// Create test user
|
||||
var userID string
|
||||
@@ -340,7 +433,7 @@ func TestAdminUsers_AddPatchTest(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdminUsers_AddPatchTest_InvalidPatchTest(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
// Create test user
|
||||
var userID string
|
||||
@@ -367,7 +460,7 @@ func TestAdminUsers_AddPatchTest_InvalidPatchTest(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.
|
||||
func TestAdminUsers_NonAdmin(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
// Create regular user in DB
|
||||
_, err := db.DB.Exec(context.Background(), `
|
||||
@@ -421,7 +514,7 @@ func TestAdminUsers_NonAdmin(t *testing.T) {
|
||||
// TestAdminUsers_Get_Success is an additional test verifying admin can
|
||||
// retrieve user details including ID, name, email, and account role.
|
||||
func TestAdminUsers_Get_Success(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
// Create a test user
|
||||
var userID string
|
||||
@@ -434,6 +527,15 @@ func TestAdminUsers_Get_Success(t *testing.T) {
|
||||
t.Fatalf("failed to create test user: %v", err)
|
||||
}
|
||||
|
||||
// Insert name history (simulating a previous name change)
|
||||
_, err = db.DB.Exec(context.Background(), `
|
||||
INSERT INTO name_history (user_id, previous_first_name, previous_last_name)
|
||||
VALUES ($1, 'OldFirst', 'OldLast')
|
||||
`, userID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to insert name_history: %v", err)
|
||||
}
|
||||
|
||||
// Call admin get user endpoint
|
||||
handler := http.HandlerFunc(user.GetAdminUserHandler)
|
||||
w := makeAdminRequest(handler, "GET", "/api/admin/users/"+userID, nil)
|
||||
@@ -464,12 +566,60 @@ func TestAdminUsers_Get_Success(t *testing.T) {
|
||||
if resp.AccountRole != "verified_email" {
|
||||
t.Errorf("expected account_role 'verified_email', got '%s'", resp.AccountRole)
|
||||
}
|
||||
|
||||
// Verify previous name from history is returned
|
||||
if resp.PreviousFirstName == nil || *resp.PreviousFirstName != "OldFirst" {
|
||||
t.Errorf("expected previousFirstName 'OldFirst', got %v", resp.PreviousFirstName)
|
||||
}
|
||||
if resp.PreviousLastName == nil || *resp.PreviousLastName != "OldLast" {
|
||||
t.Errorf("expected previousLastName 'OldLast', got %v", resp.PreviousLastName)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdminUsers_Get_ShowsPreviousNameOnlyWhenDifferent verifies that previous
|
||||
// name is omitted when the name_history entry matches the current user name.
|
||||
func TestAdminUsers_Get_ShowsPreviousNameOnlyWhenDifferent(t *testing.T) {
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
var userID string
|
||||
err := db.DB.QueryRow(context.Background(), `
|
||||
INSERT INTO users (n_first_name, n_last_name, email, phone, date_of_birth, password_hash, account_role, account_type)
|
||||
VALUES ('Alice', 'Smith', 'alice@test.com', '+447123456789', '1990-01-01', 'hash', 'verified_email', 'email')
|
||||
RETURNING id
|
||||
`).Scan(&userID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create user: %v", err)
|
||||
}
|
||||
|
||||
// Insert name_history with the SAME name as current — should be omitted
|
||||
_, err = db.DB.Exec(context.Background(), `
|
||||
INSERT INTO name_history (user_id, previous_first_name, previous_last_name)
|
||||
VALUES ($1, 'Alice', 'Smith')
|
||||
`, userID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to insert name_history: %v", err)
|
||||
}
|
||||
|
||||
handler := http.HandlerFunc(user.GetAdminUserHandler)
|
||||
w := makeAdminRequest(handler, "GET", "/api/admin/users/"+userID, nil)
|
||||
|
||||
var resp user.AdminUserDetail
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("failed to unmarshal: %v", err)
|
||||
}
|
||||
|
||||
if resp.PreviousFirstName != nil {
|
||||
t.Errorf("expected previousFirstName nil (same as current), got %v", *resp.PreviousFirstName)
|
||||
}
|
||||
if resp.PreviousLastName != nil {
|
||||
t.Errorf("expected previousLastName nil (same as current), got %v", *resp.PreviousLastName)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdminUsers_AddPatchTest_Duplicate verifies that recording the same patch test
|
||||
// twice updates the tested_at timestamp (upsert behavior).
|
||||
func TestAdminUsers_AddPatchTest_Duplicate(t *testing.T) {
|
||||
resetTestData(t)
|
||||
testutils.SetupTestDB(t)
|
||||
|
||||
// Create test user
|
||||
var userID string
|
||||
|
||||
Reference in New Issue
Block a user