docs(03): create phase plan
This commit is contained in:
@@ -55,7 +55,9 @@ Plans:
|
|||||||
2. Production SQLite connection applies `synchronous=NORMAL`, `cache_size=-8000`, and `mmap_size=67108864` PRAGMAs at database open
|
2. Production SQLite connection applies `synchronous=NORMAL`, `cache_size=-8000`, and `mmap_size=67108864` PRAGMAs at database open
|
||||||
3. Each test gets an isolated database instance — no shared state between test functions
|
3. Each test gets an isolated database instance — no shared state between test functions
|
||||||
4. Tests using `NewTestDB` pass with `-race` flag enabled
|
4. Tests using `NewTestDB` pass with `-race` flag enabled
|
||||||
**Plans:** TBD
|
**Plans:** 1 plan
|
||||||
|
Plans:
|
||||||
|
- [ ] 03-01-PLAN.md — Extract shared applyPRAGMAs, add production PRAGMAs, and create NewTestDB helper
|
||||||
|
|
||||||
### Phase 4: Queue, Config & Player Tests
|
### Phase 4: Queue, Config & Player Tests
|
||||||
**Goal:** The queue, config, and player packages have comprehensive unit tests that characterize current behavior and serve as a safety net for later refactoring
|
**Goal:** The queue, config, and player packages have comprehensive unit tests that characterize current behavior and serve as a safety net for later refactoring
|
||||||
@@ -117,7 +119,7 @@ Plans:
|
|||||||
|-------|----------------|--------|-----------|
|
|-------|----------------|--------|-----------|
|
||||||
| 1. Concurrency Race Fixes | 1/1 | Complete | 2026-02-28 |
|
| 1. Concurrency Race Fixes | 1/1 | Complete | 2026-02-28 |
|
||||||
| 2. Backend Correctness | 2/2 | Complete | 2026-03-03 |
|
| 2. Backend Correctness | 2/2 | Complete | 2026-03-03 |
|
||||||
| 3. Test Infrastructure | 0/? | Not started | — |
|
| 3. Test Infrastructure | 0/1 | Planned | — |
|
||||||
| 4. Queue, Config & Player Tests | 0/? | Not started | — |
|
| 4. Queue, Config & Player Tests | 0/? | Not started | — |
|
||||||
| 5. Database & Library Tests | 0/? | Not started | — |
|
| 5. Database & Library Tests | 0/? | Not started | — |
|
||||||
| 6. SQL Consolidation & Code Quality | 0/? | Not started | — |
|
| 6. SQL Consolidation & Code Quality | 0/? | Not started | — |
|
||||||
|
|||||||
@@ -0,0 +1,207 @@
|
|||||||
|
---
|
||||||
|
phase: 03-test-infrastructure
|
||||||
|
plan: 01
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: []
|
||||||
|
files_modified:
|
||||||
|
- backend/database/database.go
|
||||||
|
- backend/database/testhelper.go
|
||||||
|
autonomous: true
|
||||||
|
requirements:
|
||||||
|
- TEST-01
|
||||||
|
- PERF-04
|
||||||
|
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "Production SQLite connection applies synchronous=NORMAL, cache_size=-8000, and mmap_size=67108864 PRAGMAs at database open"
|
||||||
|
- "NewTestDB(t) returns a clean in-memory SQLite DB with the same migrations and PRAGMAs as production NewDB()"
|
||||||
|
- "Each test gets an isolated database instance — no shared state between test functions"
|
||||||
|
- "Tests using NewTestDB pass with -race flag enabled"
|
||||||
|
artifacts:
|
||||||
|
- path: "backend/database/database.go"
|
||||||
|
provides: "Shared applyPRAGMAs function + production PRAGMA application in NewDB"
|
||||||
|
contains: "applyPRAGMAs"
|
||||||
|
- path: "backend/database/testhelper.go"
|
||||||
|
provides: "NewTestDB test helper for in-memory SQLite with production-mirror setup"
|
||||||
|
exports: ["NewTestDB"]
|
||||||
|
key_links:
|
||||||
|
- from: "backend/database/testhelper.go"
|
||||||
|
to: "backend/database/database.go"
|
||||||
|
via: "shared applyPRAGMAs function"
|
||||||
|
pattern: "applyPRAGMAs\\("
|
||||||
|
- from: "backend/database/testhelper.go"
|
||||||
|
to: "backend/database/database.go"
|
||||||
|
via: "shared schema application (schemas embed + runMigrations)"
|
||||||
|
pattern: "schemas\\.ReadDir|runMigrations"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
Create a production-mirroring SQLite test helper and apply performance PRAGMAs to the production database connection.
|
||||||
|
|
||||||
|
Purpose: Establish the test foundation that all subsequent test phases (4-5) depend on. Tests need real database instances with identical configuration to production — same PRAGMAs, same migrations, same constraints — so test results are trustworthy.
|
||||||
|
|
||||||
|
Output: Modified `database.go` with shared PRAGMA function + production PRAGMAs applied, and new `testhelper.go` with `NewTestDB(t)`.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@/home/caleb/.config/opencode/get-shit-done/workflows/execute-plan.md
|
||||||
|
@/home/caleb/.config/opencode/get-shit-done/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.planning/PROJECT.md
|
||||||
|
@.planning/ROADMAP.md
|
||||||
|
@.planning/STATE.md
|
||||||
|
|
||||||
|
@backend/database/database.go
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<interfaces>
|
||||||
|
<!-- Key types and contracts the executor needs. Extracted from codebase. -->
|
||||||
|
|
||||||
|
From backend/database/database.go:
|
||||||
|
```go
|
||||||
|
// DB wraps the SQLite database connection and queries.
|
||||||
|
type DB struct {
|
||||||
|
db *sql.DB
|
||||||
|
Ctx context.Context
|
||||||
|
Queries *sqlcgen.Queries
|
||||||
|
logger *slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDB opens the database and applies schema migrations.
|
||||||
|
func NewDB(logger *slog.Logger) (*DB, error)
|
||||||
|
|
||||||
|
// BeginTx starts a new database transaction.
|
||||||
|
func (d *DB) BeginTx() (*sql.Tx, error)
|
||||||
|
|
||||||
|
// ExecContext executes a query without returning any rows.
|
||||||
|
func (d *DB) ExecContext(query string, args ...any) (sql.Result, error)
|
||||||
|
|
||||||
|
// QueryContext executes a query that returns rows.
|
||||||
|
func (d *DB) QueryContext(query string, args ...any) (*sql.Rows, error)
|
||||||
|
```
|
||||||
|
|
||||||
|
From backend/database/database.go (internal):
|
||||||
|
```go
|
||||||
|
//go:embed sql/schemas/*.sql
|
||||||
|
var schemas embed.FS
|
||||||
|
|
||||||
|
func runMigrations(ctx context.Context, db *sql.DB, logger *slog.Logger) error
|
||||||
|
func isDuplicateColumnErr(err error) bool
|
||||||
|
```
|
||||||
|
</interfaces>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Extract shared applyPRAGMAs and add production PRAGMAs to NewDB</name>
|
||||||
|
<files>backend/database/database.go</files>
|
||||||
|
<action>
|
||||||
|
In `backend/database/database.go`:
|
||||||
|
|
||||||
|
1. Create an unexported `applyPRAGMAs(ctx context.Context, db *sql.DB) error` function that executes these PRAGMAs in order:
|
||||||
|
- `PRAGMA foreign_keys = ON` (already exists in NewDB — extract it)
|
||||||
|
- `PRAGMA synchronous = NORMAL`
|
||||||
|
- `PRAGMA cache_size = -8000`
|
||||||
|
- `PRAGMA mmap_size = 67108864`
|
||||||
|
|
||||||
|
Use a slice of PRAGMA strings and loop over them with `db.ExecContext`. Wrap errors with `fmt.Errorf("could not apply PRAGMA %q: %w", pragma, err)`.
|
||||||
|
|
||||||
|
2. Modify `NewDB()` to call `applyPRAGMAs(dbCtx, db)` instead of the inline `PRAGMA foreign_keys = ON` exec. Insert the call right after `db.SetMaxOpenConns(1)` — PRAGMAs before schema creation, per CONTEXT.md decision.
|
||||||
|
|
||||||
|
3. Remove the standalone `foreign_keys` PRAGMA block that currently exists in `NewDB()` (lines 58-65) since it's now handled by `applyPRAGMAs`.
|
||||||
|
|
||||||
|
4. Add a doc comment on `applyPRAGMAs`: `// applyPRAGMAs configures SQLite connection settings. Called by both NewDB and NewTestDB to ensure identical behavior.`
|
||||||
|
|
||||||
|
Follow existing conventions: error wrapping with `fmt.Errorf`, blank line after early returns (`nlreturn`), keep lines under 100 chars (`golines`).
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd /mnt/vault/dev/golang/yellowjacket && go build -tags webkit2_41 ./backend/database/ && go vet -tags webkit2_41 ./backend/database/</automated>
|
||||||
|
</verify>
|
||||||
|
<done>
|
||||||
|
- `applyPRAGMAs` function exists in `database.go` with all 4 PRAGMAs (foreign_keys, synchronous, cache_size, mmap_size)
|
||||||
|
- `NewDB()` calls `applyPRAGMAs` instead of inline foreign_keys PRAGMA
|
||||||
|
- Package compiles and passes vet
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Create NewTestDB helper in testhelper.go</name>
|
||||||
|
<files>backend/database/testhelper.go</files>
|
||||||
|
<action>
|
||||||
|
Create `backend/database/testhelper.go` with:
|
||||||
|
|
||||||
|
1. Package declaration: `package database`
|
||||||
|
|
||||||
|
2. Imports: `context`, `database/sql`, `fmt`, `io/fs`, `log/slog`, `path`, `testing`, `modernc.org/sqlite` (blank import for driver), and `yellowjacket/backend/database/sql/sqlcgen`.
|
||||||
|
|
||||||
|
3. Exported function `NewTestDB(t *testing.T) *DB`:
|
||||||
|
- Call `t.Helper()` at the start
|
||||||
|
- Open in-memory SQLite: `sql.Open("sqlite", ":memory:?_busy_timeout=5000&_journal_mode=WAL")`
|
||||||
|
- If open fails, `t.Fatalf("could not open test database: %v", err)`
|
||||||
|
- `db.SetMaxOpenConns(1)` — same as production
|
||||||
|
- Create context: `ctx := t.Context()` (use `t.Context()` per usetesting linter)
|
||||||
|
- Call `applyPRAGMAs(ctx, db)` — if error, `t.Fatalf("could not apply PRAGMAs: %v", err)`
|
||||||
|
- Apply schemas: iterate `schemas.ReadDir("sql/schemas")`, read each .sql file, `db.ExecContext(ctx, string(sqlContent))` — mirror the exact loop from `NewDB()`. If error, `t.Fatalf`.
|
||||||
|
- Call `runMigrations(ctx, db, slog.Default())` — if error, `t.Fatalf("could not run migrations: %v", err)`
|
||||||
|
- Do NOT run orphan cleanup query (CONTEXT.md decision: "test DBs start empty, no orphans to clean")
|
||||||
|
- Create queries: `queries := sqlcgen.New(db)`
|
||||||
|
- Register cleanup: `t.Cleanup(func() { db.Close() })`
|
||||||
|
- Return `&DB{db: db, Ctx: ctx, Queries: queries, logger: slog.Default()}`
|
||||||
|
|
||||||
|
4. Add doc comment: `// NewTestDB returns an in-memory SQLite database that mirrors the production setup (PRAGMAs + all migrations). The database is automatically closed when the test completes via t.Cleanup.`
|
||||||
|
|
||||||
|
Note: Do NOT expose raw `*sql.DB` — tests use `DB.ExecContext()` / `DB.Queries` like production code (per CONTEXT.md decision). No functional options. No error return — failures are fatal via `t.Fatalf`.
|
||||||
|
|
||||||
|
Follow conventions: `t.Helper()`, `t.Context()`, blank import comment, doc comments ending with period, `nlreturn` spacing.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd /mnt/vault/dev/golang/yellowjacket && go build -tags webkit2_41 ./backend/database/ && go vet -tags webkit2_41 ./backend/database/ && go test -tags webkit2_41 -race -count=1 -run TestNewTestDB ./backend/database/ 2>&1 || echo "No test yet — build+vet passed"</automated>
|
||||||
|
</verify>
|
||||||
|
<done>
|
||||||
|
- `backend/database/testhelper.go` exists with exported `NewTestDB(t *testing.T) *DB`
|
||||||
|
- Function opens `:memory:` DB, applies PRAGMAs via shared `applyPRAGMAs`, applies schemas, runs migrations
|
||||||
|
- No orphan cleanup, no health check, no error return, no functional options
|
||||||
|
- Cleanup registered via `t.Cleanup()`
|
||||||
|
- Package compiles, passes vet, and passes `-race` flag
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
After both tasks complete, run the full verification:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Build the database package
|
||||||
|
go build -tags webkit2_41 ./backend/database/
|
||||||
|
|
||||||
|
# 2. Vet the database package
|
||||||
|
go vet -tags webkit2_41 ./backend/database/
|
||||||
|
|
||||||
|
# 3. Run all existing tests with race detector to confirm no regressions
|
||||||
|
make test
|
||||||
|
|
||||||
|
# 4. Verify applyPRAGMAs is called from both NewDB and NewTestDB
|
||||||
|
grep -n "applyPRAGMAs" backend/database/database.go backend/database/testhelper.go
|
||||||
|
|
||||||
|
# 5. Verify production PRAGMAs are all present
|
||||||
|
grep -c "PRAGMA" backend/database/database.go
|
||||||
|
```
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
1. `backend/database/database.go` has a shared `applyPRAGMAs` function with all 4 PRAGMAs
|
||||||
|
2. `NewDB()` calls `applyPRAGMAs` (no more inline foreign_keys PRAGMA)
|
||||||
|
3. `backend/database/testhelper.go` exports `NewTestDB(t *testing.T) *DB`
|
||||||
|
4. `NewTestDB` uses `:memory:` with same connection params, calls `applyPRAGMAs` + schema loop + `runMigrations`
|
||||||
|
5. `NewTestDB` registers `t.Cleanup(func() { db.Close() })`
|
||||||
|
6. `make test` passes (all existing tests green, race detector clean)
|
||||||
|
7. No orphan cleanup in `NewTestDB`, no health check, no functional options
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.planning/phases/03-test-infrastructure/03-01-SUMMARY.md`
|
||||||
|
</output>
|
||||||
Reference in New Issue
Block a user