Fix tests

This commit is contained in:
2026-03-02 19:57:44 +00:00
parent dd097c1022
commit 74b6f039c0
6 changed files with 78 additions and 41 deletions
+1 -1
View File
@@ -9,6 +9,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.0 github.com/aws/aws-sdk-go-v2/service/s3 v1.96.0
github.com/go-chi/jwtauth/v5 v5.3.3 github.com/go-chi/jwtauth/v5 v5.3.3
github.com/kovidgoyal/imaging v1.8.19 github.com/kovidgoyal/imaging v1.8.19
github.com/lib/pq v1.11.2
golang.org/x/text v0.34.0 golang.org/x/text v0.34.0
) )
@@ -33,7 +34,6 @@ require (
github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/kovidgoyal/go-parallel v1.1.1 // indirect github.com/kovidgoyal/go-parallel v1.1.1 // indirect
github.com/kovidgoyal/go-shm v1.0.0 // indirect github.com/kovidgoyal/go-shm v1.0.0 // indirect
github.com/lib/pq v1.11.2 // indirect
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
golang.org/x/image v0.36.0 // indirect golang.org/x/image v0.36.0 // indirect
+1 -1
View File
@@ -323,7 +323,7 @@ func TestRegister_InvalidInput_Under16(t *testing.T) {
handler := http.HandlerFunc(RegisterHandler) handler := http.HandlerFunc(RegisterHandler)
// Calculate a date that makes them under 16 // Calculate a date that makes them under 16
under16DOB := time.Now().AddDate(-15, NULL, 0).Format("2006-01-02") under16DOB := time.Now().AddDate(-15, 0, 0).Format("2006-01-02")
body := RegisterRequest{ body := RegisterRequest{
FirstName: "Young", FirstName: "Young",
+70 -22
View File
@@ -34,6 +34,7 @@ import (
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/jackc/pgx/v5/pgxpool" "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 // setupTestDB replaces the global db.DB with a test pool and returns a cleanup function
@@ -57,6 +58,39 @@ func setupTestDB(t *testing.T) func() {
} }
} }
// seedDefaultWorkingHours seeds default working hours for tests
func seedDefaultWorkingHours(t *testing.T) {
t.Helper()
// Seed 7 days of working hours (Monday=0 to Sunday=6)
// Use wide hours to avoid test failures due to business logic time checks
hours := []struct {
weekday int
startTime string
endTime string
isOpen bool
}{
{0, "08:00", "20:00", true}, // Monday
{1, "08:00", "20:00", true}, // Tuesday
{2, "08:00", "20:00", true}, // Wednesday
{3, "08:00", "20:00", true}, // Thursday
{4, "08:00", "20:00", true}, // Friday
{5, "08:00", "20:00", true}, // Saturday
{6, "08:00", "20:00", true}, // Sunday
}
for _, h := range hours {
_, err := db.DB.Exec(context.Background(), `
INSERT INTO working_hours (weekday, start_time, end_time, is_open)
VALUES ($1, $2, $3, $4)
ON CONFLICT (weekday) DO UPDATE SET start_time = $2, end_time = $3, is_open = $4
`, h.weekday, h.startTime, h.endTime, h.isOpen)
if err != nil {
t.Fatalf("failed to seed working hours: %v", err)
}
}
}
// helper function to make JSON request with JWT auth // helper function to make JSON request with JWT auth
// For authenticated requests, use makeAuthRequest which extracts user from JWT // For authenticated requests, use makeAuthRequest which extracts user from JWT
func makeRequest(handler http.Handler, method, path string, body interface{}, token string) *httptest.ResponseRecorder { func makeRequest(handler http.Handler, method, path string, body interface{}, token string) *httptest.ResponseRecorder {
@@ -209,6 +243,9 @@ func TestBookings_Create(t *testing.T) {
cleanup := setupTestDB(t) cleanup := setupTestDB(t)
defer cleanup() defer cleanup()
// Seed working hours for booking tests
seedDefaultWorkingHours(t)
// Create test user and service // Create test user and service
userID, err := fixtures.CreateTestUser(db.DB) userID, err := fixtures.CreateTestUser(db.DB)
if err != nil { if err != nil {
@@ -232,7 +269,9 @@ func TestBookings_Create(t *testing.T) {
token := jwt.GenerateUserToken(userID) token := jwt.GenerateUserToken(userID)
// Create booking request - use future time to avoid 48h deposit requirement // Create booking request - use future time to avoid 48h deposit requirement
// Use 10:00 to ensure service fits within working hours (08:00-20:00)
futureTime := time.Now().Add(72 * time.Hour).Truncate(time.Second) futureTime := time.Now().Add(72 * time.Hour).Truncate(time.Second)
futureTime = time.Date(futureTime.Year(), futureTime.Month(), futureTime.Day(), 10, 0, 0, 0, futureTime.Location())
req := CreateBookingRequest{ req := CreateBookingRequest{
StartTime: futureTime, StartTime: futureTime,
ServiceIDs: []string{serviceID}, ServiceIDs: []string{serviceID},
@@ -1248,6 +1287,9 @@ func TestBookings_Create_Within48HourDepositRequired(t *testing.T) {
cleanup := setupTestDB(t) cleanup := setupTestDB(t)
defer cleanup() defer cleanup()
// Seed working hours for booking tests
seedDefaultWorkingHours(t)
userID, err := fixtures.CreateTestUser(db.DB) userID, err := fixtures.CreateTestUser(db.DB)
if err != nil { if err != nil {
t.Fatalf("failed to create test user: %v", err) t.Fatalf("failed to create test user: %v", err)
@@ -1268,7 +1310,9 @@ func TestBookings_Create_Within48HourDepositRequired(t *testing.T) {
token := jwt.GenerateUserToken(userID) token := jwt.GenerateUserToken(userID)
// Use 10:00 to ensure service fits within working hours (08:00-20:00)
within48h := time.Now().Add(24 * time.Hour).Truncate(time.Second) within48h := time.Now().Add(24 * time.Hour).Truncate(time.Second)
within48h = time.Date(within48h.Year(), within48h.Month(), within48h.Day(), 10, 0, 0, 0, within48h.Location())
req := CreateBookingRequest{ req := CreateBookingRequest{
StartTime: within48h, StartTime: within48h,
ServiceIDs: []string{serviceID}, ServiceIDs: []string{serviceID},
@@ -1298,6 +1342,9 @@ func TestBookings_Create_MultipleServices(t *testing.T) {
cleanup := setupTestDB(t) cleanup := setupTestDB(t)
defer cleanup() defer cleanup()
// Seed working hours for booking tests
seedDefaultWorkingHours(t)
userID, err := fixtures.CreateTestUser(db.DB) userID, err := fixtures.CreateTestUser(db.DB)
if err != nil { if err != nil {
t.Fatalf("failed to create test user: %v", err) t.Fatalf("failed to create test user: %v", err)
@@ -1324,7 +1371,9 @@ func TestBookings_Create_MultipleServices(t *testing.T) {
token := jwt.GenerateUserToken(userID) token := jwt.GenerateUserToken(userID)
// Use 10:00 to ensure services fit within working hours (08:00-20:00)
futureTime := time.Now().Add(72 * time.Hour).Truncate(time.Second) futureTime := time.Now().Add(72 * time.Hour).Truncate(time.Second)
futureTime = time.Date(futureTime.Year(), futureTime.Month(), futureTime.Day(), 10, 0, 0, 0, futureTime.Location())
req := CreateBookingRequest{ req := CreateBookingRequest{
StartTime: futureTime, StartTime: futureTime,
ServiceIDs: []string{serviceID1, serviceID2}, ServiceIDs: []string{serviceID1, serviceID2},
@@ -1613,9 +1662,8 @@ func TestUserCancelBooking_PendingNoNotification(t *testing.T) {
// Transaction and Error Handling Tests // Transaction and Error Handling Tests
// ============================================================================= // =============================================================================
// TestUserCancelBooking_TransactionIntegrity tests that the cancellation // TestUserCancelBooking_TransactionIntegrity verifies that if any part of the
// transaction properly commits - verifying the booking status actually changes // cancellation transaction fails, the booking status is NOT changed (rollback behavior)
// after a successful cancellation request.
func TestUserCancelBooking_TransactionIntegrity(t *testing.T) { func TestUserCancelBooking_TransactionIntegrity(t *testing.T) {
cleanup := setupTestDB(t) cleanup := setupTestDB(t)
defer cleanup() defer cleanup()
@@ -1688,8 +1736,6 @@ func TestUserCancelBooking_TransactionIntegrity(t *testing.T) {
} }
} }
// TestCreateEditRequest tests that creating an edit request creates an admin notification
// TestCreateEditRequest verifies that a user can request an edit to their // TestCreateEditRequest verifies that a user can request an edit to their
// confirmed booking (e.g., change time). This creates a booking_edit_request record // confirmed booking (e.g., change time). This creates a booking_edit_request record
// and generates an admin notification for staff review. // and generates an admin notification for staff review.
@@ -1767,8 +1813,7 @@ func TestCreateEditRequest(t *testing.T) {
} }
// TestDeleteEditRequest tests that user deleting their edit request deletes the admin notification // TestDeleteEditRequest tests that user deleting their edit request deletes the admin notification
// TestDeleteEditRequest tests that an admin can delete/remove a pending func TestDeleteEditRequest(t *testing.T) {
// edit request from a booking without affecting the original booking data.
cleanup := setupTestDB(t) cleanup := setupTestDB(t)
defer cleanup() defer cleanup()
@@ -1859,9 +1904,7 @@ func TestCreateEditRequest(t *testing.T) {
} }
// TestAdminApproveEditRequest tests that admin approving acknowledges the notification (not deletes) // TestAdminApproveEditRequest tests that admin approving acknowledges the notification (not deletes)
// TestAdminApproveEditRequest verifies that an admin can approve a user's func TestAdminApproveEditRequest(t *testing.T) {
// edit request. This updates the booking's start time to the requested time and
// marks the edit request as handled.
cleanup := setupTestDB(t) cleanup := setupTestDB(t)
defer cleanup() defer cleanup()
@@ -1978,8 +2021,7 @@ func TestCreateEditRequest(t *testing.T) {
} }
// TestAdminRejectEditRequest tests that admin rejecting acknowledges the notification (not deletes) // TestAdminRejectEditRequest tests that admin rejecting acknowledges the notification (not deletes)
// TestAdminRejectEditRequest tests that an admin can reject an edit request. func TestAdminRejectEditRequest(t *testing.T) {
// The original booking remains unchanged and the edit request is deleted.
cleanup := setupTestDB(t) cleanup := setupTestDB(t)
defer cleanup() defer cleanup()
@@ -2090,11 +2132,8 @@ func TestCreateEditRequest(t *testing.T) {
} }
} }
// TestBookings_RequestEdit_BookingNotFound tests that requesting an edit for a non-existent booking returns 404 // TestBookings_RequestEdit_BookingNotFound tests that requesting an edit for a non-existent booking returns 404
// TestBookings_RequestEdit_BookingNotFound verifies that requesting an edit func TestBookings_RequestEdit_BookingNotFound(t *testing.T) {
// for a non-existent booking returns HTTP 404 Not Found.
cleanup := setupTestDB(t) cleanup := setupTestDB(t)
defer cleanup() defer cleanup()
@@ -2125,8 +2164,7 @@ func TestCreateEditRequest(t *testing.T) {
} }
// TestBookings_RequestEdit_AlreadyHasPending tests that a user cannot create a second edit request while one already exists // TestBookings_RequestEdit_AlreadyHasPending tests that a user cannot create a second edit request while one already exists
// TestBookings_RequestEdit_AlreadyHasPending tests that a user cannot create func TestBookings_RequestEdit_AlreadyHasPending(t *testing.T) {
// a new edit request if one is already pending for the same booking.
cleanup := setupTestDB(t) cleanup := setupTestDB(t)
defer cleanup() defer cleanup()
@@ -2182,20 +2220,19 @@ func TestCreateEditRequest(t *testing.T) {
token := jwt.GenerateUserToken(userID) token := jwt.GenerateUserToken(userID)
// Try to create another edit request via API // Try to create another edit request via API
// Note: The handler actually replaces (upserts) the existing request, not rejects it
handler := http.HandlerFunc(RequestEditHandler) handler := http.HandlerFunc(RequestEditHandler)
reqBody := map[string]interface{}{ reqBody := map[string]interface{}{
"notes": "Please change to a different day", "notes": "Please change to a different day",
} }
w := makeRequest(handler, "POST", "/api/bookings/"+bookingID+"/edit-request", reqBody, token) w := makeRequest(handler, "POST", "/api/bookings/"+bookingID+"/edit-request", reqBody, token)
// Expect HTTP 201 Created - handler replaces old edit request with new one // Expect HTTP 201 Created (handler replaces existing request)
if w.Code != http.StatusCreated { if w.Code != http.StatusCreated {
t.Errorf("expected status 201, got %d. body: %s", w.Code, w.Body.String()) t.Errorf("expected status 201, got %d. body: %s", w.Code, w.Body.String())
} }
// Verify only 1 edit request exists in DB (the old one was replaced with new one) // Verify only 1 edit request exists in DB (the old one was replaced)
// Verify only 1 edit request exists in DB (the original one)
var erCount int var erCount int
err = db.DB.QueryRow(context.Background(), err = db.DB.QueryRow(context.Background(),
"SELECT COUNT(*) FROM booking_edit_requests WHERE booking_id = $1", bookingID).Scan(&erCount) "SELECT COUNT(*) FROM booking_edit_requests WHERE booking_id = $1", bookingID).Scan(&erCount)
@@ -2205,4 +2242,15 @@ func TestCreateEditRequest(t *testing.T) {
if erCount != 1 { if erCount != 1 {
t.Errorf("expected 1 edit request, got %d", erCount) t.Errorf("expected 1 edit request, got %d", erCount)
} }
// Verify the notes were updated
var notes string
err = db.DB.QueryRow(context.Background(),
"SELECT notes FROM booking_edit_requests WHERE booking_id = $1", bookingID).Scan(&notes)
if err != nil {
t.Fatalf("failed to query edit request notes: %v", err)
}
if notes != "Please change to a different day" {
t.Errorf("expected notes 'Please change to a different day', got '%s'", notes)
}
} }
+3 -2
View File
@@ -1008,6 +1008,7 @@ func AdminListEditRequestsHandler(w http.ResponseWriter, r *http.Request) {
var origStartTime time.Time var origStartTime time.Time
var bookingStatus string var bookingStatus string
var userName string var userName string
var newServices []string
err := rows.Scan( err := rows.Scan(
&req.ID, &req.ID,
@@ -1071,6 +1072,7 @@ func AdminApproveEditRequestHandler(w http.ResponseWriter, r *http.Request) {
// Get the edit request // Get the edit request
var bookingID string var bookingID string
var newStartTime *time.Time
var newServices []string var newServices []string
var notes *string var notes *string
var hasOverrides bool var hasOverrides bool
@@ -1081,7 +1083,7 @@ func AdminApproveEditRequestHandler(w http.ResponseWriter, r *http.Request) {
`, requestID).Scan(&bookingID, &newStartTime, pq.Array(&newServices), &notes, &hasOverrides) `, requestID).Scan(&bookingID, &newStartTime, pq.Array(&newServices), &notes, &hasOverrides)
if err != nil { if err != nil {
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
http.Error(w, "Edit request not found or already processed", http.StatusNotFound) http.Error(w, "Edit request not found", http.StatusNotFound)
return return
} }
log.Printf("Failed to get edit request %s: %v", requestID, err) log.Printf("Failed to get edit request %s: %v", requestID, err)
@@ -1228,7 +1230,6 @@ func AdminApproveEditRequestHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Internal server error", http.StatusInternalServerError) http.Error(w, "Internal server error", http.StatusInternalServerError)
return return
} }
if err := tx.Commit(r.Context()); err != nil { if err := tx.Commit(r.Context()); err != nil {
log.Printf("Failed to commit: %v", err) log.Printf("Failed to commit: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError) http.Error(w, "Internal server error", http.StatusInternalServerError)
-15
View File
@@ -32,21 +32,6 @@ import (
"crussell/testutils/testdb" "crussell/testutils/testdb"
"github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/pgxpool"
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"crussell/db"
"crussell/mw"
"crussell/testutils/fixtures"
"crussell/testutils/jwt"
"crussell/testutils/testdb"
"github.com/jackc/pgx/v5/pgxpool"
) )
func setupTest(t *testing.T) (func(), *pgxpool.Pool) { func setupTest(t *testing.T) (func(), *pgxpool.Pool) {
+3
View File
@@ -263,6 +263,9 @@ if api_post "$BASE_URL/register" "{\"firstName\":\"Oscar\",\"lastName\":\"Hughes
# Promote Admin # Promote Admin
docker exec postgres psql -U myuser -d mydb -c "UPDATE users SET account_role = 'admin' WHERE email = '$ADMIN_EMAIL'" > /dev/null 2>&1 docker exec postgres psql -U myuser -d mydb -c "UPDATE users SET account_role = 'admin' WHERE email = '$ADMIN_EMAIL'" > /dev/null 2>&1
# Set deposits_required=0 for all users (so they can confirm bookings without deposit issues)
docker exec postgres psql -U myuser -d mydb -c "UPDATE users SET deposits_required = 0" > /dev/null 2>&1
echo "${C_GREEN}✅ Registered $success/$total Users${C_RESET}" echo "${C_GREEN}✅ Registered $success/$total Users${C_RESET}"
# 2. Login # 2. Login