- Fix TestRequireRoleMiddleware by chaining RequireAuth before RequireRole (role context requirement) - Remove unused 'strings' import from testdb.go - Create crussell_test database in Docker setup - Tests now properly initialize authentication context for role-based tests Result: handlers test suite passes (13/13 tests) Remaining failures in admin/auth/bookings/portfolio/scheduling/services/user packages need further investigation (environment setup, database constraints, endpoint initialization)
1089 lines
32 KiB
Go
1089 lines
32 KiB
Go
//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)
|
|
}
|
|
}
|