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
+26 -29
View File
@@ -11,27 +11,20 @@ import (
"net/http/httptest"
"testing"
"crussell/db"
"crussell/mw"
"crussell/testutils/jwt"
"crussell/testutils/testdb"
"github.com/jackc/pgx/v5/pgxpool"
)
// SetupTestDB resets test data by truncating tables
// Assumes db.DB is already set by TestMain
func SetupTestDB(t *testing.T) func() {
t.Helper()
// contextKey matches mw.UserIDKey to avoid import cycle with auth package.
type contextKey string
testdb.TruncateTables(t, db.DB)
const userIDKey contextKey = "user_id"
return func() {}
}
// MakeRequest makes an HTTP request to a handler with optional JWT token
// token can be user token or admin token. Pass empty string for no auth.
func MakeRequest(handler http.Handler, method, path string, body interface{}, token string) *httptest.ResponseRecorder {
// MakeRequest makes an HTTP request to a handler with optional JWT token.
// ctx should carry a per-test transaction (from SetupTestTx) for PoolProxy routing.
func MakeRequest(handler http.Handler, method, path string, body interface{}, token string, ctx context.Context) *httptest.ResponseRecorder {
var req *http.Request
if body != nil {
bodyBytes, _ := json.Marshal(body)
@@ -41,6 +34,8 @@ func MakeRequest(handler http.Handler, method, path string, body interface{}, to
req = httptest.NewRequest(method, path, nil)
}
req = req.WithContext(ctx)
if token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
@@ -68,25 +63,26 @@ func MakeRequestWithContext(handler http.Handler, method, path string, body inte
return w
}
// MakeUserRequest makes a request as an authenticated user
// Generates a valid user token and includes it in the Authorization header
func MakeUserRequest(handler http.Handler, method, path string, body interface{}, userID string) *httptest.ResponseRecorder {
// MakeUserRequest makes a request as an authenticated user.
// ctx should carry a per-test transaction (from SetupTestTx) for PoolProxy routing.
func MakeUserRequest(handler http.Handler, method, path string, body interface{}, userID string, ctx context.Context) *httptest.ResponseRecorder {
token := jwt.GenerateUserToken(userID)
return MakeRequest(handler, method, path, body, token)
return MakeRequest(handler, method, path, body, token, ctx)
}
// MakeAdminRequest makes a request as an authenticated admin
// Generates a valid admin token and includes it in the Authorization header
func MakeAdminRequest(handler http.Handler, method, path string, body interface{}, adminID string) *httptest.ResponseRecorder {
// MakeAdminRequest makes a request as an authenticated admin.
// ctx should carry a per-test transaction (from SetupTestTx) for PoolProxy routing.
func MakeAdminRequest(handler http.Handler, method, path string, body interface{}, adminID string, ctx context.Context) *httptest.ResponseRecorder {
token := jwt.GenerateTestToken(adminID, "admin")
return MakeRequest(handler, method, path, body, token)
return MakeRequest(handler, method, path, body, token, ctx)
}
// MakeContextRequest makes a request with user context set
// Useful for testing handlers that check context before validating token
func MakeContextRequest(handler http.Handler, method, path string, body interface{}, userID string) *httptest.ResponseRecorder {
ctx := context.WithValue(context.Background(), mw.UserIDKey, userID)
return MakeRequestWithContext(handler, method, path, body, ctx)
// MakeContextRequest makes a request with user context set.
// ctx should carry a per-test transaction (from SetupTestTx) for PoolProxy routing.
// The user ID is layered on top of the transaction context.
func MakeContextRequest(handler http.Handler, method, path string, body interface{}, userID string, ctx context.Context) *httptest.ResponseRecorder {
reqCtx := context.WithValue(ctx, userIDKey, userID)
return MakeRequestWithContext(handler, method, path, body, reqCtx)
}
// ParseResponseBody unmarshals the response body into dest
@@ -156,9 +152,10 @@ func GetBodyAsJSON(w *httptest.ResponseRecorder) (map[string]interface{}, error)
return result, err
}
// MakeRequestNoAuth makes an HTTP request without authentication (for testing unauthenticated endpoints)
func MakeRequestNoAuth(handler http.Handler, method, path string, body interface{}) *httptest.ResponseRecorder {
return MakeRequest(handler, method, path, body, "")
// MakeRequestNoAuth makes an HTTP request without authentication.
// ctx should carry a per-test transaction (from SetupTestTx) for PoolProxy routing.
func MakeRequestNoAuth(handler http.Handler, method, path string, body interface{}, ctx context.Context) *httptest.ResponseRecorder {
return MakeRequest(handler, method, path, body, "", ctx)
}
// AssertErrorStatusCode checks status code and validates error is in response body