Fixed user cancelation logic

This commit is contained in:
2026-03-07 21:00:16 +00:00
parent 94adc7f54c
commit c7c10bc177
3 changed files with 835 additions and 229 deletions
+279
View File
@@ -816,6 +816,285 @@ 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) {
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)
// Booking stays in 'pending' status (no confirmation)
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())
}
// Verify status changed to we_cancelled
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)
}
// Verify NO admin notification was created for pending cancellations
var notifCount int
err = db.DB.QueryRow(ctx,
`SELECT COUNT(*) FROM admin_notifications WHERE booking_id = $1`,
bookingID).Scan(&notifCount)
if err != nil {
t.Fatalf("failed to query notifications: %v", err)
}
if notifCount != 0 {
t.Errorf("expected 0 admin notifications for pending cancel, got %d", notifCount)
}
}
// TestAdminBookings_Cancel_ConfirmedCreatesNotification verifies that cancelling a confirmed
// booking creates an admin notification for staff awareness.
func TestAdminBookings_Cancel_ConfirmedCreatesNotification(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)
// Confirm the booking
_, err = db.DB.Exec(ctx, "UPDATE bookings SET status = 'confirmed' WHERE id = $1", bookingID)
if err != nil {
t.Fatalf("failed to confirm booking: %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())
}
// Verify status changed to we_cancelled
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)
}
// Verify admin notification WAS created for confirmed->cancelled
var notifCount int
err = db.DB.QueryRow(ctx,
`SELECT COUNT(*) FROM admin_notifications WHERE booking_id = $1 AND reason = 'cancelled_booking'`,
bookingID).Scan(&notifCount)
if err != nil {
t.Fatalf("failed to query notifications: %v", err)
}
if notifCount != 1 {
t.Errorf("expected 1 admin notification for confirmed cancel, got %d", notifCount)
}
}
// TestAdminBookings_Cancel_InProgressStatus verifies cancellation of in-progress bookings.
func TestAdminBookings_Cancel_InProgressStatus(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)
// Set booking to in-progress status
_, err = db.DB.Exec(ctx, "UPDATE bookings SET status = 'in_progress' WHERE id = $1", bookingID)
if err != nil {
t.Fatalf("failed to set in_progress 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())
}
// Verify status changed to we_cancelled
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)
}
}
// 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) {
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)
// Set to already cancelled
_, err = db.DB.Exec(ctx, "UPDATE bookings SET status = 'we_cancelled' WHERE id = $1", bookingID)
if err != nil {
t.Fatalf("failed to set we_cancelled status: %v", err)
}
// Try to cancel again
handler := http.HandlerFunc(bookings.AdminCancelBookingHandler)
w := makeAdminRequest(handler, "POST", "/api/admin/bookings/"+bookingID+"/cancel", nil)
if w.Code != http.StatusNotFound {
t.Errorf("expected status 404 for already-cancelled booking, got %d", w.Code)
}
}
// 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) {
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)
// Set to completed
_, err = db.DB.Exec(ctx, "UPDATE bookings SET status = 'completed' WHERE id = $1", bookingID)
if err != nil {
t.Fatalf("failed to set completed status: %v", err)
}
// Try to cancel
handler := http.HandlerFunc(bookings.AdminCancelBookingHandler)
w := makeAdminRequest(handler, "POST", "/api/admin/bookings/"+bookingID+"/cancel", nil)
if w.Code != http.StatusNotFound {
t.Errorf("expected status 404 for completed booking, got %d", w.Code)
}
}
// =============================================================================
// Non-Admin Tests
// =============================================================================