mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 21:46:49 +00:00
0aa5b36661
Backup, restore and the demo reset each handled the SQLite file themselves: backup.impl closed and reinitialized the connection, built MaintenanceRepository with new, and assumed the database was data/travel.db. That tied the whole chain to one local file and hid two bugs. They now go through DATABASE_BACKUP, a port declared in the shared database kernel (snapshot, verify, keep a copy before a restore, replace). SqliteDatabaseBackup implements it with what the code did before: VACUUM INTO for a consistent copy, and a close, copy, sidecar drop, rename and reopen through DatabaseLifecycle for a restore. BackupService injects it and hands it to backup.impl. The demo reset and the baseline save use the same port; the first baseline is saved by DemoResetJob once the app is up, because the boot-time seed runs before anything can be injected. Another engine can plug in a logical dump behind the port. Fixes on the way: - The backup and the restore ignored TREK_DB_FILE. The backup looked for data/travel.db and, with the database elsewhere, silently left it out of the archive; the restore wrote data/travel.db and never replaced the live database. Both now use DatabaseLifecycle.file, the path the connection runs on, and the pre-restore copy sits next to it. - A failed VACUUM INTO fell back to archiving the live WAL-mode file without a word, the torn copy the snapshot exists to prevent. The backup now fails: nothing is archived or committed, the existing backups are untouched, a partial snapshot is removed, and the log says why. A missing database file and a failed checkpoint are logged too.
67 lines
2.6 KiB
TypeScript
67 lines
2.6 KiB
TypeScript
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
import request from 'supertest';
|
|
import type { INestApplication } from '@nestjs/common';
|
|
import { buildApp } from '../../src/bootstrap';
|
|
import { DatabaseLifecycle } from '../../src/nest/database/database-lifecycle.service';
|
|
import { DATABASE_BACKUP, type DatabaseBackupStrategy } from '../../src/nest/database/database-backup.interface';
|
|
import { SqliteDatabaseBackup } from '../../src/nest/backup/sqlite-database-backup';
|
|
|
|
/**
|
|
* The core database's lifecycle, owned by the DatabaseLifecycle provider, on a
|
|
* real boot.
|
|
*
|
|
* The connection used to open as a side effect of importing db/database.ts and
|
|
* was closed and reopened through module functions the restore and the demo
|
|
* reset called directly. buildApp() now opens it through the provider, which
|
|
* also binds the ORM to later swaps; the backup port closes and reopens it
|
|
* through the same provider. Unmocked on purpose: under NODE_ENV=test the
|
|
* module opens a copy of the migrated schema snapshot, and every reopen opens a
|
|
* pristine copy again, which is all these cases need.
|
|
*/
|
|
describe('the database connection lifecycle under buildApp()', () => {
|
|
let app: INestApplication;
|
|
|
|
beforeAll(async () => {
|
|
app = await buildApp();
|
|
await app.init();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await app?.close();
|
|
});
|
|
|
|
it('LIFECYCLE-001: the boot opened the connection through the provider and the database answers', async () => {
|
|
expect(app.get(DatabaseLifecycle).file).toBe(':memory:');
|
|
|
|
const res = await request(app.getHttpServer()).get('/api/health/ready');
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(res.body).toEqual({ status: 'ready' });
|
|
});
|
|
|
|
it('LIFECYCLE-002: closed, the readiness probe answers 503; reopened, the ORM is bound to the new handle and it answers again', async () => {
|
|
const lifecycle = app.get(DatabaseLifecycle);
|
|
|
|
lifecycle.close();
|
|
try {
|
|
const down = await request(app.getHttpServer()).get('/api/health/ready');
|
|
expect(down.status).toBe(503);
|
|
expect(down.body).toEqual({ status: 'unavailable' });
|
|
} finally {
|
|
await lifecycle.reopen();
|
|
}
|
|
|
|
const up = await request(app.getHttpServer()).get('/api/health/ready');
|
|
expect(up.status).toBe(200);
|
|
});
|
|
|
|
it('LIFECYCLE-003: the backup port the container hands out is the SQLite one, on the file the connection runs on', () => {
|
|
const port = app.get<DatabaseBackupStrategy>(DATABASE_BACKUP);
|
|
|
|
expect(port).toBeInstanceOf(SqliteDatabaseBackup);
|
|
expect(port.location()).toBe(app.get(DatabaseLifecycle).file);
|
|
// The in-memory test database has no file to copy.
|
|
expect(port.canSnapshot()).toBe(false);
|
|
});
|
|
});
|