Skip to content

test(migrations): guard against source-dependent migration crashes (#3657)#3661

Merged
Yeraze merged 1 commit into
mainfrom
test/migration-source-present-guard
Jun 23, 2026
Merged

test(migrations): guard against source-dependent migration crashes (#3657)#3661
Yeraze merged 1 commit into
mainfrom
test/migration-source-present-guard

Conversation

@Yeraze

@Yeraze Yeraze commented Jun 23, 2026

Copy link
Copy Markdown
Owner

Summary

Post-incident regression guard for #3657 (the v4.11.4 boot crash).

The blind spot: the migration suite only ever ran the full registry against a DB with zero sources (createTestDb). Many migrations do source-dependent work behind if (sources.length > 0) — that whole branch was never exercised by unit CI, which is exactly how #3657 shipped: migration 033's UPDATE channel_database SET sourceId … only runs when a source exists, and it crashed once #3640 stopped migration 021 from adding that column. The bug only surfaced on the hardware system-test gate (which had a source configured) — and that gate was itself unusable because the test node had bricked itself on the bad release.

What this adds

A test that replays the full SQLite migration registry and registers a source the moment the sources table exists (migration 020), so every subsequent source-dependent migration runs its populated-source path. Failures are labeled by migration number/name.

Verification

A future "033-style" missing-column-with-a-source regression now fails in plain unit CI instead of slipping to the hardware gate.

🤖 Generated with Claude Code

…3657)

Post-incident regression guard. The migration suite only ever ran the full
registry against a DB with ZERO sources, so the `if (sources.length > 0)`
branch of every source-dependent migration went unexercised — exactly how #3657
shipped (migration 033's channel_database backfill crashed only when a source
was present and the legacy sourceId column was absent).

This replays the full SQLite migration registry and registers a source the
moment the `sources` table exists (migration 020), so every later
source-dependent migration runs its populated-source path. Verified to fail
("Migration 33 … no such column: sourceId") when 033's guard is removed, so a
future 033-style regression is now caught in plain unit CI rather than only on
the hardware system-test gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011JEaCGwY9Wz8jeV4e22GW4
@github-actions

github-actions Bot commented Jun 23, 2026

Copy link
Copy Markdown

Claude finished @Yeraze's task —— View job


PR Review: test(migrations): guard against source-dependent migration crashes

  • Gather context and read changed files
  • Review test file quality and correctness
  • Post final review

Overview

This is a well-motivated regression guard. The incident description in both the PR body and the file header clearly explains the blind spot: the existing migration test always ran against a zero-source DB, so the if (sources.length > 0) branches in source-dependent migrations (like 033's channel_database backfill) were never exercised by unit CI. This test closes that gap.

Code Quality

Strengths:

  • The header comment (lines 1–17) is excellent — it explains why the test exists, the exact failure mode, and references the incident issues.
  • The error message in the catch block (Migration ${m.number} (${m.name}) threw with a source registered) matches what the PR description says the regression test produces, making it easy to diagnose future failures.
  • The sanity assertions at the bottom (lines 75–77) are a nice touch — they guard against the test silently proving nothing if migration 020 ever moves or the table name changes.

Potential issues:

  1. Hard-coded column list in the INSERT may go stale (line 64–67). The test inserts a source with (id, name, type, config, enabled, createdAt, updatedAt). Migration 081 later adds displayOrder with DEFAULT 0, so this specific INSERT is safe today. But future migrations that add NOT NULL columns without defaults to the sources table after migration 020 could cause the pre-081 INSERT to fail before the displayOrder column is even added. This is a low-probability risk but a real one — consider noting it as a known fragility in the comment, or inserting after migration 020 completes and before the next migration runs (which the current code already does — just worth noting the dependency).

  2. registeredAtMigration is always the migration 020 number (line 76–77). The variable is set to m.number at the moment the source is inserted, which is the number of the migration where the sources table first appears (020). The assertion expect(registeredAtMigration).toBeLessThan(33) is correct and will catch it if 020 is ever renumbered above 033. However, the assertion expect(registeredAtMigration).toBeGreaterThan(0) is trivially true if we got past the first migration — consider tightening to expect(registeredAtMigration).toBe(20) to make the test self-documenting and catch accidental reordering.

  3. Only tests SQLite (line 28, new Database(':memory:')). This is noted implicitly by the if (!m.sqlite) continue guard. PostgreSQL/MySQL equivalents of 033's bug are not covered. This is an acceptable scope for the PR given the stated goal, but worth flagging as a known gap.

  4. sqlite.close() is not in a finally block (line 79). If an earlier assertion throws (e.g. expect(sourceRegistered).toBe(true) at line 75), the DB handle won't be closed. For an in-memory DB this is harmless, but the pattern is slightly inconsistent with good resource management. A try/finally would be cleaner.

Minor Nits

  • Line 51: m.sqlite(sqlite as any, getSetting, setSetting) — the as any cast works but is worth noting that the getSetting/setSetting helpers exactly mirror what the real migration runner passes, which is good.
  • The registeredAtMigration variable is assigned but only used in assertions — could use const if extracted, but the current pattern is clear enough.

Verdict

Approve with suggestions. The core approach is sound and catches the exact regression class described. The two most actionable suggestions are:

  1. Tighten expect(registeredAtMigration).toBeGreaterThan(0)expect(registeredAtMigration).toBe(20) for self-documentation.
  2. Wrap sqlite.close() in a try/finally.

Neither is a blocker — this test is already a meaningful improvement over the prior zero-source-only coverage.

@Yeraze Yeraze merged commit 617ddda into main Jun 23, 2026
19 checks passed
@Yeraze Yeraze deleted the test/migration-source-present-guard branch June 23, 2026 01:51
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