test(server): seed the atlas repository and storage admin specs through the factories

The visited countries and place regions repository specs seed their rows
through the ORM; the legacy statements they hold the repositories to stay
raw behind an allow marker, since those are the oracles the parity is
checked against.

The storage admin e2e moves from hand-rolled tables to the migrated
snapshot, seeds its two users through the factories with the ids their
session cookies are signed for, and reads the stored backends and the audit
rows through the readers instead of raw SELECTs.
This commit is contained in:
Maurice
2026-10-08 15:44:39 +02:00
parent 9de9719a02
commit 7349ee95c2
3 changed files with 70 additions and 68 deletions
+35 -43
View File
@@ -1,7 +1,7 @@
/**
* Storage admin e2e — exercises the migrated /api/admin/storage surface
* (StorageAdminController) through the real JwtAuthGuard + AdminGuard +
* ManagedGuard against a temp SQLite db. DI-native: no service mock, so the
* ManagedGuard against a migrated temp SQLite db. DI-native: no service mock, so the
* registry's real boot/seed/reload pipeline and StorageAdminService run for
* real. Covers auth (401), the admin gate (403), managed-mode refusal (403,
* the first e2e to assert it — ManagedGuard is otherwise only wired in
@@ -20,29 +20,12 @@ import { APP_GUARD } from '@nestjs/core';
import { StorageModule } from '../../src/nest/storage/storage.module';
import { ManagedGuard } from '../../src/nest/common/managed.guard';
import { Test } from '@nestjs/testing';
import { seedUser, sessionCookie } from './harness';
import { sessionCookie } from './harness';
const { db } = vi.hoisted(() => {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const Database = require('better-sqlite3');
const tmp = new Database(':memory:');
tmp.exec('PRAGMA journal_mode = WAL');
// `users` carries the columns listUsers/createUser/updateUser select, plus the
// is_guest flag the #1362 COALESCE guards read (admin.e2e.test.ts DDL).
tmp.exec(`CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL,
email TEXT NOT NULL UNIQUE, role TEXT NOT NULL DEFAULT 'user', password_version INTEGER NOT NULL DEFAULT 0,
password_hash TEXT, avatar TEXT, is_guest INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
last_login DATETIME);`);
tmp.exec('CREATE TABLE app_settings (key TEXT PRIMARY KEY, value TEXT);');
// Slim audit_log mirror (no FKs), same shape as admin.e2e.test.ts.
tmp.exec(`CREATE TABLE audit_log (
id INTEGER PRIMARY KEY AUTOINCREMENT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
user_id INTEGER, action TEXT NOT NULL, resource TEXT, details TEXT, ip TEXT);`);
return { db: tmp };
vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/db/database', () => ({ db, closeDb: () => {}, reinitialize: () => {} }));
// The audit domain is DI-native: writeAudit runs for real against the temp db's
// audit_log table; only the file logger is silenced.
vi.mock('../../src/nest/audit/audit-log.logger', () => ({ LOG_LEVEL: 'error', logInfo: vi.fn(), logDebug: vi.fn(), logError: vi.fn(), logWarn: vi.fn() }));
@@ -57,9 +40,16 @@ vi.mock('../../src/config', () => ({ ENCRYPTION_KEY: 'e2e-storage-key', JWT_SECR
import { TrekExceptionFilter } from '../../src/nest/common/trek-exception.filter';
import { ZodValidationPipe } from '../../src/nest/common/zod-validation.pipe';
import { TestUnitOfWorkModule } from '../helpers/test-uow';
import { createTestMikroOrmModule } from '../helpers/test-orm';
import { createTestMikroOrmModule, createTestOrm, type TestOrm } from '../helpers/test-orm';
import { db } from '../../src/db/database';
import { AppSettings } from '../../src/db/entities/AppSettings.entity';
import { AuditLog } from '../../src/db/entities/AuditLog.entity';
import { makeAdmin, makeUser } from '../helpers/factories/users';
import { countRows, findRow } from '../helpers/factories/rows';
describe('Storage admin e2e (real auth + admin guard + managed guard + temp SQLite)', () => {
let orm: TestOrm;
describe('Storage admin e2e (real auth + admin guard + managed guard + migrated temp SQLite)', () => {
let server: Server;
let app: Awaited<ReturnType<typeof build>>;
let adminCookie: string;
@@ -80,8 +70,10 @@ describe('Storage admin e2e (real auth + admin guard + managed guard + temp SQLi
beforeAll(async () => {
process.env.ENCRYPTION_KEY = 'e2e-storage-key';
seedUser(db as never, { id: 1 });
seedUser(db as never, { id: 2, role: 'admin', email: 'e2e-storage-admin@example.test' });
orm = await createTestOrm(db);
// Pinned ids: sessionCookie(1) and (2) sign for exactly these users.
await makeUser(orm, { id: 1, email: 'e2e@example.test' });
await makeAdmin(orm, { id: 2, email: 'e2e-storage-admin@example.test' });
userCookie = sessionCookie(1);
adminCookie = sessionCookie(2);
app = await build();
@@ -101,6 +93,7 @@ describe('Storage admin e2e (real auth + admin guard + managed guard + temp SQLi
afterAll(async () => {
await app.close();
await orm.close();
delete process.env.ENCRYPTION_KEY;
});
@@ -154,13 +147,13 @@ describe('Storage admin e2e (real auth + admin guard + managed guard + temp SQLi
expect(offBox.options.secretAccessKey).toBe('••••••••'); // masked, never echoed
expect(res.body.categories.backups).toEqual({ backend: 'nas-backups', source: 'settings' });
const row = db.prepare("SELECT value FROM app_settings WHERE key = 'storage.backends'").get() as { value: string };
expect(row.value).toContain('enc:v1:');
expect(row.value).not.toContain('sk-e2e');
const row = await findRow(orm, AppSettings, { key: 'storage.backends' });
expect(row?.value).toContain('enc:v1:');
expect(row?.value).not.toContain('sk-e2e');
const audit = db.prepare("SELECT details FROM audit_log WHERE action = 'admin.storage_update'").get() as { details: string };
expect(audit.details).toContain('***');
expect(audit.details).not.toContain('sk-e2e');
const audit = await findRow(orm, AuditLog, { action: 'admin.storage_update' });
expect(audit?.details).toContain('***');
expect(audit?.details).not.toContain('sk-e2e');
});
it('STORE2E-005 PUT with a semantic violation → 400 with the registry message verbatim', async () => {
@@ -204,9 +197,9 @@ describe('Storage admin e2e (real auth + admin guard + managed guard + temp SQLi
(b) => b.name === 'off-box',
)!;
expect(offBox.options.secretAccessKey).not.toBe('sk');
const row = db.prepare("SELECT value FROM app_settings WHERE key = 'storage.backends'").get() as { value: string };
expect(row.value).not.toContain('"sk"');
expect(row.value).toContain('enc:v1:');
const row = await findRow(orm, AppSettings, { key: 'storage.backends' });
expect(row?.value).not.toContain('"sk"');
expect(row?.value).toContain('enc:v1:');
} finally {
process.env.ENCRYPTION_KEY = 'e2e-storage-key';
}
@@ -234,8 +227,8 @@ describe('Storage admin e2e (real auth + admin guard + managed guard + temp SQLi
.send({ backend: { name: 'cand', type: 'local', options: { root } } });
expect(res.status).toBe(200);
expect(res.body).toEqual({ ok: true, targets: [{ name: 'cand', ok: true }] });
const audit = db.prepare("SELECT details FROM audit_log WHERE action = 'admin.storage_test'").get() as { details: string };
expect(JSON.parse(audit.details)).toMatchObject({ backend: 'cand', type: 'local', ok: true });
const audit = await findRow(orm, AuditLog, { action: 'admin.storage_test' });
expect(JSON.parse(String(audit?.details))).toMatchObject({ backend: 'cand', type: 'local', ok: true });
});
it('STORE2E-010 migration moves a category end to end', async () => {
@@ -267,8 +260,8 @@ describe('Storage admin e2e (real auth + admin guard + managed guard + temp SQLi
}
expect(status).toMatchObject({ status: 'done' });
expect(stateBody!.categories.journey).toEqual({ backend: 'dest', source: 'settings' });
const audit = db.prepare("SELECT details FROM audit_log WHERE action = 'admin.storage_migration'").get() as { details: string };
expect(JSON.parse(audit.details)).toEqual({ category: 'journey', to: 'dest' });
const audit = await findRow(orm, AuditLog, { action: 'admin.storage_migration' });
expect(JSON.parse(String(audit?.details))).toEqual({ category: 'journey', to: 'dest' });
});
it('STORE2E-011 backfill guards: 401 anon, 404 non-mirror, 409 while running is covered by unit — here the 404', async () => {
@@ -314,8 +307,8 @@ describe('Storage admin e2e (real auth + admin guard + managed guard + temp SQLi
}
expect(status).toMatchObject({ status: 'done' });
expect(fs.existsSync(path.join(nasRoot, 'pre-mirror.zip'))).toBe(true);
const audit = db.prepare("SELECT details FROM audit_log WHERE action = 'admin.storage_backfill'").get() as { details: string };
expect(JSON.parse(audit.details)).toMatchObject({ backend: 'm' });
const audit = await findRow(orm, AuditLog, { action: 'admin.storage_backfill' });
expect(JSON.parse(String(audit?.details))).toMatchObject({ backend: 'm' });
});
it('STORE2E-013 cancel 404s with no active run; stats refresh returns real numbers and audits', async () => {
@@ -324,8 +317,7 @@ describe('Storage admin e2e (real auth + admin guard + managed guard + temp SQLi
expect(res.status).toBe(200);
expect(res.body.computedAt).toBeGreaterThan(0);
expect(res.body.categories.backups.objects).toBeGreaterThanOrEqual(1); // pre-mirror.zip at least
const audit = db.prepare("SELECT COUNT(*) AS n FROM audit_log WHERE action = 'admin.storage_stats_refresh'").get() as { n: number };
expect(audit.n).toBe(1);
expect(await countRows(orm, AuditLog, { action: 'admin.storage_stats_refresh' })).toBe(1);
});
it('STORE2E-014 regression (audit #7): a migration flip while the admin form is open makes the stale save 409, and the flip survives', async () => {
@@ -10,6 +10,7 @@ import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
import { createSnapshotTestDb } from '../../../helpers/db-mock';
import { resetTestDb } from '../../../helpers/test-db';
import { createTestOrm, type TestOrm } from '../../../helpers/test-orm';
import { insertRow } from '../../../helpers/factories/rows';
import { createPlace, createTrip, createUser, addTripMember } from '../../../helpers/factories';
import { PlaceRegions } from '../../../../src/db/entities/PlaceRegions.entity';
import type { PlaceRegionsRepository } from '../../../../src/db/repositories/PlaceRegions.repository';
@@ -25,9 +26,8 @@ beforeAll(async () => {
beforeEach(() => { resetTestDb(testDb); t.clear(); });
afterAll(async () => { await t.close(); testDb.close(); });
function insertPlaceRegion(placeId: number, countryCode: string, regionCode: string, regionName: string): void {
testDb.prepare('INSERT INTO place_regions (place_id, country_code, region_code, region_name) VALUES (?, ?, ?, ?)')
.run(placeId, countryCode, regionCode, regionName);
async function insertPlaceRegion(placeId: number, countryCode: string, regionCode: string, regionName: string): Promise<void> {
await insertRow(t, PlaceRegions, { place: placeId, country_code: countryCode, region_code: regionCode, region_name: regionName });
}
describe('PlaceRegionsRepository.listCountryCodesForPlaceIds (AT3)', () => {
@@ -37,9 +37,10 @@ describe('PlaceRegionsRepository.listCountryCodesForPlaceIds (AT3)', () => {
const p1 = createPlace(testDb, trip.id, { name: 'Eiffel Tower' });
const p2 = createPlace(testDb, trip.id, { name: 'Louvre' });
const p3 = createPlace(testDb, trip.id, { name: 'Not geocoded' });
insertPlaceRegion(p1.id, 'FR', 'FR-IDF', 'Île-de-France');
insertPlaceRegion(p2.id, 'FR', 'FR-IDF', 'Île-de-France');
await insertPlaceRegion(p1.id, 'FR', 'FR-IDF', 'Île-de-France');
await insertPlaceRegion(p2.id, 'FR', 'FR-IDF', 'Île-de-France');
// test-sql-allow: the raw statement is the legacy oracle this parity test holds the repository to.
const legacy = testDb.prepare(`SELECT place_id, country_code FROM place_regions WHERE place_id IN (${[p1.id, p2.id, p3.id].join(',')})`).all();
const typed = await repo.listCountryCodesForPlaceIds([p1.id, p2.id, p3.id]);
expect(typed).toEqual(legacy);
@@ -57,9 +58,10 @@ describe('PlaceRegionsRepository.listForPlaceIds (AT28)', () => {
const trip = createTrip(testDb, user.id);
const p1 = createPlace(testDb, trip.id);
const p2 = createPlace(testDb, trip.id);
insertPlaceRegion(p1.id, 'US', 'US-NY', 'New York');
insertPlaceRegion(p2.id, 'US', 'US-CA', 'California');
await insertPlaceRegion(p1.id, 'US', 'US-NY', 'New York');
await insertPlaceRegion(p2.id, 'US', 'US-CA', 'California');
// test-sql-allow: the raw statement is the legacy oracle this parity test holds the repository to.
const legacy = testDb.prepare(`SELECT * FROM place_regions WHERE place_id IN (${[p1.id, p2.id].join(',')})`).all();
const typed = await repo.listForPlaceIds([p1.id, p2.id]);
expect(typed).toEqual(legacy);
@@ -77,10 +79,11 @@ describe('PlaceRegionsRepository.listDistinctRegionCodesForCountryAndPlaces (AT2
const p1 = createPlace(testDb, trip.id);
const p2 = createPlace(testDb, trip.id);
const p3 = createPlace(testDb, trip.id);
insertPlaceRegion(p1.id, 'FR', 'FR-IDF', 'Île-de-France');
insertPlaceRegion(p2.id, 'FR', 'FR-IDF', 'Île-de-France'); // same region, dedup target
insertPlaceRegion(p3.id, 'DE', 'DE-BY', 'Bavaria'); // different country, excluded
await insertPlaceRegion(p1.id, 'FR', 'FR-IDF', 'Île-de-France');
await insertPlaceRegion(p2.id, 'FR', 'FR-IDF', 'Île-de-France'); // same region, dedup target
await insertPlaceRegion(p3.id, 'DE', 'DE-BY', 'Bavaria'); // different country, excluded
// test-sql-allow: the raw statement is the legacy oracle this parity test holds the repository to.
const legacy = testDb.prepare(
`SELECT DISTINCT region_code FROM place_regions WHERE country_code = ? AND place_id IN (${[p1.id, p2.id, p3.id].join(',')})`,
).all('FR');
@@ -103,14 +106,15 @@ describe('PlaceRegionsRepository.countPlacesByCountryForTrip (AT40)', () => {
const fr2 = createPlace(testDb, trip.id);
const de1 = createPlace(testDb, trip.id);
const noRegion = createPlace(testDb, trip.id);
insertPlaceRegion(fr1.id, 'FR', 'FR-IDF', 'Île-de-France');
insertPlaceRegion(fr2.id, 'FR', 'FR-PAC', 'Provence');
insertPlaceRegion(de1.id, 'DE', 'DE-BY', 'Bavaria');
await insertPlaceRegion(fr1.id, 'FR', 'FR-IDF', 'Île-de-France');
await insertPlaceRegion(fr2.id, 'FR', 'FR-PAC', 'Provence');
await insertPlaceRegion(de1.id, 'DE', 'DE-BY', 'Bavaria');
// A different trip's place must not leak in.
const foreign = createPlace(testDb, other.id);
insertPlaceRegion(foreign.id, 'FR', 'FR-IDF', 'Île-de-France');
await insertPlaceRegion(foreign.id, 'FR', 'FR-IDF', 'Île-de-France');
void noRegion;
// test-sql-allow: the raw statement is the legacy oracle this parity test holds the repository to.
const legacy = testDb.prepare(`
SELECT pr.country_code, COUNT(DISTINCT p.id) AS places
FROM place_regions pr JOIN places p ON p.id = pr.place_id
@@ -138,13 +142,14 @@ describe('PlaceRegionsRepository.listVisitedCountryCodesForUser (AT44)', () => {
addTripMember(testDb, started.id, member.id);
const startedPlace = createPlace(testDb, started.id);
insertPlaceRegion(startedPlace.id, 'FR', 'FR-IDF', 'Île-de-France');
await insertPlaceRegion(startedPlace.id, 'FR', 'FR-IDF', 'Île-de-France');
const startedPlace2 = createPlace(testDb, started.id);
insertPlaceRegion(startedPlace2.id, 'FR', 'FR-PAC', 'Provence'); // same country, dedup target
await insertPlaceRegion(startedPlace2.id, 'FR', 'FR-PAC', 'Provence'); // same country, dedup target
const notStartedPlace = createPlace(testDb, notStarted.id);
insertPlaceRegion(notStartedPlace.id, 'DE', 'DE-BY', 'Bavaria'); // not started, excluded
await insertPlaceRegion(notStartedPlace.id, 'DE', 'DE-BY', 'Bavaria'); // not started, excluded
const today = '2026-01-01';
// test-sql-allow: the raw statement is the legacy oracle this parity test holds the repository to.
const legacy = testDb.prepare(`
SELECT DISTINCT pr.country_code
FROM place_regions pr
@@ -160,6 +165,7 @@ describe('PlaceRegionsRepository.listVisitedCountryCodesForUser (AT44)', () => {
expect(typedOwner).toEqual(legacy.map((r: unknown) => (r as { country_code: string }).country_code));
expect(typedOwner).toEqual(['FR']);
// test-sql-allow: the raw statement is the legacy oracle this parity test holds the repository to.
const legacyMember = testDb.prepare(`
SELECT DISTINCT pr.country_code
FROM place_regions pr
@@ -10,6 +10,7 @@ import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
import { createSnapshotTestDb } from '../../../helpers/db-mock';
import { resetTestDb } from '../../../helpers/test-db';
import { createTestOrm, type TestOrm } from '../../../helpers/test-orm';
import { insertRows } from '../../../helpers/factories/rows';
import { createUser } from '../../../helpers/factories';
import { VisitedCountries } from '../../../../src/db/entities/VisitedCountries.entity';
import type { VisitedCountriesRepository } from '../../../../src/db/repositories/VisitedCountries.repository';
@@ -29,13 +30,13 @@ describe('VisitedCountriesRepository.listForUser (AT10)', () => {
it('VISCOUNTRYREPO-001: matches SELECT country_code, created_at, source FROM visited_countries WHERE user_id = ? ORDER BY created_at DESC run raw, scoped to the user', async () => {
const { user } = createUser(testDb);
const { user: other } = createUser(testDb, { username: 'other' });
testDb.prepare('INSERT INTO visited_countries (user_id, country_code, source, created_at) VALUES (?, ?, ?, ?)')
.run(user.id, 'DE', 'manual', '2026-01-01T00:00:00.000Z');
testDb.prepare('INSERT INTO visited_countries (user_id, country_code, source, created_at) VALUES (?, ?, ?, ?)')
.run(user.id, 'FR', 'dawarich', '2026-02-01T00:00:00.000Z');
testDb.prepare('INSERT INTO visited_countries (user_id, country_code, source, created_at) VALUES (?, ?, ?, ?)')
.run(other.id, 'JP', 'manual', '2026-03-01T00:00:00.000Z');
await insertRows(t, VisitedCountries, [
{ user: user.id, country_code: 'DE', source: 'manual', created_at: '2026-01-01T00:00:00.000Z' },
{ user: user.id, country_code: 'FR', source: 'dawarich', created_at: '2026-02-01T00:00:00.000Z' },
{ user: other.id, country_code: 'JP', source: 'manual', created_at: '2026-03-01T00:00:00.000Z' },
]);
// test-sql-allow: the raw statement is the legacy oracle this parity test holds the repository to.
const legacy = testDb.prepare('SELECT country_code, created_at, source FROM visited_countries WHERE user_id = ? ORDER BY created_at DESC').all(user.id);
const rows = await repo.listForUser(user.id);
@@ -52,9 +53,12 @@ describe('VisitedCountriesRepository.listForUser (AT10)', () => {
describe('VisitedCountriesRepository.listCodesForUser (AT5/AT7/AT43)', () => {
it('VISCOUNTRYREPO-003: matches SELECT country_code FROM visited_countries WHERE user_id = ? run raw', async () => {
const { user } = createUser(testDb);
testDb.prepare('INSERT INTO visited_countries (user_id, country_code, source) VALUES (?, ?, ?)').run(user.id, 'DE', 'manual');
testDb.prepare('INSERT INTO visited_countries (user_id, country_code, source) VALUES (?, ?, ?)').run(user.id, 'FR', 'manual');
await insertRows(t, VisitedCountries, [
{ user: user.id, country_code: 'DE', source: 'manual' },
{ user: user.id, country_code: 'FR', source: 'manual' },
]);
// test-sql-allow: the raw statement is the legacy oracle this parity test holds the repository to.
const legacy = testDb.prepare('SELECT country_code FROM visited_countries WHERE user_id = ?').all(user.id);
const codes = await repo.listCodesForUser(user.id);