Skip to content

F-11 (Medium): Test the production repository against real SQLite#10

Merged
adamburmister merged 1 commit into
mainfrom
fix/f11-sqlite-repository-tests
Jul 18, 2026
Merged

F-11 (Medium): Test the production repository against real SQLite#10
adamburmister merged 1 commit into
mainfrom
fix/f11-sqlite-repository-tests

Conversation

@adamburmister

Copy link
Copy Markdown
Owner

Finding

F-11 (Medium) from the ledger audit: the production SqlStorageRepository — where atomicity (transactionSync), rollback, unique-constraint mapping, dedup-race recovery, and SQL date-range semantics actually live — had zero tests in this repo. The 78-test suite ran exclusively against the in-memory double, and the double demonstrably diverged: audit probe P7 pushed onto a returned entry.debitAmounts (runtime arrays; readonly is compile-time only) and the tampered line appeared in subsequent allEntries() reads — the double aliased its internal arrays, while production re-hydrates per query and is immune.

Why it matters: a refactor that, say, moved the idempotency-key insert outside the transaction block would ship with every unit test green. Trusting an external demo app to exercise rollback paths is not a control.

Fix

  • test/helpers/node-sql-storage.ts: a DurableObjectStorage stand-in over Node's built-in node:sqlite (no new dependencies), exposing exactly the surface the repository uses — sql.exec(...) with toArray()/one() cursors and transactionSync implemented via SAVEPOINT. node:sqlite enforces foreign keys by default, matching DO storage.
  • sqlite-repository.spec.ts drives the real repository through the Ledger facade and directly:
    • account creation + duplicate rejection via the actual DB unique index
    • posting by account name, balances, SQL date-range filtering, balanceByType
    • trial balance zero across types including contra accounts
    • entry hydration (getEntry, entriesForAccount, amountsForAccount, getEntryByKey)
    • idempotency-key retry dedup
    • atomicity proof: a balanced payload whose second amount row violates the FK constraint → RepositoryError and zero rows in pluts_entries/pluts_amounts — the rollback the design promises, now verified on every CI run
  • In-memory aliasing fixed: InMemoryRepository.toEntry copies its line arrays (regression test included), so the double matches production semantics.

All six production-repository tests passed on first run — the existing transaction code is correct; it just had no in-repo proof.

Testing

Full suite: 85 passed (78 base + 7 new). tsc --noEmit and biome check clean. The aliasing test was written first and watched fail against the unfixed double.

Note: shares test/types/node-sqlite.d.ts with #4/#5/#7/#8 (identical content — merges cleanly in any order).

🤖 Generated with Claude Code

Every invariant the audit cares about — atomic commit/rollback, no
partial writes, unique-constraint mapping, idempotency dedup, SQL
date-range semantics — lives in SqlStorageRepository, which had zero
tests in this repo: the suite ran only against the in-memory double,
whose divergence was demonstrable (the audit mutated a returned entry's
arrays and the tampering appeared in subsequent reads — the double
aliased internal state; production re-hydrates per query and is
immune). A refactor breaking the transaction block would have shipped
with all unit tests green.

- test/helpers/node-sql-storage.ts: a DurableObjectStorage stand-in
  over Node's built-in node:sqlite exposing exactly the surface the
  repository uses (exec with toArray()/one() cursors, transactionSync
  via SAVEPOINT). Foreign keys enforced by default, matching DO storage.
- sqlite-repository.spec.ts drives the real repository: account
  uniqueness via the DB index, posting + balances + range filtering,
  trial balance with contra accounts, entry hydration, idempotent
  retries, and an atomicity proof — a balanced payload whose second
  amount row violates the FK leaves zero rows behind.
- InMemoryRepository.toEntry now copies its line arrays, with a
  regression test, so the double matches production aliasing semantics.

Audit finding F-11 (Medium).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@adamburmister
adamburmister merged commit c166927 into main Jul 18, 2026
1 check passed
adamburmister added a commit that referenced this pull request Jul 18, 2026
…g test

Main merged F-11 (repository test suite, PR #10) and F-16 (frozen entry
arrays, PR #11) independently; combined, the in-memory aliasing test's
push onto entry.debitAmounts now throws TypeError on the frozen array,
failing on main itself and on every PR's merge CI. The freeze strengthens
the invariant the test checks, so it now asserts the push throws and that
later reads still come back clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant