feat(backend): add PoolProxy, Querier, and per-test transaction infrastructure
New types and helpers for context-aware DB routing and per-test transactions: - PoolProxy: wraps pgxpool.Pool, routes queries through context transaction when active - Querier: interface accepted by fixture/helper functions for decoupling - ContextWithTx / TxFromContext: store/extract pgx.Tx in context.Context - SetupTestTx (in testutils): begins tx, stores in context, auto-rolls back on cleanup - SetupTestTx (in testtx): package-level variant with semaphore for parallel safety Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
//go:build test
|
||||
// +build test
|
||||
|
||||
package testutils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"crussell/db"
|
||||
)
|
||||
|
||||
// txSemaphore limits concurrent test transactions per package to prevent
|
||||
// pgxpool connection exhaustion when many tests use t.Parallel().
|
||||
// The cap (8) should match the -parallel N flag used in test invocations
|
||||
// and should be well below MaxConns per pool (currently 16).
|
||||
var txSemaphore = make(chan struct{}, 8)
|
||||
|
||||
// SetupTestTx begins a transaction on the db.Conn pool, stores it in the
|
||||
// returned context (for PoolProxy routing), and returns the raw tx for use
|
||||
// with fixture functions (e.g. fixtures.CreateTestUser(tx)).
|
||||
//
|
||||
// The transaction is automatically rolled back in t.Cleanup. After calling
|
||||
// SetupTestTx, the test should call t.Parallel() and pass the returned context
|
||||
// to any request helper functions (MakeRequest, makeAdminRequest, etc.).
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// func TestMyHandler(t *testing.T) {
|
||||
// t.Parallel()
|
||||
// ctx, tx := testutils.SetupTestTx(t)
|
||||
// userID, _ := fixtures.CreateTestUser(tx)
|
||||
// w := testutils.MakeUserRequest(handler, "GET", "/api/foo", nil, userID, ctx)
|
||||
// assert.Equal(t, 200, w.Code)
|
||||
// }
|
||||
func SetupTestTx(t *testing.T) (context.Context, db.Querier) {
|
||||
t.Helper()
|
||||
txSemaphore <- struct{}{}
|
||||
pool := db.Conn.Pool()
|
||||
tx, err := pool.Begin(context.Background())
|
||||
if err != nil {
|
||||
<-txSemaphore
|
||||
t.Fatalf("SetupTestTx: failed to begin transaction: %v", err)
|
||||
}
|
||||
ctx := db.ContextWithTx(context.Background(), tx)
|
||||
t.Cleanup(func() {
|
||||
tx.Rollback(context.Background())
|
||||
<-txSemaphore
|
||||
})
|
||||
return ctx, tx
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user