test fixes

This commit is contained in:
2026-03-01 16:05:02 +00:00
parent e00740a8c9
commit 803aab7073
6 changed files with 1652 additions and 4 deletions
+509
View File
@@ -22,6 +22,7 @@ import (
"github.com/go-chi/chi/v5"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/lib/pq"
)
// setupTestDB replaces the global db.DB with a test pool and returns a cleanup function
@@ -1610,3 +1611,511 @@ func TestUserCancelBooking_TransactionIntegrity(t *testing.T) {
t.Error("booking status should have changed after cancellation (transaction should have committed)")
}
}
// TestCreateEditRequest tests that creating an edit request creates an admin notification
func TestCreateEditRequest(t *testing.T) {
cleanup := setupTestDB(t)
defer cleanup()
userID, err := fixtures.CreateTestUser(db.DB)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
defer fixtures.DeleteUser(db.DB, userID)
// Set deposits_required=0 to avoid 48h advance booking requirement
_, err = db.DB.Exec(context.Background(), "UPDATE users SET deposits_required = 0 WHERE id = $1", userID)
if err != nil {
t.Fatalf("failed to set deposits_required: %v", err)
}
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)
// Confirm the booking
_, err = db.DB.Exec(context.Background(),
"UPDATE bookings SET status = $1 WHERE id = $2", "confirmed", bookingID)
if err != nil {
t.Fatalf("failed to confirm booking: %v", err)
}
token := jwt.GenerateUserToken(userID)
// Create edit request
handler := http.HandlerFunc(RequestEditHandler)
reqBody := map[string]interface{}{
"notes": "Please change the time",
}
w := makeRequest(handler, "POST", "/api/bookings/"+bookingID+"/edit-request", reqBody, token)
if w.Code != http.StatusCreated && w.Code != http.StatusOK {
t.Errorf("expected status 200/201, got %d. body: %s", w.Code, w.Body.String())
}
// Verify edit request was created
var erCount int
err = db.DB.QueryRow(context.Background(),
"SELECT COUNT(*) FROM booking_edit_requests WHERE booking_id = $1", bookingID).Scan(&erCount)
if err != nil {
t.Fatalf("failed to query edit requests: %v", err)
}
if erCount != 1 {
t.Errorf("expected 1 edit request, got %d", erCount)
}
// Verify admin notification was created
var notifCount int
err = db.DB.QueryRow(context.Background(),
`SELECT COUNT(*) FROM admin_notifications
WHERE booking_id = $1 AND reason = 'edit_request' AND acknowledged_at IS NULL`,
bookingID).Scan(&notifCount)
if err != nil {
t.Fatalf("failed to query notifications: %v", err)
}
if notifCount != 1 {
t.Errorf("expected 1 unacknowledged admin notification, got %d", notifCount)
}
}
// TestDeleteEditRequest tests that user deleting their edit request deletes the admin notification
func TestDeleteEditRequest(t *testing.T) {
cleanup := setupTestDB(t)
defer cleanup()
userID, err := fixtures.CreateTestUser(db.DB)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
defer fixtures.DeleteUser(db.DB, userID)
// Set deposits_required=0
_, err = db.DB.Exec(context.Background(), "UPDATE users SET deposits_required = 0 WHERE id = $1", userID)
if err != nil {
t.Fatalf("failed to set deposits_required: %v", err)
}
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)
// Confirm the booking
_, err = db.DB.Exec(context.Background(),
"UPDATE bookings SET status = $1 WHERE id = $2", "confirmed", bookingID)
if err != nil {
t.Fatalf("failed to confirm booking: %v", err)
}
// Create edit request directly in DB (simulating user request)
var editRequestID string
err = db.DB.QueryRow(context.Background(),
`INSERT INTO booking_edit_requests (booking_id, requested_by, notes)
VALUES ($1, $2, 'Please change time')
RETURNING id`,
bookingID, userID).Scan(&editRequestID)
if err != nil {
t.Fatalf("failed to create edit request: %v", err)
}
// Create admin notification
_, err = db.DB.Exec(context.Background(),
`INSERT INTO admin_notifications (reason, booking_id, user_id)
VALUES ('edit_request', $1, $2)`,
bookingID, userID)
if err != nil {
t.Fatalf("failed to create admin notification: %v", err)
}
token := jwt.GenerateUserToken(userID)
// Delete edit request (user cancels their request)
handler := http.HandlerFunc(DeleteEditRequestHandler)
w := makeRequest(handler, "DELETE", "/api/bookings/"+bookingID+"/edit-request", nil, token)
if w.Code != http.StatusNoContent {
t.Errorf("expected status 204, got %d. body: %s", w.Code, w.Body.String())
}
// Verify edit request was deleted
var erCount int
err = db.DB.QueryRow(context.Background(),
"SELECT COUNT(*) FROM booking_edit_requests WHERE id = $1", editRequestID).Scan(&erCount)
if err != nil {
t.Fatalf("failed to query edit requests: %v", err)
}
if erCount != 0 {
t.Errorf("expected 0 edit requests after delete, got %d", erCount)
}
// Verify admin notification was DELETED (not acknowledged)
var notifCount int
err = db.DB.QueryRow(context.Background(),
`SELECT COUNT(*) FROM admin_notifications
WHERE booking_id = $1 AND reason = 'edit_request'`,
bookingID).Scan(&notifCount)
if err != nil {
t.Fatalf("failed to query notifications: %v", err)
}
if notifCount != 0 {
t.Errorf("expected 0 admin notifications after delete, got %d", notifCount)
}
}
// TestAdminApproveEditRequest tests that admin approving acknowledges the notification (not deletes)
func TestAdminApproveEditRequest(t *testing.T) {
cleanup := setupTestDB(t)
defer cleanup()
userID, err := fixtures.CreateTestUser(db.DB)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
defer fixtures.DeleteUser(db.DB, userID)
// Set deposits_required=0
_, err = db.DB.Exec(context.Background(), "UPDATE users SET deposits_required = 0 WHERE id = $1", userID)
if err != nil {
t.Fatalf("failed to set deposits_required: %v", err)
}
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)
// Confirm the booking
_, err = db.DB.Exec(context.Background(),
"UPDATE bookings SET status = $1 WHERE id = $2", "confirmed", bookingID)
if err != nil {
t.Fatalf("failed to confirm booking: %v", err)
}
// Create edit request directly in DB (need pq.Array for PostgreSQL array)
var editRequestID string
newTime := time.Now().Add(24 * time.Hour).Truncate(time.Minute)
var emptyServices []string
err = db.DB.QueryRow(context.Background(),
`INSERT INTO booking_edit_requests (booking_id, requested_by, new_start_time, new_services, notes)
VALUES ($1, $2, $3, $4, 'Please change time')
RETURNING id`,
bookingID, userID, newTime, pq.Array(&emptyServices)).Scan(&editRequestID)
if err != nil {
t.Fatalf("failed to create edit request: %v", err)
}
if err != nil {
t.Fatalf("failed to create edit request: %v", err)
}
// Create admin notification
_, err = db.DB.Exec(context.Background(),
`INSERT INTO admin_notifications (reason, booking_id, user_id)
VALUES ('edit_request', $1, $2)`,
bookingID, userID)
if err != nil {
t.Fatalf("failed to create admin notification: %v", err)
}
// Verify notification starts as unacknowledged
var ackTime *time.Time
err = db.DB.QueryRow(context.Background(),
`SELECT acknowledged_at FROM admin_notifications
WHERE booking_id = $1 AND reason = 'edit_request'`,
bookingID).Scan(&ackTime)
if err != nil {
t.Fatalf("failed to query notification: %v", err)
}
if ackTime != nil {
t.Fatalf("expected notification to be unacknowledged initially")
}
// Simulate admin approval
adminToken := jwt.GenerateAdminToken()
// Create request with chi context
req := httptest.NewRequest("POST", "/api/admin/bookings/"+bookingID+"/edit-requests/"+editRequestID+"/approve", nil)
req.Header.Set("Authorization", "Bearer "+adminToken)
rctx := chi.NewRouteContext()
rctx.URLParams.Add("id", bookingID)
rctx.URLParams.Add("request_id", editRequestID)
req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
w := httptest.NewRecorder()
AdminApproveEditRequestHandler(w, req)
if w.Code != http.StatusNoContent && w.Code != http.StatusOK {
t.Errorf("expected status 200/204, got %d. body: %s", w.Code, w.Body.String())
}
// Verify edit request was deleted (approved)
var erCount int
err = db.DB.QueryRow(context.Background(),
"SELECT COUNT(*) FROM booking_edit_requests WHERE id = $1", editRequestID).Scan(&erCount)
if err != nil {
t.Fatalf("failed to query edit requests: %v", err)
}
if erCount != 0 {
t.Errorf("expected 0 edit requests after approve, got %d", erCount)
}
// Verify admin notification was ACKNOWLEDGED (not deleted) - history preserved
var ackTimeAfter *time.Time
err = db.DB.QueryRow(context.Background(),
`SELECT acknowledged_at FROM admin_notifications
WHERE booking_id = $1 AND reason = 'edit_request'`,
bookingID).Scan(&ackTimeAfter)
if err != nil {
t.Fatalf("failed to query notification: %v", err)
}
if ackTimeAfter == nil {
t.Errorf("expected notification to be acknowledged after approve, but acknowledged_at is still NULL")
}
}
// TestAdminRejectEditRequest tests that admin rejecting acknowledges the notification (not deletes)
func TestAdminRejectEditRequest(t *testing.T) {
cleanup := setupTestDB(t)
defer cleanup()
userID, err := fixtures.CreateTestUser(db.DB)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
defer fixtures.DeleteUser(db.DB, userID)
// Set deposits_required=0
_, err = db.DB.Exec(context.Background(), "UPDATE users SET deposits_required = 0 WHERE id = $1", userID)
if err != nil {
t.Fatalf("failed to set deposits_required: %v", err)
}
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)
// Confirm the booking
_, err = db.DB.Exec(context.Background(),
"UPDATE bookings SET status = $1 WHERE id = $2", "confirmed", bookingID)
if err != nil {
t.Fatalf("failed to confirm booking: %v", err)
}
// Create edit request directly in DB
var editRequestID string
err = db.DB.QueryRow(context.Background(),
`INSERT INTO booking_edit_requests (booking_id, requested_by, notes)
VALUES ($1, $2, 'Please change time')
RETURNING id`,
bookingID, userID).Scan(&editRequestID)
if err != nil {
t.Fatalf("failed to create edit request: %v", err)
}
// Create admin notification
_, err = db.DB.Exec(context.Background(),
`INSERT INTO admin_notifications (reason, booking_id, user_id)
VALUES ('edit_request', $1, $2)`,
bookingID, userID)
if err != nil {
t.Fatalf("failed to create admin notification: %v", err)
}
// Verify notification starts as unacknowledged
var ackTime *time.Time
err = db.DB.QueryRow(context.Background(),
`SELECT acknowledged_at FROM admin_notifications
WHERE booking_id = $1 AND reason = 'edit_request'`,
bookingID).Scan(&ackTime)
if err != nil {
t.Fatalf("failed to query notification: %v", err)
}
if ackTime != nil {
t.Fatalf("expected notification to be unacknowledged initially")
}
// Simulate admin denial
adminToken := jwt.GenerateAdminToken()
// Create request with chi context
req := httptest.NewRequest("POST", "/api/admin/bookings/"+bookingID+"/edit-requests/"+editRequestID+"/deny", nil)
req.Header.Set("Authorization", "Bearer "+adminToken)
rctx := chi.NewRouteContext()
rctx.URLParams.Add("id", bookingID)
rctx.URLParams.Add("request_id", editRequestID)
req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
w := httptest.NewRecorder()
AdminRejectEditRequestHandler(w, req)
if w.Code != http.StatusNoContent && w.Code != http.StatusOK {
t.Errorf("expected status 200/204, got %d. body: %s", w.Code, w.Body.String())
}
// Verify edit request was deleted (rejected)
var erCount int
err = db.DB.QueryRow(context.Background(),
"SELECT COUNT(*) FROM booking_edit_requests WHERE id = $1", editRequestID).Scan(&erCount)
if err != nil {
t.Fatalf("failed to query edit requests: %v", err)
}
if erCount != 0 {
t.Errorf("expected 0 edit requests after reject, got %d", erCount)
}
// Verify admin notification was ACKNOWLEDGED (not deleted) - history preserved
var ackTimeAfter *time.Time
err = db.DB.QueryRow(context.Background(),
`SELECT acknowledged_at FROM admin_notifications
WHERE booking_id = $1 AND reason = 'edit_request'`,
bookingID).Scan(&ackTimeAfter)
if err != nil {
t.Fatalf("failed to query notification: %v", err)
}
if ackTimeAfter == nil {
t.Errorf("expected notification to be acknowledged after reject, but acknowledged_at is still NULL")
}
}
// TestBookings_RequestEdit_BookingNotFound tests that requesting an edit for a non-existent booking returns 404
func TestBookings_RequestEdit_BookingNotFound(t *testing.T) {
cleanup := setupTestDB(t)
defer cleanup()
// Create test user
userID, err := fixtures.CreateTestUser(db.DB)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
defer fixtures.DeleteUser(db.DB, userID)
// Set deposits_required=0
_, err = db.DB.Exec(context.Background(), "UPDATE users SET deposits_required = 0 WHERE id = $1", userID)
if err != nil {
t.Fatalf("failed to set deposits_required: %v", err)
}
token := jwt.GenerateUserToken(userID)
handler := http.HandlerFunc(RequestEditHandler)
reqBody := map[string]interface{}{
"notes": "Please change the time",
}
w := makeRequest(handler, "POST", "/api/bookings/nonexistent-booking-id/edit-request", reqBody, token)
if w.Code != http.StatusNotFound {
t.Errorf("expected status 404, got %d", w.Code)
}
}
// TestBookings_RequestEdit_AlreadyHasPending tests that a user cannot create a second edit request while one already exists
func TestBookings_RequestEdit_AlreadyHasPending(t *testing.T) {
cleanup := setupTestDB(t)
defer cleanup()
userID, err := fixtures.CreateTestUser(db.DB)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
defer fixtures.DeleteUser(db.DB, userID)
// Set deposits_required=0 to avoid 48h advance booking requirement
_, err = db.DB.Exec(context.Background(), "UPDATE users SET deposits_required = 0 WHERE id = $1", userID)
if err != nil {
t.Fatalf("failed to set deposits_required: %v", err)
}
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)
// Confirm the booking
_, err = db.DB.Exec(context.Background(),
"UPDATE bookings SET status = $1 WHERE id = $2", "confirmed", bookingID)
if err != nil {
t.Fatalf("failed to confirm booking: %v", err)
}
// Create a pending edit request directly in DB (pre-condition)
_, err = db.DB.Exec(context.Background(),
`INSERT INTO booking_edit_requests (booking_id, requested_by, notes)
VALUES ($1, $2, 'Please change the time')`,
bookingID, userID)
if err != nil {
t.Fatalf("failed to create initial edit request: %v", err)
}
// Create admin notification for the initial edit request
_, err = db.DB.Exec(context.Background(),
`INSERT INTO admin_notifications (reason, booking_id, user_id)
VALUES ('edit_request', $1, $2)`,
bookingID, userID)
if err != nil {
t.Fatalf("failed to create admin notification: %v", err)
}
token := jwt.GenerateUserToken(userID)
// Try to create another edit request via API
handler := http.HandlerFunc(RequestEditHandler)
reqBody := map[string]interface{}{
"notes": "Please change to a different day",
}
w := makeRequest(handler, "POST", "/api/bookings/"+bookingID+"/edit-request", reqBody, token)
// Expect HTTP 400 Bad Request or 409 Conflict
if w.Code != http.StatusBadRequest && w.Code != http.StatusConflict {
t.Errorf("expected status 400 or 409, got %d. body: %s", w.Code, w.Body.String())
}
// Verify only 1 edit request exists in DB (the original one)
var erCount int
err = db.DB.QueryRow(context.Background(),
"SELECT COUNT(*) FROM booking_edit_requests WHERE booking_id = $1", bookingID).Scan(&erCount)
if err != nil {
t.Fatalf("failed to query edit requests: %v", err)
}
if erCount != 1 {
t.Errorf("expected 1 edit request, got %d", erCount)
}
}