refactor(backend): update test files for PoolProxy and per-test transactions

Migrate all test files from SetupTestDB/db.DB pattern to per-test transactions:

- Replace SetupTestDB(t) with SetupTestTx(t) for context + transaction
- Replace db.DB.Query/QueryRow/Exec with tx.Query/QueryRow/Exec
- Replace context.Background() with context from SetupTestTx
- Replace defer rows.Close() pattern with explicit rows.Close()
- Add testdb.SeedBaseline(pool) to all TestMain functions
- Wire db.Conn = db.NewPoolProxy(pool) in all TestMain functions

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-06-21 19:29:24 +01:00
co-authored by Sisyphus
parent 3d0e2afc4c
commit 220a0ef6e8
57 changed files with 5911 additions and 6235 deletions
+71 -61
View File
@@ -19,7 +19,6 @@ import (
"testing"
"time"
"crussell/db"
"crussell/testutils"
"crussell/mw"
"crussell/testutils/fixtures"
@@ -29,7 +28,7 @@ import (
// makeAdminReserveRequest creates a request with admin context for admin reserve slot handler
// It sets mw.UserIDKey and mw.UserRoleKey to "admin" in the context
func makeAdminReserveRequest(handler http.Handler, body interface{}, adminID string) *httptest.ResponseRecorder {
func makeAdminReserveRequest(handler http.Handler, body interface{}, adminID string, requestCtx ...context.Context) *httptest.ResponseRecorder {
var req *http.Request
if body != nil {
bodyBytes, _ := json.Marshal(body)
@@ -39,8 +38,13 @@ func makeAdminReserveRequest(handler http.Handler, body interface{}, adminID str
req = httptest.NewRequest("POST", "/api/admin/bookings/reserve", nil)
}
baseCtx := req.Context()
if len(requestCtx) > 0 {
baseCtx = requestCtx[0]
}
rctx := chi.NewRouteContext()
ctx := context.WithValue(req.Context(), chi.RouteCtxKey, rctx)
ctx := context.WithValue(baseCtx, chi.RouteCtxKey, rctx)
ctx = context.WithValue(ctx, mw.UserIDKey, adminID)
ctx = context.WithValue(ctx, mw.UserRoleKey, "admin")
@@ -60,15 +64,15 @@ func makeAdminReserveRequest(handler http.Handler, body interface{}, adminID str
// create a walk-in reservation with a valid duration. The test verifies
// the reservation is created in the database with the correct duration.
func TestAdminReserveSlot_WalkIn_Success(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
seedDefaultWorkingHours(t)
adminID, err := fixtures.CreateTestAdminUser(db.DB)
adminID, err := fixtures.CreateTestAdminUser(tx)
if err != nil {
t.Fatalf("failed to create admin: %v", err)
}
defer fixtures.DeleteUser(db.DB, adminID)
defer fixtures.DeleteUser(tx, adminID)
tomorrow := time.Now().Add(24 * time.Hour)
now := time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), 12, 0, 0, 0, tomorrow.Location())
@@ -80,7 +84,7 @@ func TestAdminReserveSlot_WalkIn_Success(t *testing.T) {
}
handler := http.HandlerFunc(AdminReserveSlotHandler)
w := makeAdminReserveRequest(handler, req, adminID)
w := makeAdminReserveRequest(handler, req, adminID, ctx)
if w.Code != http.StatusCreated {
t.Errorf("expected status 201, got %d. body: %s", w.Code, w.Body.String())
@@ -98,7 +102,7 @@ func TestAdminReserveSlot_WalkIn_Success(t *testing.T) {
// Verify time_blocker was created with correct description pattern
var desc string
err = db.DB.QueryRow(context.Background(),
err = tx.QueryRow(ctx,
"SELECT description FROM time_blockers WHERE description LIKE 'RESERVATION:admin:walkin:%'",
).Scan(&desc)
if err != nil {
@@ -118,29 +122,29 @@ func TestAdminReserveSlot_WalkIn_Success(t *testing.T) {
// create a call-in reservation with valid service IDs. The test verifies
// the reservation duration matches the service duration.
func TestAdminReserveSlot_CallIn_Success(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
seedDefaultWorkingHours(t)
adminID, err := fixtures.CreateTestAdminUser(db.DB)
adminID, err := fixtures.CreateTestAdminUser(tx)
if err != nil {
t.Fatalf("failed to create admin: %v", err)
}
defer fixtures.DeleteUser(db.DB, adminID)
defer fixtures.DeleteUser(tx, adminID)
userID, err := fixtures.CreateTestUser(db.DB)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
defer fixtures.DeleteUser(db.DB, userID)
defer fixtures.DeleteUser(tx, userID)
serviceID, err := fixtures.CreateTestService(db.DB)
serviceID, err := fixtures.CreateTestService(tx)
if err != nil {
t.Fatalf("failed to create test service: %v", err)
}
defer fixtures.DeleteService(db.DB, serviceID)
defer fixtures.DeleteService(tx, serviceID)
_, err = db.DB.Exec(context.Background(), "UPDATE services SET duration_minutes = 30 WHERE id = $1", serviceID)
_, err = tx.Exec(ctx, "UPDATE services SET duration_minutes = 30 WHERE id = $1", serviceID)
if err != nil {
t.Fatalf("failed to update service duration: %v", err)
}
@@ -157,7 +161,7 @@ func TestAdminReserveSlot_CallIn_Success(t *testing.T) {
}
handler := http.HandlerFunc(AdminReserveSlotHandler)
w := makeAdminReserveRequest(handler, req, adminID)
w := makeAdminReserveRequest(handler, req, adminID, ctx)
if w.Code != http.StatusCreated {
t.Errorf("expected status 201, got %d. body: %s", w.Code, w.Body.String())
@@ -175,7 +179,7 @@ func TestAdminReserveSlot_CallIn_Success(t *testing.T) {
// Verify time_blocker was created with correct description pattern
var desc string
err = db.DB.QueryRow(context.Background(),
err = tx.QueryRow(ctx,
"SELECT description FROM time_blockers WHERE description LIKE 'RESERVATION:admin:callin:%'",
).Scan(&desc)
if err != nil {
@@ -194,15 +198,16 @@ func TestAdminReserveSlot_CallIn_Success(t *testing.T) {
// TestAdminReserveSlot_WalkIn_MissingDuration tests that walk-in reservations
// fail with HTTP 400 when duration_minutes is missing or zero.
func TestAdminReserveSlot_WalkIn_MissingDuration(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
seedDefaultWorkingHours(t)
adminID, err := fixtures.CreateTestAdminUser(db.DB)
adminID, err := fixtures.CreateTestAdminUser(tx)
if err != nil {
t.Fatalf("failed to create admin: %v", err)
}
defer fixtures.DeleteUser(db.DB, adminID)
defer fixtures.DeleteUser(tx, adminID)
now := time.Now()
req := AdminReserveSlotRequest{
@@ -212,7 +217,7 @@ func TestAdminReserveSlot_WalkIn_MissingDuration(t *testing.T) {
}
handler := http.HandlerFunc(AdminReserveSlotHandler)
w := makeAdminReserveRequest(handler, req, adminID)
w := makeAdminReserveRequest(handler, req, adminID, ctx)
if w.Code != http.StatusBadRequest {
t.Errorf("expected status 400, got %d. body: %s", w.Code, w.Body.String())
@@ -232,15 +237,16 @@ func TestAdminReserveSlot_WalkIn_MissingDuration(t *testing.T) {
// TestAdminReserveSlot_CallIn_MissingServices tests that call-in reservations
// fail with HTTP 400 when service_ids is empty.
func TestAdminReserveSlot_CallIn_MissingServices(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
seedDefaultWorkingHours(t)
adminID, err := fixtures.CreateTestAdminUser(db.DB)
adminID, err := fixtures.CreateTestAdminUser(tx)
if err != nil {
t.Fatalf("failed to create admin: %v", err)
}
defer fixtures.DeleteUser(db.DB, adminID)
defer fixtures.DeleteUser(tx, adminID)
tomorrow := time.Now().Add(24 * time.Hour).Truncate(time.Second)
tomorrow = time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), 10, 0, 0, 0, tomorrow.Location())
@@ -253,7 +259,7 @@ func TestAdminReserveSlot_CallIn_MissingServices(t *testing.T) {
}
handler := http.HandlerFunc(AdminReserveSlotHandler)
w := makeAdminReserveRequest(handler, req, adminID)
w := makeAdminReserveRequest(handler, req, adminID, ctx)
if w.Code != http.StatusBadRequest {
t.Errorf("expected status 400, got %d. body: %s", w.Code, w.Body.String())
@@ -273,15 +279,16 @@ func TestAdminReserveSlot_CallIn_MissingServices(t *testing.T) {
// TestAdminReserveSlot_InvalidReservationType tests that reservations
// fail with HTTP 400 when reservation_type is invalid.
func TestAdminReserveSlot_InvalidReservationType(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
seedDefaultWorkingHours(t)
adminID, err := fixtures.CreateTestAdminUser(db.DB)
adminID, err := fixtures.CreateTestAdminUser(tx)
if err != nil {
t.Fatalf("failed to create admin: %v", err)
}
defer fixtures.DeleteUser(db.DB, adminID)
defer fixtures.DeleteUser(tx, adminID)
req := AdminReserveSlotRequest{
ReservationType: "invalid",
@@ -290,7 +297,7 @@ func TestAdminReserveSlot_InvalidReservationType(t *testing.T) {
}
handler := http.HandlerFunc(AdminReserveSlotHandler)
w := makeAdminReserveRequest(handler, req, adminID)
w := makeAdminReserveRequest(handler, req, adminID, ctx)
if w.Code != http.StatusBadRequest {
t.Errorf("expected status 400, got %d. body: %s", w.Code, w.Body.String())
@@ -310,32 +317,33 @@ func TestAdminReserveSlot_InvalidReservationType(t *testing.T) {
// TestAdminReserveSlot_SlotOverlap tests that a reservation fails
// with HTTP 409 when the slot overlaps with an existing booking.
func TestAdminReserveSlot_SlotOverlap(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
seedDefaultWorkingHours(t)
// Create test admin user (for the booking)
adminID, err := fixtures.CreateTestAdminUser(db.DB)
adminID, err := fixtures.CreateTestAdminUser(tx)
if err != nil {
t.Fatalf("failed to create admin: %v", err)
}
defer fixtures.DeleteUser(db.DB, adminID)
defer fixtures.DeleteUser(tx, adminID)
// Create test regular user
userID, err := fixtures.CreateTestUser(db.DB)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
defer fixtures.DeleteUser(db.DB, userID)
defer fixtures.DeleteUser(tx, userID)
serviceID, err := fixtures.CreateTestService(db.DB)
serviceID, err := fixtures.CreateTestService(tx)
if err != nil {
t.Fatalf("failed to create test service: %v", err)
}
defer fixtures.DeleteService(db.DB, serviceID)
defer fixtures.DeleteService(tx, serviceID)
// Set deposits_required=0 for test user
_, err = db.DB.Exec(context.Background(), "UPDATE users SET deposits_required = 0 WHERE id = $1", userID)
_, err = tx.Exec(ctx, "UPDATE users SET deposits_required = 0 WHERE id = $1", userID)
if err != nil {
t.Fatalf("failed to set deposits_required: %v", err)
}
@@ -343,13 +351,13 @@ func TestAdminReserveSlot_SlotOverlap(t *testing.T) {
tomorrow := time.Now().Add(24 * time.Hour).Truncate(time.Second)
tomorrow = time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), 10, 0, 0, 0, tomorrow.Location())
bookingID, err := fixtures.CreateTestBooking(db.DB, userID, serviceID)
bookingID, err := fixtures.CreateTestBooking(tx, userID, serviceID)
if err != nil {
t.Fatalf("failed to create test booking: %v", err)
}
defer fixtures.DeleteBooking(db.DB, bookingID)
defer fixtures.DeleteBooking(tx, bookingID)
_, err = db.DB.Exec(context.Background(),
_, err = tx.Exec(ctx,
"UPDATE bookings SET start_time = $1, status = 'confirmed' WHERE id = $2",
tomorrow, bookingID)
if err != nil {
@@ -367,7 +375,7 @@ func TestAdminReserveSlot_SlotOverlap(t *testing.T) {
}
handler := http.HandlerFunc(AdminReserveSlotHandler)
w := makeAdminReserveRequest(handler, req, adminID)
w := makeAdminReserveRequest(handler, req, adminID, ctx)
// Should return 409 Conflict due to overlap
if w.Code != http.StatusConflict {
@@ -382,15 +390,16 @@ func TestAdminReserveSlot_SlotOverlap(t *testing.T) {
// TestAdminReserveSlot_ReplacesExisting tests that reserving twice
// on the same admin replaces the previous reservation.
func TestAdminReserveSlot_ReplacesExisting(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
seedDefaultWorkingHours(t)
adminID, err := fixtures.CreateTestAdminUser(db.DB)
adminID, err := fixtures.CreateTestAdminUser(tx)
if err != nil {
t.Fatalf("failed to create admin: %v", err)
}
defer fixtures.DeleteUser(db.DB, adminID)
defer fixtures.DeleteUser(tx, adminID)
tomorrow := time.Now().Add(24 * time.Hour)
now := time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), 12, 0, 0, 0, tomorrow.Location())
@@ -404,7 +413,7 @@ func TestAdminReserveSlot_ReplacesExisting(t *testing.T) {
// First reservation
handler := http.HandlerFunc(AdminReserveSlotHandler)
w := makeAdminReserveRequest(handler, req, adminID)
w := makeAdminReserveRequest(handler, req, adminID, ctx)
if w.Code != http.StatusCreated {
t.Errorf("expected status 201, got %d. body: %s", w.Code, w.Body.String())
@@ -418,7 +427,7 @@ func TestAdminReserveSlot_ReplacesExisting(t *testing.T) {
// Count reservations before second request
var countBefore int
var qerr error
qerr = db.DB.QueryRow(context.Background(),
qerr = tx.QueryRow(ctx,
"SELECT COUNT(*) FROM time_blockers WHERE description LIKE 'RESERVATION:admin:%'",
).Scan(&countBefore)
if qerr != nil {
@@ -434,7 +443,7 @@ func TestAdminReserveSlot_ReplacesExisting(t *testing.T) {
TTLMinutes: 15,
}
w = makeAdminReserveRequest(handler, req2, adminID)
w = makeAdminReserveRequest(handler, req2, adminID, ctx)
if w.Code != http.StatusCreated {
t.Errorf("expected status 201, got %d. body: %s", w.Code, w.Body.String())
@@ -447,7 +456,7 @@ func TestAdminReserveSlot_ReplacesExisting(t *testing.T) {
// Count reservations after second request - should still be 1
var countAfter int
qerr = db.DB.QueryRow(context.Background(),
qerr = tx.QueryRow(ctx,
"SELECT COUNT(*) FROM time_blockers WHERE description LIKE 'RESERVATION:admin:%'",
).Scan(&countAfter)
if qerr != nil {
@@ -470,15 +479,16 @@ func TestAdminReserveSlot_ReplacesExisting(t *testing.T) {
// TestAdminReserveSlot_WalkIn_PastStart tests that walk-in reservations
func TestAdminReserveSlot_WalkIn_PastStart(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
seedDefaultWorkingHours(t)
adminID, err := fixtures.CreateTestAdminUser(db.DB)
adminID, err := fixtures.CreateTestAdminUser(tx)
if err != nil {
t.Fatalf("failed to create admin: %v", err)
}
defer fixtures.DeleteUser(db.DB, adminID)
defer fixtures.DeleteUser(tx, adminID)
pastTime := time.Now().Add(-5 * time.Minute)
req := AdminReserveSlotRequest{
@@ -489,7 +499,7 @@ func TestAdminReserveSlot_WalkIn_PastStart(t *testing.T) {
}
handler := http.HandlerFunc(AdminReserveSlotHandler)
w := makeAdminReserveRequest(handler, req, adminID)
w := makeAdminReserveRequest(handler, req, adminID, ctx)
if w.Code != http.StatusBadRequest {
t.Errorf("expected status 400, got %d. body: %s", w.Code, w.Body.String())