refactor(backend): migrate db.DB to db.Conn PoolProxy across all handlers

Replace direct *pgxpool.Pool usage with PoolProxy wrapper across the entire backend:

- db.DB renamed to db.Conn (*pgxpool.Pool -> *PoolProxy)
- JWT functions now accept context.Context instead of using context.Background()
- Handler DB calls route through PoolProxy for per-test transaction support
- Fixture/helper/testdb functions accept Querier interface for decoupling
- Query ordering fixed in bookings handlers: COUNT after data query to avoid pgx conn busy
- Time truncation fixed: time.Date instead of Truncate(24*time.Hour) for week start calc
- testmain_test.go files updated with SeedBaseline and NewPoolProxy

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:28:54 +01:00
co-authored by Sisyphus
parent c69a243f75
commit 3d0e2afc4c
39 changed files with 751 additions and 638 deletions
+65 -64
View File
@@ -6,28 +6,29 @@ package fixtures
import (
"context"
"fmt"
"sync/atomic"
"time"
"github.com/jackc/pgx/v5/pgxpool"
"crussell/db"
"golang.org/x/crypto/bcrypt"
)
// Global counter for unique emails in tests
var testEmailCounter int64
var testEmailCounter atomic.Int64
func CreateTestAdminUser(pool *pgxpool.Pool) (string, error) {
return createTestUser(pool, "Admin", "User", "", "admin")
func CreateTestAdminUser(q db.Querier) (string, error) {
return createTestUser(q, "Admin", "User", "", "admin")
}
func CreateTestUser(pool *pgxpool.Pool) (string, error) {
return createTestUser(pool, "Test", "User", "", "verified_email")
func CreateTestUser(q db.Querier) (string, error) {
return createTestUser(q, "Test", "User", "", "verified_email")
}
func CreateTestUserWithEmail(pool *pgxpool.Pool, email, role string) (string, error) {
return createTestUser(pool, "Test", "User", email, role)
func CreateTestUserWithEmail(q db.Querier, email, role string) (string, error) {
return createTestUser(q, "Test", "User", email, role)
}
func createTestUser(pool *pgxpool.Pool, firstName, lastName, email, role string) (string, error) {
func createTestUser(q db.Querier, firstName, lastName, email, role string) (string, error) {
passwordHash, err := bcrypt.GenerateFromPassword([]byte("testpassword123"), bcrypt.DefaultCost)
if err != nil {
return "", fmt.Errorf("failed to hash password: %w", err)
@@ -35,13 +36,13 @@ func createTestUser(pool *pgxpool.Pool, firstName, lastName, email, role string)
// Generate unique email if not provided
if email == "" {
testEmailCounter++
email = fmt.Sprintf("%s.%s.%d@test.com", firstName, lastName, testEmailCounter)
n := testEmailCounter.Add(1)
email = fmt.Sprintf("%s.%s.%d@test.com", firstName, lastName, n)
}
ctx := context.Background()
var userID string
err = pool.QueryRow(ctx, `
err = q.QueryRow(ctx, `
INSERT INTO users (n_first_name, n_last_name, email, phone, date_of_birth, password_hash, account_role, account_type)
VALUES ($1, $2, $3, $4, $5, $6, $7, 'email')
RETURNING id
@@ -54,10 +55,10 @@ func createTestUser(pool *pgxpool.Pool, firstName, lastName, email, role string)
return userID, nil
}
func CreateTestService(pool *pgxpool.Pool) (string, error) {
func CreateTestService(q db.Querier) (string, error) {
ctx := context.Background()
var serviceID string
err := pool.QueryRow(ctx, `
err := q.QueryRow(ctx, `
INSERT INTO services (name, description, price, duration_minutes, is_active, minimum_age_required)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id
@@ -72,12 +73,12 @@ func CreateTestService(pool *pgxpool.Pool) (string, error) {
// CreateTestServiceWithPatchTest creates a service and a patch test that links to it
// Returns serviceID, patchTestID
func CreateTestServiceWithPatchTest(pool *pgxpool.Pool) (string, string, error) {
func CreateTestServiceWithPatchTest(q db.Querier) (string, string, error) {
ctx := context.Background()
// First create the service
var serviceID string
err := pool.QueryRow(ctx, `
err := q.QueryRow(ctx, `
INSERT INTO services (name, description, price, duration_minutes, is_active, minimum_age_required)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id
@@ -89,7 +90,7 @@ func CreateTestServiceWithPatchTest(pool *pgxpool.Pool) (string, string, error)
// Now create a patch test that links to this service
var patchTestID string
err = pool.QueryRow(ctx, `
err = q.QueryRow(ctx, `
INSERT INTO patch_tests (name, description, notice_duration_hours, expiry_months, service_ids)
VALUES ($1, $2, $3, $4, $5)
RETURNING id
@@ -103,10 +104,10 @@ func CreateTestServiceWithPatchTest(pool *pgxpool.Pool) (string, string, error)
}
// CreateTestPatchTest creates a patch test definition
func CreateTestPatchTest(pool *pgxpool.Pool, serviceIDs []string) (string, error) {
func CreateTestPatchTest(q db.Querier, serviceIDs []string) (string, error) {
ctx := context.Background()
var patchTestID string
err := pool.QueryRow(ctx, `
err := q.QueryRow(ctx, `
INSERT INTO patch_tests (name, description, notice_duration_hours, expiry_months, service_ids)
VALUES ($1, $2, $3, $4, $5)
RETURNING id
@@ -120,9 +121,9 @@ func CreateTestPatchTest(pool *pgxpool.Pool, serviceIDs []string) (string, error
}
// CreateUserPatchTest creates a user patch test record
func CreateUserPatchTest(pool *pgxpool.Pool, userID, patchTestID string, testedAt string) error {
func CreateUserPatchTest(q db.Querier, userID, patchTestID string, testedAt string) error {
ctx := context.Background()
_, err := pool.Exec(ctx, `
_, err := q.Exec(ctx, `
INSERT INTO user_patch_tests (user_id, patch_test_id, tested_at)
VALUES ($1, $2, $3)
`, userID, patchTestID, testedAt)
@@ -134,14 +135,14 @@ func CreateUserPatchTest(pool *pgxpool.Pool, userID, patchTestID string, testedA
return nil
}
func CreateTestBooking(pool *pgxpool.Pool, userID, serviceID string) (string, error) {
return CreateTestBookingAtTime(pool, userID, serviceID, time.Date(2099, 12, 31, 10, 0, 0, 0, time.UTC))
func CreateTestBooking(q db.Querier, userID, serviceID string) (string, error) {
return CreateTestBookingAtTime(q, userID, serviceID, time.Date(2099, 12, 31, 10, 0, 0, 0, time.UTC))
}
func CreateTestBookingAtTime(pool *pgxpool.Pool, userID, serviceID string, startTime time.Time) (string, error) {
func CreateTestBookingAtTime(q db.Querier, userID, serviceID string, startTime time.Time) (string, error) {
ctx := context.Background()
var bookingID string
err := pool.QueryRow(ctx, `
err := q.QueryRow(ctx, `
INSERT INTO bookings (user_id, start_time, status, notes)
VALUES ($1, $2, $3, $4)
RETURNING id
@@ -151,7 +152,7 @@ func CreateTestBookingAtTime(pool *pgxpool.Pool, userID, serviceID string, start
return "", fmt.Errorf("failed to create booking: %w", err)
}
_, err = pool.Exec(ctx, `
_, err = q.Exec(ctx, `
INSERT INTO booking_services (booking_id, service_id)
VALUES ($1, $2)
`, bookingID, serviceID)
@@ -163,40 +164,40 @@ func CreateTestBookingAtTime(pool *pgxpool.Pool, userID, serviceID string, start
return bookingID, nil
}
func CreateTestVerifiedUser(pool *pgxpool.Pool) (string, error) {
return createTestUser(pool, "Verified", "User", "verified@test.com", "verified_email")
func CreateTestVerifiedUser(q db.Querier) (string, error) {
return createTestUser(q, "Verified", "User", "verified@test.com", "verified_email")
}
func CreateTestUnverifiedUser(pool *pgxpool.Pool) (string, error) {
return createTestUser(pool, "Unverified", "User", "unverified@test.com", "unverified_email")
func CreateTestUnverifiedUser(q db.Querier) (string, error) {
return createTestUser(q, "Unverified", "User", "unverified@test.com", "unverified_email")
}
func CreateTestGuestUser(pool *pgxpool.Pool) (string, error) {
return createTestUser(pool, "Guest", "User", "guest@test.com", "guest")
func CreateTestGuestUser(q db.Querier) (string, error) {
return createTestUser(q, "Guest", "User", "guest@test.com", "guest")
}
func DeleteUser(pool *pgxpool.Pool, userID string) error {
func DeleteUser(q db.Querier, userID string) error {
ctx := context.Background()
_, err := pool.Exec(ctx, "DELETE FROM users WHERE id = $1", userID)
_, err := q.Exec(ctx, "DELETE FROM users WHERE id = $1", userID)
return err
}
func DeleteService(pool *pgxpool.Pool, serviceID string) error {
func DeleteService(q db.Querier, serviceID string) error {
ctx := context.Background()
_, err := pool.Exec(ctx, "DELETE FROM services WHERE id = $1", serviceID)
_, err := q.Exec(ctx, "DELETE FROM services WHERE id = $1", serviceID)
return err
}
func DeleteBooking(pool *pgxpool.Pool, bookingID string) error {
func DeleteBooking(q db.Querier, bookingID string) error {
ctx := context.Background()
_, err := pool.Exec(ctx, "DELETE FROM bookings WHERE id = $1", bookingID)
_, err := q.Exec(ctx, "DELETE FROM bookings WHERE id = $1", bookingID)
return err
}
func CreateTestCustomService(pool *pgxpool.Pool) (string, error) {
func CreateTestCustomService(q db.Querier) (string, error) {
ctx := context.Background()
var serviceID string
err := pool.QueryRow(ctx, `
err := q.QueryRow(ctx, `
INSERT INTO custom_services (name, description, price, duration_minutes, minimum_age_required)
VALUES ($1, $2, $3, $4, $5)
RETURNING id
@@ -209,33 +210,33 @@ func CreateTestCustomService(pool *pgxpool.Pool) (string, error) {
return serviceID, nil
}
func DeleteCustomService(pool *pgxpool.Pool, serviceID string) error {
func DeleteCustomService(q db.Querier, serviceID string) error {
ctx := context.Background()
_, err := pool.Exec(ctx, "DELETE FROM custom_services WHERE id = $1", serviceID)
_, err := q.Exec(ctx, "DELETE FROM custom_services WHERE id = $1", serviceID)
return err
}
// SafeDeleteUser wraps DeleteUser and returns error (for tests that care about cleanup failure)
func SafeDeleteUser(db *pgxpool.Pool, userID string) error {
return DeleteUser(db, userID)
func SafeDeleteUser(q db.Querier, userID string) error {
return DeleteUser(q, userID)
}
// SafeDeleteService wraps DeleteService and returns error (for tests that care about cleanup failure)
func SafeDeleteService(db *pgxpool.Pool, serviceID string) error {
return DeleteService(db, serviceID)
func SafeDeleteService(q db.Querier, serviceID string) error {
return DeleteService(q, serviceID)
}
// SafeDeleteBooking wraps DeleteBooking and returns error (for tests that care about cleanup failure)
func SafeDeleteBooking(db *pgxpool.Pool, bookingID string) error {
return DeleteBooking(db, bookingID)
func SafeDeleteBooking(q db.Querier, bookingID string) error {
return DeleteBooking(q, bookingID)
}
// CreateTestTimeBlocker creates a time blocker for testing
// Returns the blocker ID
func CreateTestTimeBlocker(pool *pgxpool.Pool, startTime time.Time, durationMinutes int, description string) (string, error) {
func CreateTestTimeBlocker(q db.Querier, startTime time.Time, durationMinutes int, description string) (string, error) {
ctx := context.Background()
var blockerID string
err := pool.QueryRow(ctx, `
err := q.QueryRow(ctx, `
INSERT INTO time_blockers (start_time, duration_minutes, description)
VALUES ($1, $2, $3)
RETURNING id
@@ -249,18 +250,18 @@ func CreateTestTimeBlocker(pool *pgxpool.Pool, startTime time.Time, durationMinu
}
// DeleteTimeBlocker removes a time blocker from the database
func DeleteTimeBlocker(pool *pgxpool.Pool, blockerID string) error {
func DeleteTimeBlocker(q db.Querier, blockerID string) error {
ctx := context.Background()
_, err := pool.Exec(ctx, "DELETE FROM time_blockers WHERE id = $1", blockerID)
_, err := q.Exec(ctx, "DELETE FROM time_blockers WHERE id = $1", blockerID)
return err
}
// CreateTestPayment creates a payment record for testing
// Returns payment ID
func CreateTestPayment(db *pgxpool.Pool, bookingID string, amount float64, method string, ptype string, status string) (string, error) {
func CreateTestPayment(q db.Querier, bookingID string, amount float64, method string, ptype string, status string) (string, error) {
ctx := context.Background()
var paymentID string
err := db.QueryRow(ctx, `
err := q.QueryRow(ctx, `
INSERT INTO payments (booking_id, payment_type, payment_method, status, amount, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, NOW(), NOW())
RETURNING id
@@ -275,10 +276,10 @@ func CreateTestPayment(db *pgxpool.Pool, bookingID string, amount float64, metho
// CreateTestRefund creates a refund record for testing
// Returns refund ID
func CreateTestRefund(db *pgxpool.Pool, paymentID string, bookingID string, amount float64) (string, error) {
func CreateTestRefund(q db.Querier, paymentID string, bookingID string, amount float64) (string, error) {
ctx := context.Background()
var refundID string
err := db.QueryRow(ctx, `
err := q.QueryRow(ctx, `
INSERT INTO refunds (payment_id, booking_id, amount, status, reason, created_at)
VALUES ($1, $2, $3, 'completed', 'test refund', NOW())
RETURNING id
@@ -293,10 +294,10 @@ func CreateTestRefund(db *pgxpool.Pool, paymentID string, bookingID string, amou
// CreateTestPaymentMethod creates a saved card for a user
// Returns card ID
func CreateTestPaymentMethod(db *pgxpool.Pool, userID string, squareCardID string, brand string, last4 string) (string, error) {
func CreateTestPaymentMethod(q db.Querier, userID string, squareCardID string, brand string, last4 string) (string, error) {
ctx := context.Background()
var cardID string
err := db.QueryRow(ctx, `
err := q.QueryRow(ctx, `
INSERT INTO user_saved_cards (user_id, square_card_id, brand, last_4, exp_month, exp_year, fingerprint, is_default, created_at)
VALUES ($1, $2, $3, $4, 12, 2030, 'test_fp', false, NOW())
RETURNING id
@@ -310,22 +311,22 @@ func CreateTestPaymentMethod(db *pgxpool.Pool, userID string, squareCardID strin
}
// DeletePayment deletes a payment from the database
func DeletePayment(pool *pgxpool.Pool, paymentID string) error {
func DeletePayment(q db.Querier, paymentID string) error {
ctx := context.Background()
_, err := pool.Exec(ctx, "DELETE FROM payments WHERE id = $1", paymentID)
_, err := q.Exec(ctx, "DELETE FROM payments WHERE id = $1", paymentID)
return err
}
// DeleteRefund deletes a refund from the database
func DeleteRefund(pool *pgxpool.Pool, refundID string) error {
func DeleteRefund(q db.Querier, refundID string) error {
ctx := context.Background()
_, err := pool.Exec(ctx, "DELETE FROM refunds WHERE id = $1", refundID)
_, err := q.Exec(ctx, "DELETE FROM refunds WHERE id = $1", refundID)
return err
}
// DeletePaymentMethod deletes a saved card from the database
func DeletePaymentMethod(pool *pgxpool.Pool, cardID string) error {
func DeletePaymentMethod(q db.Querier, cardID string) error {
ctx := context.Background()
_, err := pool.Exec(ctx, "DELETE FROM user_saved_cards WHERE id = $1", cardID)
_, err := q.Exec(ctx, "DELETE FROM user_saved_cards WHERE id = $1", cardID)
return err
}