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:
2026-08-19 23:20:15 -06:00
parent 82efe19ae0
commit 4e77052a37
35 changed files with 1242 additions and 144 deletions
+115
View File
@@ -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
}