Files
TREK/server/tests/unit/db/seeds-admin.test.ts
T
Maurice 9de9719a02 test(server): seed the share, quota, photo and stats specs through the factories
Converts the raw prepare() fixtures and reads in the share and place shadow
e2e suites, the google quota, travel stats and MCP guard services, the
memories MCP spec, the admin seeder spec and the assignment participants
repository spec to the ORM factories and readers.

The place shadow e2e moves from hand-rolled tables to the migrated snapshot
with its users pinned to the ids their session cookies are signed for. The
share e2e drops the raw canAccessTrip it still put on the database mock:
nothing has read it since trip access moved onto the repositories. The
quota spec computes yesterday and the retention window in UTC, the same
calendar day SQLite's date('now') gives. The participants oracle keeps its
raw statement behind an allow marker.
2026-10-08 18:46:47 +02:00

113 lines
4.2 KiB
TypeScript

/**
* First-run admin seeding — the live MikroORM `AdminSeeder` (`db/seeders/AdminSeeder.ts`),
* not the retired `db/seeds.ts::seedAdminAccount` this file used to test (that function
* has no production caller any more — `db/orm.ts`'s own comment says it "replaces
* createTables() → runMigrations() → runSeeds()"). Same behavior, same env reads
* (`readEnv().adminBootstrap`), redirected to the seeder that actually runs at boot.
*
* Covers the #1339 fix: ADMIN_EMAIL/ADMIN_PASSWORD only take effect on first run
* (empty database). Setting them once a user exists must no longer be silent — it
* has to warn — and a partial config (only one of the two) must warn too instead
* of quietly falling back to a generated password.
*/
import { AdminSeeder } from '../../../src/db/seeders/AdminSeeder';
import { createSnapshotTestDb } from '../../helpers/db-mock';
import { createTestOrm, type TestOrm } from '../../helpers/test-orm';
import { countRows, findRow } from '../../helpers/factories/rows';
import { makeAdmin } from '../../helpers/factories/users';
import { Users } from '../../../src/db/entities/Users.entity';
import type Database from 'better-sqlite3';
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
const ENV_KEYS = ['ADMIN_EMAIL', 'ADMIN_PASSWORD', 'DEMO_MODE', 'OIDC_ONLY', 'OIDC_ISSUER', 'OIDC_CLIENT_ID'];
function countUsers(orm: TestOrm): Promise<number> {
return countRows(orm, Users);
}
async function insertExistingUser(orm: TestOrm): Promise<void> {
await makeAdmin(orm, { username: 'admin', email: 'admin@trek.local' });
}
function userByEmail(orm: TestOrm, email: string) {
return findRow(orm, Users, { email });
}
describe('AdminSeeder — first-run admin', () => {
let db: Database.Database;
let t: TestOrm;
let saved: Record<string, string | undefined>;
beforeEach(async () => {
db = createSnapshotTestDb();
t = await createTestOrm(db);
saved = {};
for (const k of ENV_KEYS) {
saved[k] = process.env[k];
delete process.env[k];
}
});
afterEach(async () => {
await t.close();
db.close();
for (const k of ENV_KEYS) {
if (saved[k] === undefined) delete process.env[k];
else process.env[k] = saved[k];
}
vi.restoreAllMocks();
});
it('creates the admin from ADMIN_EMAIL/ADMIN_PASSWORD on an empty database', async () => {
process.env.ADMIN_EMAIL = 'me@example.com';
process.env.ADMIN_PASSWORD = 'S3cret-pw';
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
await new AdminSeeder().run(t.em);
const user = await userByEmail(t, 'me@example.com');
expect(user).not.toBeNull();
expect(user!.role).toBe('admin');
expect(user!.must_change_password).toBe(1);
expect(warn).not.toHaveBeenCalled();
});
it('warns and creates nothing when ADMIN_* is set but a user already exists', async () => {
await insertExistingUser(t);
process.env.ADMIN_EMAIL = 'new@example.com';
process.env.ADMIN_PASSWORD = 'whatever';
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
await new AdminSeeder().run(t.em);
expect(await countUsers(t)).toBe(1);
expect(await userByEmail(t, 'new@example.com')).toBeNull();
const msg = warn.mock.calls.map((c) => c.join(' ')).join('\n');
expect(msg).toContain('only apply on first run');
});
it('stays silent when no admin env is set and a user already exists', async () => {
await insertExistingUser(t);
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
await new AdminSeeder().run(t.em);
expect(await countUsers(t)).toBe(1);
expect(warn).not.toHaveBeenCalled();
});
it('warns about a partial config and falls back to a generated password', async () => {
process.env.ADMIN_EMAIL = 'me@example.com'; // ADMIN_PASSWORD intentionally missing
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
await new AdminSeeder().run(t.em);
// Falls back to the default local admin, NOT the provided email.
expect(await userByEmail(t, 'admin@trek.local')).not.toBeNull();
expect(await userByEmail(t, 'me@example.com')).toBeNull();
const msg = warn.mock.calls.map((c) => c.join(' ')).join('\n');
expect(msg).toContain('Only one of ADMIN_EMAIL/ADMIN_PASSWORD');
});
});