Files
gbrain/test/migrations-registry.test.ts
T
Garry TanandClaude Opus 4.7 de027ceb43 feat(migrations): TS registry replaces filesystem migration scan
Context: Codex flagged that bun build --compile produces a self-contained
binary, and the existing findMigrationsDir() in upgrade.ts:145 walks
skills/migrations/v*.md on disk — which fails on a compiled install
because the markdown files aren't bundled. The plan's fix is a TS
registry: migrations are code, imported directly, visible to both source
installs and compiled binaries.

- src/commands/migrations/types.ts: shared Migration, OrchestratorOpts,
  OrchestratorResult types.
- src/commands/migrations/index.ts: exports the migrations[] array,
  getMigration(version), and compareVersions() (semver comparator).
  The feature_pitch data that lived in the MD file frontmatter now
  lives here as a code constant on each Migration, so runPostUpgrade's
  post-upgrade pitch printer can consume it without a filesystem read.
- src/commands/migrations/v0_11_0.ts: stub orchestrator + pitch. The
  full phase implementation lands in Lane C-1; for now the stub throws
  a clear "not yet implemented" so apply-migrations --list (Lane A-4)
  can still enumerate the migration.

test/migrations-registry.test.ts: 9 tests covering ascending-semver
ordering, feature_pitch shape invariants, getMigration lookup, and
compareVersions edge cases (equal / newer / older / single-digit
across major bumps).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 13:14:20 +08:00

65 lines
2.1 KiB
TypeScript

/**
* Tests for the TS migration registry (src/commands/migrations/index.ts).
*
* The registry replaces filesystem discovery of skills/migrations/*.md so
* the compiled `gbrain` binary can enumerate migrations without a readdir.
*/
import { describe, test, expect } from 'bun:test';
import { migrations, getMigration, compareVersions } from '../src/commands/migrations/index.ts';
describe('migration registry', () => {
test('exports a non-empty migrations array', () => {
expect(migrations.length).toBeGreaterThan(0);
});
test('every migration has version + featurePitch.headline + orchestrator', () => {
for (const m of migrations) {
expect(typeof m.version).toBe('string');
expect(m.version).toMatch(/^\d+\.\d+\.\d+$/);
expect(typeof m.featurePitch.headline).toBe('string');
expect(m.featurePitch.headline.length).toBeGreaterThan(0);
expect(typeof m.orchestrator).toBe('function');
}
});
test('migrations are in ascending semver order', () => {
for (let i = 1; i < migrations.length; i++) {
expect(compareVersions(migrations[i].version, migrations[i - 1].version)).toBe(1);
}
});
test('v0.11.0 is present', () => {
const m = getMigration('0.11.0');
expect(m).not.toBeNull();
expect(m!.featurePitch.headline).toContain('Minions');
});
test('getMigration returns null for unknown versions', () => {
expect(getMigration('99.99.99')).toBeNull();
expect(getMigration('')).toBeNull();
});
});
describe('compareVersions', () => {
test('equal versions return 0', () => {
expect(compareVersions('1.2.3', '1.2.3')).toBe(0);
});
test('newer returns 1', () => {
expect(compareVersions('1.2.4', '1.2.3')).toBe(1);
expect(compareVersions('1.3.0', '1.2.9')).toBe(1);
expect(compareVersions('2.0.0', '1.99.99')).toBe(1);
});
test('older returns -1', () => {
expect(compareVersions('1.2.2', '1.2.3')).toBe(-1);
expect(compareVersions('0.11.0', '0.11.1')).toBe(-1);
expect(compareVersions('0.11.0', '0.12.0')).toBe(-1);
});
test('handles single-digit versions', () => {
expect(compareVersions('9.0.0', '10.0.0')).toBe(-1);
});
});