//go:build test // +build test package admin import ( "context" "net/http" "testing" "time" "crussell/db" "crussell/handlers/bookings" "crussell/testutils/fixtures" ) // ============================================================================= // List Admin Bookings Tests // ============================================================================= func TestAdminBookings_List(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() adminID, err := fixtures.CreateTestAdminUser(db.DB) if err != nil { t.Fatalf("failed to create admin user: %v", err) } defer fixtures.DeleteUser(db.DB, adminID) userID, err := fixtures.CreateTestUser(db.DB) if err != nil { t.Fatalf("failed to create test user: %v", err) } defer fixtures.DeleteUser(db.DB, userID) serviceID, err := fixtures.CreateTestService(db.DB) if err != nil { t.Fatalf("failed to create test service: %v", err) } defer fixtures.DeleteService(db.DB, serviceID) bookingID1, err := fixtures.CreateTestBooking(db.DB, userID, serviceID) if err != nil { t.Fatalf("failed to create test booking 1: %v", err) } defer fixtures.DeleteBooking(db.DB, bookingID1) bookingID2, err := fixtures.CreateTestBooking(db.DB, userID, serviceID) if err != nil { t.Fatalf("failed to create test booking 2: %v", err) } defer fixtures.DeleteBooking(db.DB, bookingID2) handler := http.HandlerFunc(bookings.GetAllAdminBookingsHandler) w := makeAdminRequest(handler, "GET", "/api/admin/bookings", nil) if w.Code != http.StatusOK { t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String()) } var resp bookings.BookingListResponse if err := parseResponseBody(w, &resp); err != nil { t.Fatalf("failed to parse response: %v", err) } if len(resp.Bookings) != 2 { t.Errorf("expected 2 bookings, got %d", len(resp.Bookings)) } if resp.Total != 2 { t.Errorf("expected total 2, got %d", resp.Total) } } func TestAdminBookings_List_FilterByStatus(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() adminID, err := fixtures.CreateTestAdminUser(db.DB) if err != nil { t.Fatalf("failed to create admin user: %v", err) } defer fixtures.DeleteUser(db.DB, adminID) userID, err := fixtures.CreateTestUser(db.DB) if err != nil { t.Fatalf("failed to create test user: %v", err) } defer fixtures.DeleteUser(db.DB, userID) serviceID, err := fixtures.CreateTestService(db.DB) if err != nil { t.Fatalf("failed to create test service: %v", err) } defer fixtures.DeleteService(db.DB, serviceID) bookingID, err := fixtures.CreateTestBooking(db.DB, userID, serviceID) if err != nil { t.Fatalf("failed to create test booking: %v", err) } defer fixtures.DeleteBooking(db.DB, bookingID) handler := http.HandlerFunc(bookings.GetAllAdminBookingsHandler) w := makeAdminRequest(handler, "GET", "/api/admin/bookings?status=pending", nil) if w.Code != http.StatusOK { t.Errorf("expected status 200, got %d", w.Code) } var resp bookings.BookingListResponse if err := parseResponseBody(w, &resp); err != nil { t.Fatalf("failed to parse response: %v", err) } if len(resp.Bookings) != 1 { t.Errorf("expected 1 pending booking, got %d", len(resp.Bookings)) } w = makeAdminRequest(handler, "GET", "/api/admin/bookings?status=completed", nil) if w.Code != http.StatusOK { t.Errorf("expected status 200, got %d", w.Code) } if err := parseResponseBody(w, &resp); err != nil { t.Fatalf("failed to parse response: %v", err) } if len(resp.Bookings) != 0 { t.Errorf("expected 0 bookings for completed status, got %d", len(resp.Bookings)) } } // ============================================================================= // Admin Create Booking Tests // ============================================================================= func TestAdminBookings_Create(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() adminID, err := fixtures.CreateTestAdminUser(db.DB) if err != nil { t.Fatalf("failed to create admin user: %v", err) } defer fixtures.DeleteUser(db.DB, adminID) userID, err := fixtures.CreateTestUser(db.DB) if err != nil { t.Fatalf("failed to create test user: %v", err) } defer fixtures.DeleteUser(db.DB, userID) serviceID, err := fixtures.CreateTestService(db.DB) if err != nil { t.Fatalf("failed to create test service: %v", err) } defer fixtures.DeleteService(db.DB, serviceID) futureTime := time.Now().Add(72 * time.Hour).Truncate(time.Second) req := bookings.AdminCreateBookingForUserRequest{ UserID: userID, StartTime: futureTime, ServiceIDs: []string{serviceID}, } handler := http.HandlerFunc(bookings.AdminCreateBookingForUserHandler) w := makeAdminRequest(handler, "POST", "/api/admin/bookings", req) if w.Code != http.StatusCreated { t.Errorf("expected status 201, got %d. body: %s", w.Code, w.Body.String()) } var booking bookings.Booking if err := parseResponseBody(w, &booking); err != nil { t.Fatalf("failed to parse response: %v", err) } if booking.Status != "confirmed" { t.Errorf("expected status 'confirmed', got %s", booking.Status) } var count int err = db.DB.QueryRow(context.Background(), "SELECT COUNT(*) FROM bookings WHERE user_id = $1", userID).Scan(&count) if err != nil { t.Errorf("failed to query bookings: %v", err) } if count != 1 { t.Errorf("expected 1 booking, got %d", count) } } func TestAdminBookings_Create_InvalidInput(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() adminID, err := fixtures.CreateTestAdminUser(db.DB) if err != nil { t.Fatalf("failed to create admin user: %v", err) } defer fixtures.DeleteUser(db.DB, adminID) userID, err := fixtures.CreateTestUser(db.DB) if err != nil { t.Fatalf("failed to create test user: %v", err) } defer fixtures.DeleteUser(db.DB, userID) tests := []struct { name string req bookings.AdminCreateBookingForUserRequest }{ { name: "missing user ID", req: bookings.AdminCreateBookingForUserRequest{ StartTime: time.Now().Add(72 * time.Hour), ServiceIDs: []string{"some-service-id"}, }, }, { name: "missing start time", req: bookings.AdminCreateBookingForUserRequest{ UserID: userID, ServiceIDs: []string{"some-service-id"}, }, }, { name: "missing service IDs", req: bookings.AdminCreateBookingForUserRequest{ UserID: userID, StartTime: time.Now().Add(72 * time.Hour), }, }, { name: "empty service IDs", req: bookings.AdminCreateBookingForUserRequest{ UserID: userID, StartTime: time.Now().Add(72 * time.Hour), ServiceIDs: []string{}, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { handler := http.HandlerFunc(bookings.AdminCreateBookingForUserHandler) w := makeAdminRequest(handler, "POST", "/api/admin/bookings", tt.req) if w.Code != http.StatusBadRequest { t.Errorf("expected status 400, got %d", w.Code) } }) } } // ============================================================================= // Search Admin Bookings Tests // ============================================================================= func TestAdminBookings_Search(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() adminID, err := fixtures.CreateTestAdminUser(db.DB) if err != nil { t.Fatalf("failed to create admin user: %v", err) } defer fixtures.DeleteUser(db.DB, adminID) userID, err := fixtures.CreateTestUser(db.DB) if err != nil { t.Fatalf("failed to create test user: %v", err) } defer fixtures.DeleteUser(db.DB, userID) serviceID, err := fixtures.CreateTestService(db.DB) if err != nil { t.Fatalf("failed to create test service: %v", err) } defer fixtures.DeleteService(db.DB, serviceID) bookingID, err := fixtures.CreateTestBooking(db.DB, userID, serviceID) if err != nil { t.Fatalf("failed to create test booking: %v", err) } defer fixtures.DeleteBooking(db.DB, bookingID) _, err = db.DB.Exec(context.Background(), "UPDATE bookings SET notes = 'Test booking for search' WHERE id = $1", bookingID) if err != nil { t.Fatalf("failed to update booking notes: %v", err) } handler := http.HandlerFunc(bookings.SearchAdminBookingsHandler) w := makeAdminRequest(handler, "GET", "/api/admin/bookings/search?q=Test", nil) if w.Code != http.StatusOK { t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String()) } var resp bookings.BookingListResponse if err := parseResponseBody(w, &resp); err != nil { t.Fatalf("failed to parse response: %v", err) } if len(resp.Bookings) != 1 { t.Errorf("expected 1 booking, got %d", len(resp.Bookings)) } } func TestAdminBookings_Search_MissingQuery(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() adminID, err := fixtures.CreateTestAdminUser(db.DB) if err != nil { t.Fatalf("failed to create admin user: %v", err) } defer fixtures.DeleteUser(db.DB, adminID) handler := http.HandlerFunc(bookings.SearchAdminBookingsHandler) w := makeAdminRequest(handler, "GET", "/api/admin/bookings/search", nil) if w.Code != http.StatusBadRequest { t.Errorf("expected status 400, got %d", w.Code) } } // ============================================================================= // Get Single Admin Booking Tests // ============================================================================= func TestAdminBookings_Get(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() adminID, err := fixtures.CreateTestAdminUser(db.DB) if err != nil { t.Fatalf("failed to create admin user: %v", err) } defer fixtures.DeleteUser(db.DB, adminID) userID, err := fixtures.CreateTestUser(db.DB) if err != nil { t.Fatalf("failed to create test user: %v", err) } defer fixtures.DeleteUser(db.DB, userID) serviceID, err := fixtures.CreateTestService(db.DB) if err != nil { t.Fatalf("failed to create test service: %v", err) } defer fixtures.DeleteService(db.DB, serviceID) bookingID, err := fixtures.CreateTestBooking(db.DB, userID, serviceID) if err != nil { t.Fatalf("failed to create test booking: %v", err) } defer fixtures.DeleteBooking(db.DB, bookingID) handler := http.HandlerFunc(bookings.GetAdminBookingHandler) w := makeAdminRequest(handler, "GET", "/api/admin/bookings/"+bookingID, nil) if w.Code != http.StatusOK { t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String()) } var booking bookings.Booking if err := parseResponseBody(w, &booking); err != nil { t.Fatalf("failed to parse response: %v", err) } if booking.ID != bookingID { t.Errorf("expected booking ID %s, got %s", bookingID, booking.ID) } if booking.User == nil { t.Error("expected user to be populated") } if len(booking.Services) != 1 { t.Errorf("expected 1 service, got %d", len(booking.Services)) } } func TestAdminBookings_Get_NotFound(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() adminID, err := fixtures.CreateTestAdminUser(db.DB) if err != nil { t.Fatalf("failed to create admin user: %v", err) } defer fixtures.DeleteUser(db.DB, adminID) handler := http.HandlerFunc(bookings.GetAdminBookingHandler) w := makeAdminRequest(handler, "GET", "/api/admin/bookings/nonexistent-id", nil) if w.Code != http.StatusNotFound { t.Errorf("expected status 404, got %d", w.Code) } } // ============================================================================= // Get User's Bookings Tests (Admin view) // ============================================================================= func TestAdminBookings_GetUserBookings(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() adminID, err := fixtures.CreateTestAdminUser(db.DB) if err != nil { t.Fatalf("failed to create admin user: %v", err) } defer fixtures.DeleteUser(db.DB, adminID) userID, err := fixtures.CreateTestUser(db.DB) if err != nil { t.Fatalf("failed to create test user: %v", err) } defer fixtures.DeleteUser(db.DB, userID) serviceID, err := fixtures.CreateTestService(db.DB) if err != nil { t.Fatalf("failed to create test service: %v", err) } defer fixtures.DeleteService(db.DB, serviceID) bookingID1, err := fixtures.CreateTestBooking(db.DB, userID, serviceID) if err != nil { t.Fatalf("failed to create test booking 1: %v", err) } defer fixtures.DeleteBooking(db.DB, bookingID1) bookingID2, err := fixtures.CreateTestBooking(db.DB, userID, serviceID) if err != nil { t.Fatalf("failed to create test booking 2: %v", err) } defer fixtures.DeleteBooking(db.DB, bookingID2) handler := http.HandlerFunc(bookings.GetAllBookingsByUserHandler) w := makeAdminRequest(handler, "GET", "/api/admin/bookings/user/"+userID, nil) if w.Code != http.StatusOK { t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String()) } var resp bookings.BookingListResponse if err := parseResponseBody(w, &resp); err != nil { t.Fatalf("failed to parse response: %v", err) } if len(resp.Bookings) != 2 { t.Errorf("expected 2 bookings, got %d", len(resp.Bookings)) } if resp.Total != 2 { t.Errorf("expected total 2, got %d", resp.Total) } } // ============================================================================= // Progress Booking Tests // ============================================================================= func TestAdminBookings_Progress(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() adminID, err := fixtures.CreateTestAdminUser(db.DB) if err != nil { t.Fatalf("failed to create admin user: %v", err) } defer fixtures.DeleteUser(db.DB, adminID) userID, err := fixtures.CreateTestUser(db.DB) if err != nil { t.Fatalf("failed to create test user: %v", err) } defer fixtures.DeleteUser(db.DB, userID) serviceID, err := fixtures.CreateTestService(db.DB) if err != nil { t.Fatalf("failed to create test service: %v", err) } defer fixtures.DeleteService(db.DB, serviceID) bookingID, err := fixtures.CreateTestBooking(db.DB, userID, serviceID) if err != nil { t.Fatalf("failed to create test booking: %v", err) } defer fixtures.DeleteBooking(db.DB, bookingID) req := bookings.ProgressBookingRequest{ Status: "confirmed", } handler := http.HandlerFunc(bookings.ProgressBookingHandler) w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID+"/progress", req) if w.Code != http.StatusOK { t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String()) } var booking bookings.Booking if err := parseResponseBody(w, &booking); err != nil { t.Fatalf("failed to parse response: %v", err) } if booking.Status != "confirmed" { t.Errorf("expected status 'confirmed', got %s", booking.Status) } var dbStatus string err = db.DB.QueryRow(context.Background(), "SELECT status FROM bookings WHERE id = $1", bookingID).Scan(&dbStatus) if err != nil { t.Fatalf("failed to query booking: %v", err) } if dbStatus != "confirmed" { t.Errorf("expected status 'confirmed' in DB, got %s", dbStatus) } } func TestAdminBookings_Progress_InvalidStatus(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() adminID, err := fixtures.CreateTestAdminUser(db.DB) if err != nil { t.Fatalf("failed to create admin user: %v", err) } defer fixtures.DeleteUser(db.DB, adminID) userID, err := fixtures.CreateTestUser(db.DB) if err != nil { t.Fatalf("failed to create test user: %v", err) } defer fixtures.DeleteUser(db.DB, userID) serviceID, err := fixtures.CreateTestService(db.DB) if err != nil { t.Fatalf("failed to create test service: %v", err) } defer fixtures.DeleteService(db.DB, serviceID) bookingID, err := fixtures.CreateTestBooking(db.DB, userID, serviceID) if err != nil { t.Fatalf("failed to create test booking: %v", err) } defer fixtures.DeleteBooking(db.DB, bookingID) req := bookings.ProgressBookingRequest{ Status: "invalid_status", } handler := http.HandlerFunc(bookings.ProgressBookingHandler) w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/"+bookingID+"/progress", req) if w.Code != http.StatusBadRequest { t.Errorf("expected status 400, got %d", w.Code) } } func TestAdminBookings_Progress_NotFound(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() adminID, err := fixtures.CreateTestAdminUser(db.DB) if err != nil { t.Fatalf("failed to create admin user: %v", err) } defer fixtures.DeleteUser(db.DB, adminID) req := bookings.ProgressBookingRequest{ Status: "confirmed", } handler := http.HandlerFunc(bookings.ProgressBookingHandler) w := makeAdminRequest(handler, "PUT", "/api/admin/bookings/nonexistent-id/progress", req) if w.Code != http.StatusNotFound { t.Errorf("expected status 404, got %d", w.Code) } } // ============================================================================= // Confirm Booking Tests // ============================================================================= func TestAdminBookings_Confirm(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() adminID, err := fixtures.CreateTestAdminUser(db.DB) if err != nil { t.Fatalf("failed to create admin user: %v", err) } defer fixtures.DeleteUser(db.DB, adminID) userID, err := fixtures.CreateTestUser(db.DB) if err != nil { t.Fatalf("failed to create test user: %v", err) } defer fixtures.DeleteUser(db.DB, userID) serviceID, err := fixtures.CreateTestService(db.DB) if err != nil { t.Fatalf("failed to create test service: %v", err) } defer fixtures.DeleteService(db.DB, serviceID) bookingID, err := fixtures.CreateTestBooking(db.DB, userID, serviceID) if err != nil { t.Fatalf("failed to create test booking: %v", err) } defer fixtures.DeleteBooking(db.DB, bookingID) req := bookings.ConfirmBookingRequest{} handler := http.HandlerFunc(bookings.ConfirmBookingHandler) w := makeAdminRequest(handler, "POST", "/api/admin/bookings/"+bookingID+"/confirm", req) if w.Code != http.StatusOK { t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String()) } var booking bookings.Booking if err := parseResponseBody(w, &booking); err != nil { t.Fatalf("failed to parse response: %v", err) } if booking.Status != "confirmed" { t.Errorf("expected status 'confirmed', got %s", booking.Status) } } func TestAdminBookings_Confirm_AlreadyConfirmed(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() adminID, err := fixtures.CreateTestAdminUser(db.DB) if err != nil { t.Fatalf("failed to create admin user: %v", err) } defer fixtures.DeleteUser(db.DB, adminID) userID, err := fixtures.CreateTestUser(db.DB) if err != nil { t.Fatalf("failed to create test user: %v", err) } defer fixtures.DeleteUser(db.DB, userID) serviceID, err := fixtures.CreateTestService(db.DB) if err != nil { t.Fatalf("failed to create test service: %v", err) } defer fixtures.DeleteService(db.DB, serviceID) ctx := context.Background() bookingID, err := fixtures.CreateTestBooking(db.DB, userID, serviceID) if err != nil { t.Fatalf("failed to create test booking: %v", err) } defer fixtures.DeleteBooking(db.DB, bookingID) _, err = db.DB.Exec(ctx, "UPDATE bookings SET status = 'confirmed' WHERE id = $1", bookingID) if err != nil { t.Fatalf("failed to update booking status: %v", err) } req := bookings.ConfirmBookingRequest{} handler := http.HandlerFunc(bookings.ConfirmBookingHandler) w := makeAdminRequest(handler, "POST", "/api/admin/bookings/"+bookingID+"/confirm", req) if w.Code != http.StatusNotFound { t.Errorf("expected status 404, got %d", w.Code) } } // ============================================================================= // Cancel Booking Tests // ============================================================================= func TestAdminBookings_Cancel(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() adminID, err := fixtures.CreateTestAdminUser(db.DB) if err != nil { t.Fatalf("failed to create admin user: %v", err) } defer fixtures.DeleteUser(db.DB, adminID) userID, err := fixtures.CreateTestUser(db.DB) if err != nil { t.Fatalf("failed to create test user: %v", err) } defer fixtures.DeleteUser(db.DB, userID) serviceID, err := fixtures.CreateTestService(db.DB) if err != nil { t.Fatalf("failed to create test service: %v", err) } defer fixtures.DeleteService(db.DB, serviceID) ctx := context.Background() bookingID, err := fixtures.CreateTestBooking(db.DB, userID, serviceID) if err != nil { t.Fatalf("failed to create test booking: %v", err) } defer fixtures.DeleteBooking(db.DB, bookingID) _, err = db.DB.Exec(ctx, "UPDATE bookings SET status = 'confirmed' WHERE id = $1", bookingID) if err != nil { t.Fatalf("failed to update booking status: %v", err) } handler := http.HandlerFunc(bookings.AdminCancelBookingHandler) w := makeAdminRequest(handler, "POST", "/api/admin/bookings/"+bookingID+"/cancel", nil) if w.Code != http.StatusNoContent { t.Errorf("expected status 204, got %d. body: %s", w.Code, w.Body.String()) } var dbStatus string err = db.DB.QueryRow(ctx, "SELECT status FROM bookings WHERE id = $1", bookingID).Scan(&dbStatus) if err != nil { t.Fatalf("failed to query booking: %v", err) } if dbStatus != "we_cancelled" { t.Errorf("expected status 'we_cancelled' in DB, got %s", dbStatus) } } func TestAdminBookings_Cancel_NotFound(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() adminID, err := fixtures.CreateTestAdminUser(db.DB) if err != nil { t.Fatalf("failed to create admin user: %v", err) } defer fixtures.DeleteUser(db.DB, adminID) handler := http.HandlerFunc(bookings.AdminCancelBookingHandler) w := makeAdminRequest(handler, "POST", "/api/admin/bookings/nonexistent-id/cancel", nil) if w.Code != http.StatusNotFound { t.Errorf("expected status 404, got %d", w.Code) } } // ============================================================================= // Non-Admin Tests // ============================================================================= func TestAdminBookings_NonAdmin(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() _, err := fixtures.CreateTestUser(db.DB) if err != nil { t.Fatalf("failed to create user: %v", err) } userID, err := fixtures.CreateTestUser(db.DB) if err != nil { t.Fatalf("failed to create test user: %v", err) } defer fixtures.DeleteUser(db.DB, userID) serviceID, err := fixtures.CreateTestService(db.DB) if err != nil { t.Fatalf("failed to create test service: %v", err) } defer fixtures.DeleteService(db.DB, serviceID) bookingID, err := fixtures.CreateTestBooking(db.DB, userID, serviceID) if err != nil { t.Fatalf("failed to create test booking: %v", err) } defer fixtures.DeleteBooking(db.DB, bookingID) w := makeUserRequest(http.HandlerFunc(bookings.GetAllAdminBookingsHandler), "GET", "/api/admin/bookings", nil) if w.Code != http.StatusForbidden { t.Errorf("LIST: expected status 403, got %d", w.Code) } req := bookings.AdminCreateBookingForUserRequest{ UserID: userID, StartTime: time.Now().Add(72 * time.Hour), ServiceIDs: []string{serviceID}, } w = makeUserRequest(http.HandlerFunc(bookings.AdminCreateBookingForUserHandler), "POST", "/api/admin/bookings", req) if w.Code != http.StatusForbidden { t.Errorf("CREATE: expected status 403, got %d", w.Code) } w = makeUserRequest(http.HandlerFunc(bookings.SearchAdminBookingsHandler), "GET", "/api/admin/bookings/search?q=test", nil) if w.Code != http.StatusForbidden { t.Errorf("SEARCH: expected status 403, got %d", w.Code) } w = makeUserRequest(http.HandlerFunc(bookings.GetAdminBookingHandler), "GET", "/api/admin/bookings/"+bookingID, nil) if w.Code != http.StatusForbidden { t.Errorf("GET: expected status 403, got %d", w.Code) } progressReq := bookings.ProgressBookingRequest{Status: "confirmed"} w = makeUserRequest(http.HandlerFunc(bookings.ProgressBookingHandler), "PUT", "/api/admin/bookings/"+bookingID+"/progress", progressReq) if w.Code != http.StatusForbidden { t.Errorf("PROGRESS: expected status 403, got %d", w.Code) } confirmReq := bookings.ConfirmBookingRequest{} w = makeUserRequest(http.HandlerFunc(bookings.ConfirmBookingHandler), "POST", "/api/admin/bookings/"+bookingID+"/confirm", confirmReq) if w.Code != http.StatusForbidden { t.Errorf("CONFIRM: expected status 403, got %d", w.Code) } w = makeUserRequest(http.HandlerFunc(bookings.AdminCancelBookingHandler), "POST", "/api/admin/bookings/"+bookingID+"/cancel", nil) if w.Code != http.StatusForbidden { t.Errorf("CANCEL: expected status 403, got %d", w.Code) } } // ============================================================================= // Admin Booking Holiday Conflict Tests // ============================================================================= func TestAdminBookings_Create_DuringHolidayHours_Rejected(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() adminID, err := fixtures.CreateTestAdminUser(db.DB) if err != nil { t.Fatalf("failed to create admin user: %v", err) } defer fixtures.DeleteUser(db.DB, adminID) userID, err := fixtures.CreateTestUser(db.DB) if err != nil { t.Fatalf("failed to create test user: %v", err) } defer fixtures.DeleteUser(db.DB, userID) serviceID, err := fixtures.CreateTestService(db.DB) if err != nil { t.Fatalf("failed to create test service: %v", err) } defer fixtures.DeleteService(db.DB, serviceID) // Create an exceptional (holiday) hours group for tomorrow tomorrowDate := time.Now().AddDate(0, 0, 1) groupID := tomorrowDate.Format("2006-01-02") + "_holiday" _, err = db.DB.Exec(context.Background(), ` INSERT INTO exceptional_working_hours_groups (id, name, start_date, end_date) VALUES ($1, $2, $3, $4) `, groupID, "Holiday Closure", tomorrowDate, tomorrowDate) if err != nil { t.Fatalf("failed to create holiday group: %v", err) } defer db.DB.Exec(context.Background(), "DELETE FROM exceptional_working_hours_groups WHERE id = $1", groupID) // Add closed hours for tomorrow (9 AM - 5 PM, no slots available) _, err = db.DB.Exec(context.Background(), ` INSERT INTO exceptional_working_hours (group_id, day_of_week, opening_time, closing_time) VALUES ($1, $2, $3, $4) `, groupID, int(tomorrowDate.Weekday()), "23:59", "00:00") // Closed all day if err != nil { t.Fatalf("failed to create holiday hours: %v", err) } // Try to create booking during holiday - should fail tomorrowTime := tomorrowDate.Add(14 * time.Hour).Truncate(time.Second) // 2 PM tomorrow req := bookings.AdminCreateBookingForUserRequest{ UserID: userID, StartTime: tomorrowTime, ServiceIDs: []string{serviceID}, } handler := http.HandlerFunc(bookings.AdminCreateBookingForUserHandler) w := makeAdminRequest(handler, "POST", "/api/admin/bookings", req) // Should be rejected (400 or 409 depending on implementation) if w.Code == http.StatusCreated { t.Errorf("expected booking to be rejected during holiday hours, but got 201") } } // ============================================================================= // Search Edge Case Tests // ============================================================================= func TestAdminBookings_Search_CaseInsensitive(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() adminID, err := fixtures.CreateTestAdminUser(db.DB) if err != nil { t.Fatalf("failed to create admin user: %v", err) } defer fixtures.DeleteUser(db.DB, adminID) userID, err := fixtures.CreateTestUser(db.DB) if err != nil { t.Fatalf("failed to create test user: %v", err) } defer fixtures.DeleteUser(db.DB, userID) serviceID, err := fixtures.CreateTestService(db.DB) if err != nil { t.Fatalf("failed to create test service: %v", err) } defer fixtures.DeleteService(db.DB, serviceID) bookingID, err := fixtures.CreateTestBooking(db.DB, userID, serviceID) if err != nil { t.Fatalf("failed to create test booking: %v", err) } defer fixtures.DeleteBooking(db.DB, bookingID) // Update notes with mixed case _, err = db.DB.Exec(context.Background(), "UPDATE bookings SET notes = 'TestBooking With MixedCase' WHERE id = $1", bookingID) if err != nil { t.Fatalf("failed to update booking notes: %v", err) } handler := http.HandlerFunc(bookings.SearchAdminBookingsHandler) // Test 1: Uppercase search should find mixed case notes w := makeAdminRequest(handler, "GET", "/api/admin/bookings/search?q=TESTBOOKING", nil) if w.Code != http.StatusOK { t.Errorf("expected status 200, got %d", w.Code) } var resp bookings.BookingListResponse if err := parseResponseBody(w, &resp); err != nil { t.Fatalf("failed to parse response: %v", err) } if len(resp.Bookings) != 1 { t.Errorf("expected 1 booking for uppercase search, got %d", len(resp.Bookings)) } // Test 2: Lowercase search should also find w = makeAdminRequest(handler, "GET", "/api/admin/bookings/search?q=testbooking", nil) if w.Code != http.StatusOK { t.Errorf("expected status 200, got %d", w.Code) } if err := parseResponseBody(w, &resp); err != nil { t.Fatalf("failed to parse response: %v", err) } if len(resp.Bookings) != 1 { t.Errorf("expected 1 booking for lowercase search, got %d", len(resp.Bookings)) } } func TestAdminBookings_Search_NoResults(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() adminID, err := fixtures.CreateTestAdminUser(db.DB) if err != nil { t.Fatalf("failed to create admin user: %v", err) } defer fixtures.DeleteUser(db.DB, adminID) userID, err := fixtures.CreateTestUser(db.DB) if err != nil { t.Fatalf("failed to create test user: %v", err) } defer fixtures.DeleteUser(db.DB, userID) serviceID, err := fixtures.CreateTestService(db.DB) if err != nil { t.Fatalf("failed to create test service: %v", err) } defer fixtures.DeleteService(db.DB, serviceID) bookingID, err := fixtures.CreateTestBooking(db.DB, userID, serviceID) if err != nil { t.Fatalf("failed to create test booking: %v", err) } defer fixtures.DeleteBooking(db.DB, bookingID) handler := http.HandlerFunc(bookings.SearchAdminBookingsHandler) // Search with non-matching query w := makeAdminRequest(handler, "GET", "/api/admin/bookings/search?q=NONEXISTENT_QUERY_XYZ123", nil) if w.Code != http.StatusOK { t.Errorf("expected status 200 for no results, got %d", w.Code) } var resp bookings.BookingListResponse if err := parseResponseBody(w, &resp); err != nil { t.Fatalf("failed to parse response: %v", err) } if len(resp.Bookings) != 0 { t.Errorf("expected 0 bookings for non-matching search, got %d", len(resp.Bookings)) } if resp.Total != 0 { t.Errorf("expected total 0 for non-matching search, got %d", resp.Total) } } func TestAdminBookings_Search_MultipleResults(t *testing.T) { cleanup := setupTestDB(t) defer cleanup() adminID, err := fixtures.CreateTestAdminUser(db.DB) if err != nil { t.Fatalf("failed to create admin user: %v", err) } defer fixtures.DeleteUser(db.DB, adminID) userID, err := fixtures.CreateTestUser(db.DB) if err != nil { t.Fatalf("failed to create test user: %v", err) } defer fixtures.DeleteUser(db.DB, userID) serviceID, err := fixtures.CreateTestService(db.DB) if err != nil { t.Fatalf("failed to create test service: %v", err) } defer fixtures.DeleteService(db.DB, serviceID) // Create multiple bookings with similar notes bookingID1, err := fixtures.CreateTestBooking(db.DB, userID, serviceID) if err != nil { t.Fatalf("failed to create test booking 1: %v", err) } defer fixtures.DeleteBooking(db.DB, bookingID1) bookingID2, err := fixtures.CreateTestBooking(db.DB, userID, serviceID) if err != nil { t.Fatalf("failed to create test booking 2: %v", err) } defer fixtures.DeleteBooking(db.DB, bookingID2) // Update both with searchable notes _, err = db.DB.Exec(context.Background(), "UPDATE bookings SET notes = 'Search Query Pattern' WHERE id = $1", bookingID1) if err != nil { t.Fatalf("failed to update booking 1: %v", err) } _, err = db.DB.Exec(context.Background(), "UPDATE bookings SET notes = 'Another Search Query' WHERE id = $1", bookingID2) if err != nil { t.Fatalf("failed to update booking 2: %v", err) } handler := http.HandlerFunc(bookings.SearchAdminBookingsHandler) // Search for "Query" - should match both w := makeAdminRequest(handler, "GET", "/api/admin/bookings/search?q=Query", nil) if w.Code != http.StatusOK { t.Errorf("expected status 200, got %d", w.Code) } var resp bookings.BookingListResponse if err := parseResponseBody(w, &resp); err != nil { t.Fatalf("failed to parse response: %v", err) } if len(resp.Bookings) != 2 { t.Errorf("expected 2 bookings matching 'Query', got %d", len(resp.Bookings)) } if resp.Total != 2 { t.Errorf("expected total 2, got %d", resp.Total) } }