Зачем: корневой 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.
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('[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: [] });
|
||
});
|
||
});
|