Deposit tracking
This commit is contained in:
@@ -2629,10 +2629,220 @@ func TestBookings_Create_NoDepositRequired_Within48Hours(t *testing.T) {
|
||||
t.Errorf("expected status 201 for booking within 48h with no deposit required, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
}
|
||||
// =============================================================================
|
||||
// Deposit Snapshot and Field Tests
|
||||
// =============================================================================
|
||||
|
||||
// TestBookings_Create_DepositSnapshot verifies that deposit_required is snapshotted
|
||||
// at booking creation time from user's current deposits_required value.
|
||||
func TestBookings_Create_DepositSnapshot(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
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=3 BEFORE creating booking
|
||||
_, err = db.DB.Exec(context.Background(), "UPDATE users SET deposits_required = 3 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)
|
||||
|
||||
token := jwt.GenerateUserToken(userID)
|
||||
|
||||
// Create booking after deposits_required is set
|
||||
after48h := time.Now().Add(72 * time.Hour).Truncate(time.Second)
|
||||
after48h = time.Date(after48h.Year(), after48h.Month(), after48h.Day(), 10, 0, 0, 0, after48h.Location())
|
||||
req := CreateBookingRequest{
|
||||
StartTime: after48h,
|
||||
ServiceIDs: []string{serviceID},
|
||||
}
|
||||
|
||||
handler := http.HandlerFunc(CreateBookingHandler)
|
||||
w := makeRequest(handler, "POST", "/api/bookings", req, token)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Fatalf("expected status 201, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
// Verify deposit_required was snapshotted on the booking
|
||||
var depositRequired bool
|
||||
err = db.DB.QueryRow(context.Background(),
|
||||
"SELECT deposit_required FROM bookings WHERE user_id = $1", userID).Scan(&depositRequired)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to query booking: %v", err)
|
||||
}
|
||||
if !depositRequired {
|
||||
t.Error("expected deposit_required=true to be snapshotted on booking")
|
||||
}
|
||||
|
||||
// Now change user's deposits_required to 0
|
||||
_, err = db.DB.Exec(context.Background(), "UPDATE users SET deposits_required = 0 WHERE id = $1", userID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to update deposits_required: %v", err)
|
||||
}
|
||||
|
||||
// Verify the booking's deposit_required is still true (snapshot is not updated)
|
||||
err = db.DB.QueryRow(context.Background(),
|
||||
"SELECT deposit_required FROM bookings WHERE user_id = $1", userID).Scan(&depositRequired)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to query booking: %v", err)
|
||||
}
|
||||
if !depositRequired {
|
||||
t.Error("expected deposit_required to remain true after user's deposits_required changed")
|
||||
}
|
||||
}
|
||||
|
||||
// TestBookings_Create_DepositRequired_OneActiveBookingLimit verifies that a user
|
||||
// with deposits_required > 0 can only have ONE active booking at a time.
|
||||
func TestBookings_Create_DepositRequired_OneActiveBookingLimit(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
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=3 (triggers one-active-booking limit)
|
||||
_, err = db.DB.Exec(context.Background(), "UPDATE users SET deposits_required = 3 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)
|
||||
|
||||
token := jwt.GenerateUserToken(userID)
|
||||
|
||||
// Create first booking (should succeed)
|
||||
after48h := time.Now().Add(72 * time.Hour).Truncate(time.Second)
|
||||
after48h = time.Date(after48h.Year(), after48h.Month(), after48h.Day(), 10, 0, 0, 0, after48h.Location())
|
||||
req1 := CreateBookingRequest{
|
||||
StartTime: after48h,
|
||||
ServiceIDs: []string{serviceID},
|
||||
}
|
||||
|
||||
handler := http.HandlerFunc(CreateBookingHandler)
|
||||
w := makeRequest(handler, "POST", "/api/bookings", req1, token)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Fatalf("expected first booking to succeed, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
// Try to create second booking (should fail - one active booking limit)
|
||||
after72h := time.Now().Add(96 * time.Hour).Truncate(time.Second)
|
||||
after72h = time.Date(after72h.Year(), after72h.Month(), after72h.Day(), 10, 0, 0, 0, after72h.Location())
|
||||
req2 := CreateBookingRequest{
|
||||
StartTime: after72h,
|
||||
ServiceIDs: []string{serviceID},
|
||||
}
|
||||
|
||||
w = makeRequest(handler, "POST", "/api/bookings", req2, token)
|
||||
|
||||
if w.Code != http.StatusConflict {
|
||||
t.Errorf("expected status 409 for second booking attempt, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
if !bytes.Contains(w.Body.Bytes(), []byte("active booking")) {
|
||||
t.Errorf("expected error about active booking, got: %s", w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// TestBookings_Get_DepositFieldsReturned verifies that GET /api/bookings returns
|
||||
// the deposit-related fields (deposit_required, deposit_amount, deposit_paid, deposit_deadline).
|
||||
func TestBookings_Get_DepositFieldsReturned(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
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=3 and create booking
|
||||
_, err = db.DB.Exec(context.Background(), "UPDATE users SET deposits_required = 3 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)
|
||||
|
||||
token := jwt.GenerateUserToken(userID)
|
||||
|
||||
// Create booking with deposit requirement
|
||||
after48h := time.Now().Add(72 * time.Hour).Truncate(time.Second)
|
||||
after48h = time.Date(after48h.Year(), after48h.Month(), after48h.Day(), 10, 0, 0, 0, after48h.Location())
|
||||
req := CreateBookingRequest{
|
||||
StartTime: after48h,
|
||||
ServiceIDs: []string{serviceID},
|
||||
}
|
||||
|
||||
handler := http.HandlerFunc(CreateBookingHandler)
|
||||
w := makeRequest(handler, "POST", "/api/bookings", req, token)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Fatalf("expected status 201, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
// GET the booking and verify deposit fields
|
||||
w = makeRequest(http.HandlerFunc(GetAllUserBookingsHandler), "GET", "/api/bookings", nil, token)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var resp BookingListResponse
|
||||
if err := parseResponseBody(w, &resp); err != nil {
|
||||
t.Fatalf("failed to parse response: %v", err)
|
||||
}
|
||||
|
||||
if len(resp.Bookings) != 1 {
|
||||
t.Fatalf("expected 1 booking, got %d", len(resp.Bookings))
|
||||
}
|
||||
|
||||
booking := resp.Bookings[0]
|
||||
|
||||
// Verify deposit fields exist
|
||||
// Verify deposit fields exist
|
||||
if !booking.DepositRequired {
|
||||
t.Error("expected DepositRequired to be true")
|
||||
}
|
||||
if booking.DepositAmount <= 0 {
|
||||
t.Error("expected DepositAmount to be positive")
|
||||
}
|
||||
// DepositPaid is a bool, check it's set (should be false for new booking)
|
||||
// Just verify the field exists by accessing it
|
||||
_ = booking.DepositPaid
|
||||
if booking.DepositDeadline == nil {
|
||||
t.Error("expected DepositDeadline to be set")
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Holiday/Closed Day Booking Tests
|
||||
// =============================================================================
|
||||
|
||||
// TestBookings_Edit_ClosedDay_UserBlocked verifies that a regular user cannot edit a booking
|
||||
// to fall on a closed day (exceptional hours marked as is_open=false).
|
||||
|
||||
Reference in New Issue
Block a user