feat: GetBookingsByCreatedRange endpoint for admin booking queries
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -2855,3 +2855,269 @@ func TestAdminCreateBookingForUser_ClosedExceptionalHours_Rejected(t *testing.T)
|
||||
t.Errorf("expected error message to mention 'holiday hours', got: %s", w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// GetBookingsByCreatedRange Tests
|
||||
// =============================================================================
|
||||
|
||||
// TestGetBookingsByCreatedRange verifies that the endpoint returns bookings
|
||||
// created within the specified created_at range.
|
||||
func TestGetBookingsByCreatedRange(t *testing.T) {
|
||||
resetTestData(t)
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
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 bookings with specific created_at timestamps
|
||||
_, err = db.DB.Exec(context.Background(), `
|
||||
INSERT INTO bookings (id, user_id, start_time, status, created_at)
|
||||
VALUES ('book00000001', $1, '2099-12-31 10:00:00+00', 'confirmed', '2025-01-15 09:00:00+00')
|
||||
`, userID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create booking 1: %v", err)
|
||||
}
|
||||
defer fixtures.DeleteBooking(db.DB, "book00000001")
|
||||
_, err = db.DB.Exec(context.Background(), `
|
||||
INSERT INTO booking_services (booking_id, service_id) VALUES ('book00000001', $1)
|
||||
`, serviceID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to link service to booking 1: %v", err)
|
||||
}
|
||||
|
||||
_, err = db.DB.Exec(context.Background(), `
|
||||
INSERT INTO bookings (id, user_id, start_time, status, created_at)
|
||||
VALUES ('book00000002', $1, '2099-12-31 11:00:00+00', 'pending', '2025-01-15 14:00:00+00')
|
||||
`, userID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create booking 2: %v", err)
|
||||
}
|
||||
defer fixtures.DeleteBooking(db.DB, "book00000002")
|
||||
_, err = db.DB.Exec(context.Background(), `
|
||||
INSERT INTO booking_services (booking_id, service_id) VALUES ('book00000002', $1)
|
||||
`, serviceID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to link service to booking 2: %v", err)
|
||||
}
|
||||
|
||||
// Booking outside the range (created before)
|
||||
_, err = db.DB.Exec(context.Background(), `
|
||||
INSERT INTO bookings (id, user_id, start_time, status, created_at)
|
||||
VALUES ('book00000003', $1, '2099-12-31 12:00:00+00', 'confirmed', '2025-01-10 09:00:00+00')
|
||||
`, userID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create booking 3: %v", err)
|
||||
}
|
||||
defer fixtures.DeleteBooking(db.DB, "book00000003")
|
||||
_, err = db.DB.Exec(context.Background(), `
|
||||
INSERT INTO booking_services (booking_id, service_id) VALUES ('book00000003', $1)
|
||||
`, serviceID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to link service to booking 3: %v", err)
|
||||
}
|
||||
|
||||
handler := http.HandlerFunc(bookings.GetBookingsByCreatedRangeHandler)
|
||||
w := makeAdminRequest(handler, "GET", "/api/admin/bookings/by-created-range?start=2025-01-15T00:00:00Z&end=2025-01-16T00:00:00Z", nil)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var resp bookings.OverlappingBookingsResponse
|
||||
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 in range, got %d", len(resp.Bookings))
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
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.GetBookingsByCreatedRangeHandler)
|
||||
w := makeAdminRequest(handler, "GET", "/api/admin/bookings/by-created-range?start=2025-01-01T00:00:00Z&end=2025-01-02T00:00:00Z", nil)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var resp bookings.OverlappingBookingsResponse
|
||||
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, got %d", len(resp.Bookings))
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetBookingsByCreatedRange_MissingParams verifies that the endpoint
|
||||
// returns 400 when start or end query parameters are missing.
|
||||
func TestGetBookingsByCreatedRange_MissingParams(t *testing.T) {
|
||||
resetTestData(t)
|
||||
|
||||
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.GetBookingsByCreatedRangeHandler)
|
||||
|
||||
// Missing both params
|
||||
w := makeAdminRequest(handler, "GET", "/api/admin/bookings/by-created-range", nil)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected status 400 (missing both), got %d", w.Code)
|
||||
}
|
||||
|
||||
// Missing end param
|
||||
w = makeAdminRequest(handler, "GET", "/api/admin/bookings/by-created-range?start=2025-01-01T00:00:00Z", nil)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected status 400 (missing end), got %d", w.Code)
|
||||
}
|
||||
|
||||
// Missing start param
|
||||
w = makeAdminRequest(handler, "GET", "/api/admin/bookings/by-created-range?end=2025-01-02T00:00:00Z", nil)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected status 400 (missing start), got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetBookingsByCreatedRange_InvalidFormat verifies that the endpoint
|
||||
// returns 400 when the date format is invalid.
|
||||
func TestGetBookingsByCreatedRange_InvalidFormat(t *testing.T) {
|
||||
resetTestData(t)
|
||||
|
||||
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.GetBookingsByCreatedRangeHandler)
|
||||
w := makeAdminRequest(handler, "GET", "/api/admin/bookings/by-created-range?start=not-a-date&end=2025-01-02T00:00:00Z", nil)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected status 400, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetBookingsByCreatedRange_OrderedByCreatedAt verifies that results
|
||||
// are returned in ascending order by created_at.
|
||||
func TestGetBookingsByCreatedRange_OrderedByCreatedAt(t *testing.T) {
|
||||
resetTestData(t)
|
||||
seedDefaultWorkingHours(t)
|
||||
|
||||
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 bookings with created_at in reverse order
|
||||
_, err = db.DB.Exec(context.Background(), `
|
||||
INSERT INTO bookings (id, user_id, start_time, status, created_at)
|
||||
VALUES ('book00000010', $1, '2099-12-31 10:00:00+00', 'confirmed', '2025-03-01 15:00:00+00')
|
||||
`, userID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create booking 10: %v", err)
|
||||
}
|
||||
defer fixtures.DeleteBooking(db.DB, "book00000010")
|
||||
_, err = db.DB.Exec(context.Background(), `
|
||||
INSERT INTO booking_services (booking_id, service_id) VALUES ('book00000010', $1)
|
||||
`, serviceID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to link service to booking 10: %v", err)
|
||||
}
|
||||
|
||||
_, err = db.DB.Exec(context.Background(), `
|
||||
INSERT INTO bookings (id, user_id, start_time, status, created_at)
|
||||
VALUES ('book00000011', $1, '2099-12-31 11:00:00+00', 'pending', '2025-03-01 10:00:00+00')
|
||||
`, userID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create booking 11: %v", err)
|
||||
}
|
||||
defer fixtures.DeleteBooking(db.DB, "book00000011")
|
||||
_, err = db.DB.Exec(context.Background(), `
|
||||
INSERT INTO booking_services (booking_id, service_id) VALUES ('book00000011', $1)
|
||||
`, serviceID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to link service to booking 11: %v", err)
|
||||
}
|
||||
|
||||
_, err = db.DB.Exec(context.Background(), `
|
||||
INSERT INTO bookings (id, user_id, start_time, status, created_at)
|
||||
VALUES ('book00000012', $1, '2099-12-31 12:00:00+00', 'confirmed', '2025-03-01 12:00:00+00')
|
||||
`, userID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create booking 12: %v", err)
|
||||
}
|
||||
defer fixtures.DeleteBooking(db.DB, "book00000012")
|
||||
_, err = db.DB.Exec(context.Background(), `
|
||||
INSERT INTO booking_services (booking_id, service_id) VALUES ('book00000012', $1)
|
||||
`, serviceID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to link service to booking 12: %v", err)
|
||||
}
|
||||
|
||||
handler := http.HandlerFunc(bookings.GetBookingsByCreatedRangeHandler)
|
||||
w := makeAdminRequest(handler, "GET", "/api/admin/bookings/by-created-range?start=2025-03-01T00:00:00Z&end=2025-03-02T00:00:00Z", nil)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var resp bookings.OverlappingBookingsResponse
|
||||
if err := parseResponseBody(w, &resp); err != nil {
|
||||
t.Fatalf("failed to parse response: %v", err)
|
||||
}
|
||||
|
||||
if len(resp.Bookings) != 3 {
|
||||
t.Fatalf("expected 3 bookings, got %d", len(resp.Bookings))
|
||||
}
|
||||
|
||||
// Verify order: book00000011 (10:00) < book00000012 (12:00) < book00000010 (15:00)
|
||||
expectedOrder := []string{"book00000011", "book00000012", "book00000010"}
|
||||
for i, expected := range expectedOrder {
|
||||
if resp.Bookings[i].ID != expected {
|
||||
t.Errorf("booking[%d] expected %s, got %s", i, expected, resp.Bookings[i].ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user