krona-staff/tests/e2e/40-compose-pilot.e2e.ts
grok c73c714611 test(e2e): проверка deploy/pilot/compose.yaml (PILOT-1..3)
Зачем: корневой compose.yaml уже проверяется tests/unit/deployment.test.ts,
а pilot-файл, который реально разворачивается у владельца, — нет. В нём
подстановки вида ${STANDALONE_MANAGER_TOKEN} без :? молча дают пустое
значение, а при пустых токенах standalone выдаёт права анониму (F2);
NODE_ENV=development в pilot отключает production-проверки config.ts.

- PILOT-1: каждая подстановка должна быть ${VAR:?сообщение};
- PILOT-2: ни один сервис не с NODE_ENV=development;
- PILOT-3: у app нет учётных данных мигратора (сейчас зелёный).
Файл в tests/e2e как *.e2e.ts: tests/unit вне разрешённых путей, а
красные пункты (дефекты deploy/, которые правят исполнители) идут через
known-defects.json и не ломают `npm run check`. Сейчас PILOT-1 и PILOT-2
красные: 14 подстановок без :?, app с NODE_ENV=development.
2026-09-26 15:18:31 +07:00

56 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Статическая проверка 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('[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('[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: [] });
});
});