krona-staff/tests/e2e/harness/context.ts
grok cf7f6f4510 test(e2e): стенд с реальным сервером и PostgreSQL 17
Зачем: 156 зелёных тестов на fakeDatabase не поймали, что ключевые
сценарии (создание сотрудника, подтверждение смены, расчёт зарплаты)
всегда падают на реальной PostgreSQL. Нужен приёмочный стенд, который
проверяет продукт как чёрный ящик: настоящий процесс сервера, настоящая
БД, роли как в pilot, но с минимальными привилегиями.

- tests/e2e/harness/global-setup.ts: создаёт БД и роли через
  scripts/test/provision-pg.mjs, применяет миграции ролью krona_migrator
  (NOSUPERUSER), поднимает `tsx src/server.ts` под krona_staff_app с
  токенами/VAPID из crypto.randomBytes; три процесса: с токенами, без
  токенов (как корневой compose.yaml) и отдельный для проверки rate limit;
  логи серверов пишутся в tests/e2e/evidence/.
- *.e2e.ts и отдельный vitest.e2e.config.ts: e2e не попадают в `npm test`,
  поэтому `npm run check` не требует БД и не краснеет от известных дефектов.
- [HARNESS]/[CTRL] — позитивные контроли (версия PG >= 17, атрибуты ролей,
  app-роль не видит schema_migrations, RBAC, уволенный теряет доступ):
  если они красные, красные дефектные сценарии ничего не доказывают.
- scripts/test/run-e2e.mjs: «храповик» известных дефектов по
  tests/e2e/known-defects.json (новый красный или исправленный-но-не-
  вычеркнутый ID = exit 1), таблица e2e-table.md и проверка, что в
  evidence нет ничего похожего на секреты стенда.

Логи названы *.txt, т.к. *.log в .gitignore.
2026-09-26 14:27:59 +07:00

41 lines
1.2 KiB
TypeScript

import { randomBytes } from 'node:crypto';
export interface E2EContext {
baseUrl: string;
openBaseUrl: string;
rateLimitBaseUrl: string;
managerToken: string;
terminalToken: string;
tenantId: string;
actorId: string;
terminalActorId: string;
appDatabaseUrl: string;
migrationDatabaseUrl: string;
}
declare module 'vitest' {
export interface ProvidedContext {
e2e: E2EContext;
}
}
const crockford = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
/** Случайный ULID (время + crypto.randomBytes), чтобы не зависеть от src/. */
export function randomUlid(): string {
let time = Date.now();
let timePart = '';
for (let i = 0; i < 10; i += 1) {
timePart = crockford[time % 32]! + timePart;
time = Math.floor(time / 32);
}
const bytes = randomBytes(16);
let randomPart = '';
for (let i = 0; i < 16; i += 1) randomPart += crockford[bytes[i]! % 32]!;
return timePart + randomPart;
}
/** Синтетическая метка для данных тестов: очевидно не реальные люди/суммы. */
export function synthetic(label: string): string {
return `E2E-${label}-${randomBytes(3).toString('hex')}`;
}