Зачем: аудит владельца (docs/audit/2026-09-26/AUDIT.md) стал основным реестром дефектов; без сопоставления исполнители и приёмка говорят о разных ID для одной ошибки (TASK-GROK-2 §2). Что: теги переименованы по таблице владельца ([K3/N2], [K13/SYNC-A], [S5/SYNC-C], [D6/OWN-01], [K17/OWN-10] и т. д.), ключи known-defects.json — так же; N9–N12, F7, F10 остаются одиночными, как в таблице. Сверх таблицы, по тексту AUDIT.md: K1/SYNC-I (анонимный доступ без токенов — это K1 на /sync), K1/PILOT-1 (подстановки без :? в пилотном compose прямо названы в K1), K2/PILOT-2 (NODE_ENV=development в пилоте — суть K2). SYNC-C0, SYNC-K, OWN-03..06/08/09, PILOT-3 ID владельца не имеют. run-e2e принимает «/» в теге. Прогон: 38 red / 9 green, храповик OK — те же сценарии, новые имена.
56 lines
2.7 KiB
TypeScript
56 lines
2.7 KiB
TypeScript
// Статическая проверка deploy/pilot/compose.yaml (аналог tests/unit/deployment.test.ts
|
||
// для корневого compose.yaml). Лежит в tests/e2e, а не в tests/unit: у исполнителя
|
||
// теста нет права менять tests/unit, а красные пункты (открытые дефекты deploy/)
|
||
// должны идти через known-defects.json, не ломая `npm run check`.
|
||
import { readFileSync } from 'node:fs';
|
||
import path from 'node:path';
|
||
import { describe, expect, it } from 'vitest';
|
||
import { parse } from 'yaml';
|
||
|
||
const composePath = path.resolve(import.meta.dirname, '../../deploy/pilot/compose.yaml');
|
||
const raw = readFileSync(composePath, 'utf8');
|
||
const compose = parse(raw) as {
|
||
services: Record<string, { environment?: Record<string, string | number> | string[] }>;
|
||
};
|
||
|
||
function environmentOf(service: string): Record<string, string> {
|
||
const env = compose.services[service]?.environment ?? {};
|
||
if (Array.isArray(env)) {
|
||
return Object.fromEntries(
|
||
env.map((entry) => {
|
||
const index = entry.indexOf('=');
|
||
return index < 0 ? [entry, ''] : [entry.slice(0, index), entry.slice(index + 1)];
|
||
}),
|
||
);
|
||
}
|
||
return Object.fromEntries(Object.entries(env).map(([key, value]) => [key, String(value)]));
|
||
}
|
||
|
||
describe('deploy/pilot/compose.yaml', () => {
|
||
it('[K1/PILOT-1] все подставляемые переменные обязательны: ${VAR:?сообщение}', () => {
|
||
const interpolations = [...raw.matchAll(/\$\{([^}]*)\}/g)].map((match) => match[1]!);
|
||
expect(interpolations.length).toBeGreaterThan(0);
|
||
const optional = interpolations.filter((inner) => !/^[A-Z0-9_]+:\?.+$/.test(inner));
|
||
expect(optional, 'переменные без :? — пустое значение молча попадёт в контейнер').toEqual([]);
|
||
});
|
||
|
||
it('[K2/PILOT-2] ни один сервис не запускается с NODE_ENV=development', () => {
|
||
const offenders = Object.keys(compose.services).filter(
|
||
(service) => environmentOf(service).NODE_ENV === 'development',
|
||
);
|
||
expect(offenders).toEqual([]);
|
||
});
|
||
|
||
it('[PILOT-3] у сервиса app нет учётных данных мигратора', () => {
|
||
const env = environmentOf('app');
|
||
const keys = Object.keys(env).filter((key) =>
|
||
['DATABASE_MIGRATION_URL', 'POSTGRES_MIGRATION_PASSWORD', 'POSTGRES_MIGRATION_USER'].includes(
|
||
key,
|
||
),
|
||
);
|
||
const values = Object.entries(env)
|
||
.filter(([, value]) => /POSTGRES_MIGRATION_PASSWORD|krona_migrator/.test(value))
|
||
.map(([key]) => key);
|
||
expect({ keys, values }).toEqual({ keys: [], values: [] });
|
||
});
|
||
});
|