Skip to content

refactor(test): shared createTestDb helper from migration registry (section B2, part 1)#3498

Merged
Yeraze merged 2 commits into
mainfrom
refactor/b2-test-db-helper
Jun 16, 2026
Merged

refactor(test): shared createTestDb helper from migration registry (section B2, part 1)#3498
Yeraze merged 2 commits into
mainfrom
refactor/b2-test-db-helper

Conversation

@Yeraze

@Yeraze Yeraze commented Jun 16, 2026

Copy link
Copy Markdown
Owner

Section-B refactor #2. Addresses the test-infrastructure fragility the review flagged — the class of bug that broke #3495.

Problem

~40 non-migration test files hand-roll CREATE TABLE per backend, duplicating src/db/schema/. That DDL drifts from the real schema: a single column-add breaks N tests, and the PG/MySQL copies (only run in CI) diverge silently — which is exactly how the #3495 PostgreSQL test-table gap slipped past local runs.

Change

New src/server/test-helpers/testDb.ts:

  • createTestDb() runs the real SQLite migration registry (baseline 001 + every subsequent ALTER) against a fresh :memory: database, returning { sqlite, db, close }.
  • Every test gets the exact production schema, automatically — a schema change is now a single edit in src/db/schema/ + its migration, with zero test-DDL to update.
  • A smoke test (testDb.test.ts) asserts key tables exist, recent columns are present (telemetry.rxSnr from migration 089), and a repo insert/read round-trip works.

Migrated in this PR (part 1)

The three SQLite-only telemetry repo tests whose hand-rolled telemetry DDL had to be patched for #3492/#3495:

  • telemetry.test.ts, telemetry.extra.test.ts, telemetry.badTimestamp.test.ts

These were clean drop-ins (the helper provides the full telemetry table and the migration-032 dedupe index they were mirroring).

Scope / follow-ups

This is part 1 — it lands the proven helper and removes the most-recently-fragile DDL. The remaining ~37 hand-rolled tables (nodes in ~20 files, etc.) will be migrated incrementally in follow-up PRs (now trivial mechanical changes). telemetry.multidb.test.ts is intentionally left — it deliberately exercises per-backend DDL.

Design notes

  • The helper applies migrations via the same registry.getAll() + m.sqlite(db, getSetting, setSetting) path production uses. It does not pre-create a settings stub (that would block the baseline's full table); the get/set shims are defensive for the brief window before settings exists.

Validation

  • tsc --noEmit: clean
  • Full Vitest suite: 6527 pass, 0 fail, 0 suite failures

🤖 Generated with Claude Code

…y (section B2, part 1)

~40 non-migration test files hand-roll `CREATE TABLE` per backend, duplicating
src/db/schema. That DDL drifts: a schema column-add breaks N tests, and the
PG/MySQL copies (only run in CI) diverge silently — exactly what broke #3495.

Add src/server/test-helpers/testDb.ts: `createTestDb()` runs the REAL SQLite
migration registry (baseline 001 + every ALTER) against a fresh `:memory:`
database, so each test gets the exact production schema and a schema change is a
single edit. Includes a smoke test asserting key tables and recent columns
(telemetry.rxSnr from migration 089) are present and a repo round-trip works.

Migrate the three SQLite-only telemetry repo tests — the ones whose hand-rolled
telemetry DDL had to be patched for #3492/#3495 — to use the helper:
telemetry.test.ts, telemetry.extra.test.ts, telemetry.badTimestamp.test.ts.

This is part 1: the helper is proven and the most-recently-fragile DDL is gone.
The remaining ~37 hand-rolled tables (nodes in ~20 files, etc.) will be migrated
incrementally in follow-ups using this now-proven helper. telemetry.multidb.test.ts
is intentionally left — it deliberately exercises per-backend DDL.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown

Claude finished @Yeraze's task —— View job


PR Review: refactor(test): shared createTestDb helper from migration registry

Tasks:

  • Gather context and understand the PR
  • Read changed files
  • Analyze code quality, bugs, performance, security
  • Post review feedback

Summary

This is a well-targeted refactor that solves a real, documented problem. The design is sound and the implementation is clean. A few observations below, ranging from a genuine concern down to minor nits.


Concern: Redundant dedupe index in telemetry.test.ts beforeEach

src/db/repositories/telemetry.test.ts:419–426

The duplicate packet dedupe describe block still manually creates telemetry_source_packet_type_uniq in its own beforeEach:

beforeEach(() => {
  db.exec(`
    CREATE UNIQUE INDEX IF NOT EXISTS telemetry_source_packet_type_uniq
      ON telemetry(sourceId, nodeNum, packetId, telemetryType)
      WHERE packetId IS NOT NULL
  `);
});

But createTestDb() now runs the full migration registry, which includes migration 032 (032_telemetry_packet_dedupe.ts) that already creates this exact index. The IF NOT EXISTS guard means this doesn't cause a test failure, but it's now dead DDL — a reader will reasonably wonder whether the index is present before this block runs, and whether they need to keep this in sync with the migration.

The comment above the describe block (// Regression: issue #3119) says the "migration 032 partial unique index must suppress" conflicts. It should work without the extra db.exec. Consider dropping the inner beforeEach and adding a comment noting the index comes from migration 032 via createTestDb().


Nit: telemetry.extra.test.ts imports still include BetterSQLite3Database but the comment on telemetrySqlite schema import was removed

src/db/repositories/telemetry.extra.test.ts:15-16

The type imports from Drizzle and better-sqlite3 are still present (Database, BetterSQLite3Database) which is fine since the local variables use them. This is correct as-is.


Design observation: no sourceId FK guard in the round-trip smoke test

src/server/test-helpers/testDb.test.ts:30–32

The nodes INSERT in the smoke test uses sourceId: 'default' — worth confirming that sources table has a 'default' row (or that there's no FK enforced from nodes.sourceIdsources.id). In production the baseline migration seeds 'default'; migration 020 creates the sources table and the baseline should seed it. Since createTestDb() runs all migrations including 001, this should be fine — but the smoke test itself doesn't assert it. Low risk, just noting it.


Minor: as any cast in the migration runner

src/server/test-helpers/testDb.ts:59

migration.sqlite(sqlite as any, getSetting, setSetting);

The as any is needed because migrations typed their db argument as Database from better-sqlite3 but Drizzle's SQLite type has a slightly different surface. This is the same workaround used in production. A brief inline comment explaining why the cast is needed (vs. being a lazy bypass) would help future maintainers — though the existing block comment at line 36–38 partially covers it.


Positive highlights

  • The helper itself is tight and correct. Running real migrations against :memory: is the right approach — it's fast (sub-millisecond to spin up) and eliminates schema drift by construction.
  • The getSetting/setSetting shims are defensive without being fragile — the try/catch handles the bootstrapping window cleanly.
  • The smoke test is well-structured: it checks table existence, a column added by a recent migration, and a full repo round-trip. That last test is especially valuable since it exercises the Drizzle binding.
  • The three migrated test files are clean drop-ins. The reduction from 34/28/12 lines of hand-rolled DDL to 6-7 lines of setup is a real readability win.
  • Idiomatic afterEach(() => t?.close()) pattern in the smoke test prevents connection leaks properly.

Verdict

Approve with the dedupe-index observation. The redundant CREATE UNIQUE INDEX in telemetry.test.ts:419-426 isn't a correctness issue (IF NOT EXISTS saves it), but it's the kind of thing that will confuse the next developer migrating another test file. Worth a one-line cleanup or a clarifying comment before merge.

…ument cast

claude-pr review on #3498: the 'duplicate packet dedupe' block in
telemetry.test.ts still manually created telemetry_source_packet_type_uniq,
now dead DDL since createTestDb() applies migration 032. Removed it (test still
passes, confirming the index comes from the migration). Also documented the
as-any cast in testDb.ts per the review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown

Claude finished @Yeraze's task —— View job


PR Review: refactor(test): shared createTestDb helper from migration registry

Tasks:

  • Gather context and understand the PR
  • Read changed files
  • Analyze code quality, bugs, performance, security
  • Post review feedback

Summary

This is a well-structured, correctly motivated refactor. The previous review comment on the redundant dedupe-index beforeEach has been addressed — the block comment at telemetry.test.ts:419–422 now clearly explains that the index comes from the migration registry, and the beforeEach DDL is gone. The implementation is solid. A few observations below:


Positive highlights

  • Root cause fix: Using real migrations against :memory: is definitively correct — the schema drift that caused feat(map): position-history markers at every fix, points-only mode, hover tooltip (#3492) #3495 is structurally impossible with this approach.
  • The helper is minimal and correct. The getSetting/setSetting try/catch shims are the right defensive choice for the bootstrapping window, and the settingsKey guard-write at line 63 faithfully replicates production semantics.
  • The smoke test covers the right things: table presence, a recently-added column (migration 089 rxSnr), and an end-to-end repository round-trip. Checking rxSnr specifically means future column-add migrations are immediately validated.
  • The three migrated test files are clean and reduced. The comment pattern (// Full production schema from the migration registry — see testDb.ts) consistently points readers to the helper.
  • The previous concern is addressed. The dedupe beforeEach is gone; the describe block now carries a clear comment that createTestDb() already provides the migration 032 index.

Minor: telemetry.extra.test.ts imports BetterSQLite3Database type but the variable is only typed at declaration

src/db/repositories/telemetry.extra.test.ts:15

import { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3';

This is used at line 35 (let drizzleDb: BetterSQLite3Database<typeof schema>), so it's not dead — this is fine as-is. Just confirming it's intentional.


Observation: testDb.test.ts:30–32 — smoke test inserts into nodes without a sources row

t.sqlite.prepare(
  "INSERT INTO nodes (nodeNum, nodeId, sourceId, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?)",
).run(0xaabbccdd, '!aabbccdd', 'default', Date.now(), Date.now());

This works because the baseline migration (001) seeds a 'default' row into sources, so the FK is satisfied. It's correct — just worth noting that the test relies on that seed behavior. If you ever add an assertion like expect(tables).toContain('sources') to the first smoke test, it would make this dependency explicit and serve as a canary if the seed is ever removed.

Not a blocker, just a nice-to-have for future-proofing.


Nit: migration count comment in CLAUDE.md is stale

The CLAUDE.md says **Current migration count:** 84 (latest: \084_add_auto_favorite_targets`)` but this branch adds nothing new there, and the repo already has migrations up to 089. This is pre-existing and out of scope for this PR — noting it only so it gets updated in a future pass.


Verdict

Approve. The previous concern (redundant dedupe DDL) is cleanly resolved. The implementation is correct, the scope is appropriately narrow (part 1 as described), and the smoke test gives good confidence. The minor items above are all non-blocking observations.

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