Claude-assisted improvements (untested)
- dev auth flow (side-step OAuth) - db event processing integration tests - dev scripts (eg Makefile) - db / test db migration setup scripts.
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
// Package testdb provides shared fixtures for integration tests that need a
|
||||
// real Postgres connection. It targets TEST_DATABASE_URL, which `make test`
|
||||
// points at a dedicated inventory_2_test database by default; override it
|
||||
// (e.g. `make test TEST_DATABASE_URL=...`) to run the same tests against
|
||||
// another database, such as the real dev one.
|
||||
package testdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"ruben/inventory2/logging"
|
||||
)
|
||||
|
||||
// Pool connects to TEST_DATABASE_URL. If it isn't set, the test is skipped
|
||||
// rather than failed, so `go test ./...` works without Postgres running.
|
||||
func Pool(t *testing.T) *pgxpool.Pool {
|
||||
t.Helper()
|
||||
|
||||
dsn := os.Getenv("TEST_DATABASE_URL")
|
||||
if dsn == "" {
|
||||
t.Skip("TEST_DATABASE_URL not set; skipping integration test (see .env.example, or run via `make test`)")
|
||||
}
|
||||
|
||||
pool, err := pgxpool.New(context.Background(), dsn)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to connect to test database: %v", err)
|
||||
}
|
||||
t.Cleanup(pool.Close)
|
||||
|
||||
if err := pool.Ping(context.Background()); err != nil {
|
||||
t.Fatalf("failed to reach test database at TEST_DATABASE_URL: %v", err)
|
||||
}
|
||||
|
||||
return pool
|
||||
}
|
||||
|
||||
// Logger returns a logger that discards output, for stores that require one
|
||||
// but whose logging isn't under test.
|
||||
func Logger() *logging.Logger {
|
||||
return logging.New(slog.NewTextHandler(io.Discard, nil))
|
||||
}
|
||||
|
||||
// NewUserID returns a unique user_id for a test to use, so parallel test
|
||||
// runs (including against a shared database) never collide.
|
||||
func NewUserID(t *testing.T) string {
|
||||
t.Helper()
|
||||
return "test-" + t.Name() + "-" + uuid.NewString()
|
||||
}
|
||||
|
||||
// SeedOAuthUser inserts a bare oauth_users row so accounts/etc. FKs that
|
||||
// reference it are satisfiable, and registers cleanup. Use SeedOAuthSession
|
||||
// instead if the test also needs a valid access token.
|
||||
func SeedOAuthUser(t *testing.T, pool *pgxpool.Pool, userID string) {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
|
||||
if _, err := pool.Exec(ctx, `INSERT INTO oauth_users (user_id) VALUES ($1)`, userID); err != nil {
|
||||
t.Fatalf("failed to seed oauth_users row: %v", err)
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
if _, err := pool.Exec(context.Background(), `DELETE FROM oauth_users WHERE user_id = $1`, userID); err != nil {
|
||||
t.Errorf("cleanup: failed to delete oauth_users row: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// SeedOAuthSession inserts an oauth_users row plus a matching oauth_tokens
|
||||
// row with a freshly generated access token, mirroring the shape a real (or
|
||||
// authentication.Authenticator.DevLogin) session leaves behind. Registers
|
||||
// cleanup for both rows, in dependency order.
|
||||
func SeedOAuthSession(t *testing.T, pool *pgxpool.Pool, userID string) (accessToken string) {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
|
||||
SeedOAuthUser(t, pool, userID)
|
||||
|
||||
accessToken = "test-token-" + uuid.NewString()
|
||||
|
||||
_, err := pool.Exec(ctx, `
|
||||
INSERT INTO oauth_tokens (
|
||||
access_token, token_type, refresh_token, expiry,
|
||||
id_token_issuer, id_token_audience, id_token_subject,
|
||||
id_token_expiry, id_token_issued_at, id_token_nonce, id_token_access_token_hash,
|
||||
id_token_custom_claims_family_name, id_token_custom_claims_given_name,
|
||||
id_token_custom_claims_name, id_token_custom_claims_nickname,
|
||||
id_token_custom_claims_picture, id_token_custom_claims_updated_at
|
||||
) VALUES (
|
||||
$1, 'test', '', NOW() + INTERVAL '1 hour',
|
||||
'test', '{test}', $2,
|
||||
NOW() + INTERVAL '1 hour', NOW(), '', '',
|
||||
'Test', 'User',
|
||||
'Test User', 'testuser',
|
||||
'', NOW()
|
||||
)
|
||||
`, accessToken, userID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to seed oauth_tokens row: %v", err)
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
if _, err := pool.Exec(context.Background(), `DELETE FROM oauth_tokens WHERE access_token = $1`, accessToken); err != nil {
|
||||
t.Errorf("cleanup: failed to delete oauth_tokens row: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
return accessToken
|
||||
}
|
||||
Reference in New Issue
Block a user