Files
TREK/server/tests/integration/legacy-upgrade-v244.test.ts
T
Maurice f527b933fb test(db): boot a v4.3.3 install with real rows on this release
The legacy-upgrade suites stopped at legacy-v242, while v4.3.3, the
release every 4.x install will come from, left schema_version 244. The
most common upgrade path only ever ran against synthetic step maps.

legacy-v244.sql is that release's own runner booted once on an empty
file, plus synthetic rows across the main tables: users (one with a
mixed-case email), trips with a member, days, places, assignments with
the canary times, a booked stay, a flight with endpoints, budget items
with splits, packing, todos, notes, file metadata, tags, a journey with
an entry and a notification. scripts/build-legacy-fixture-v244.mjs
rebuilds it byte for byte from the tag: it archives v4.3.3 into a temp
dir outside the repo, boots that runner, seeds, pins every timestamp,
VACUUMs and dumps.

The suite runs the shared upgrade assertions and checks that every row
of those tables comes through with its values, that the boot copied the
database first and the copy still is the 4.3.3 schema, and that the
reminder backfill marks only the reminders whose day has passed.
2026-10-08 13:02:57 +02:00

133 lines
5.1 KiB
TypeScript

/**
* An install of the v4.3.3 release (the last one shipped with the positional
* runner, schema_version 244) boots on this release: the most common upgrade
* there is. Its rows span the main tables, so beyond the shared assertions in
* tests/helpers/legacy-upgrade-suite.ts this checks that the data comes through
* and that the boot copied the database before it migrated it.
*/
import { db as legacyDb } from '../../src/db/database';
import { openLegacyFixture } from '../helpers/legacy-fixture';
import { describeLegacyUpgrade } from '../helpers/legacy-upgrade-suite';
import Database from 'better-sqlite3';
import fs from 'node:fs';
import path from 'node:path';
import { afterAll, beforeAll, expect, it, vi } from 'vitest';
vi.mock('../../src/db/database', async () => {
const { buildDbMock } = await import('../helpers/db-mock');
const { openLegacyFixture } = await import('../helpers/legacy-fixture');
return buildDbMock(openLegacyFixture('legacy-v244'));
});
vi.mock('../../src/config', () => ({
JWT_SECRET: 'test-jwt-secret-for-trek-testing-only',
ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2',
updateJwtSecret: () => {},
SESSION_DURATION: '24h',
SESSION_DURATION_MS: 86400000,
SESSION_DURATION_SECONDS: 86400,
DEFAULT_LANGUAGE: 'en',
}));
vi.mock('../../src/websocket', () => ({
broadcast: vi.fn(),
broadcastToUser: vi.fn(),
getOnlineUserIds: vi.fn(() => []),
}));
// Off by default under NODE_ENV=test; this suite is the one that asks for it.
const previousSwitch = process.env.TREK_DB_PRE_MIGRATE_SNAPSHOT;
beforeAll(() => {
process.env.TREK_DB_PRE_MIGRATE_SNAPSHOT = 'true';
});
afterAll(() => {
if (previousSwitch === undefined) delete process.env.TREK_DB_PRE_MIGRATE_SNAPSHOT;
else process.env.TREK_DB_PRE_MIGRATE_SNAPSHOT = previousSwitch;
});
/** The user data the fixture carries, table by table. */
const DATA_TABLES = [
'users',
'settings',
'trips',
'trip_members',
'days',
'places',
'day_accommodations',
'day_assignments',
'day_notes',
'reservations',
'reservation_endpoints',
'budget_items',
'budget_item_members',
'packing_bags',
'packing_items',
'todo_items',
'collab_notes',
'trip_files',
'tags',
'place_tags',
'journeys',
'journey_trips',
'journey_contributors',
'journey_entries',
'notifications',
];
function rows(db: Database.Database, table: string, columns: string[]): unknown[] {
const list = columns.map((column) => `"${column}"`).join(', ');
return db.prepare(`SELECT ${list} FROM "${table}" ORDER BY rowid`).all();
}
describeLegacyUpgrade(
{ fixture: 'legacy-v244', version: 244, reseated: 0, nightOrderIndex: 3, id: 'LEGACYUP-244' },
legacyDb,
({ logged }) => {
it('LEGACYUP-244-101: every row of the main tables comes through with the values it had', () => {
const pristine = openLegacyFixture('legacy-v244');
try {
for (const table of DATA_TABLES) {
const columns = (
pristine.prepare('SELECT name FROM pragma_table_info(?)').all(table) as Array<{ name: string }>
).map((column) => column.name);
const before = rows(pristine, table, columns);
expect(before.length, `${table} carries rows in the fixture`).toBeGreaterThan(0);
expect(rows(legacyDb, table, columns), table).toEqual(before);
}
} finally {
pristine.close();
}
});
it('LEGACYUP-244-102: the boot copied the v4.3.3 database before migrating it, and the copy is the old schema', () => {
const line = logged.find((entry) => entry.startsWith('[DB] Copy of the database before migrating: '));
expect(line).toBeDefined();
const snapshot = line!.slice('[DB] Copy of the database before migrating: '.length);
expect(path.dirname(snapshot)).toBe(path.dirname(legacyDb.name));
expect(path.basename(snapshot)).toMatch(/^pre-migrate-legacy-244-\d{8}T\d{6}Z\.db$/);
expect(fs.existsSync(snapshot)).toBe(true);
const copy = new Database(snapshot, { readonly: true });
try {
expect(copy.prepare('SELECT version FROM schema_version').all()).toEqual([{ version: 244 }]);
expect(copy.prepare('SELECT COUNT(*) AS c FROM mikro_orm_migrations').get()).toEqual({ c: 0 });
expect(copy.prepare('SELECT COUNT(*) AS c FROM users').get()).toEqual({ c: 3 });
const tripColumns = (
copy.prepare('SELECT name FROM pragma_table_info(?)').all('trips') as Array<{ name: string }>
).map((column) => column.name);
expect(tripColumns).not.toContain('reminder_sent_for');
} finally {
copy.close();
}
});
it('LEGACYUP-244-103: a reminder whose day already passed is marked sent, so the upgrade does not repeat it', () => {
// Trip 1 (2027-05-10, three days ahead) is upcoming until its reminder day passes.
const today = new Date().toISOString().slice(0, 10);
expect(legacyDb.prepare('SELECT id, reminder_sent_for FROM trips ORDER BY id').all()).toEqual([
{ id: 1, reminder_sent_for: '2027-05-07' < today ? '2027-05-10' : null },
{ id: 2, reminder_sent_for: '2026-03-01' },
]);
});
},
);