From a19c06b5741f5b870c2682d09229b05ce4735bd1 Mon Sep 17 00:00:00 2001 From: grok Date: Sat, 26 Sep 2026 14:30:10 +0700 Subject: [PATCH] =?UTF-8?q?test(e2e):=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D0=BA=D0=B0=20deploy/pilot/compose.yaml=20(PILOT-1..3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Зачем: корневой 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. --- tests/e2e/40-compose-pilot.e2e.ts | 56 +++++++++++++++++++++++++++++++ tests/e2e/known-defects.json | 4 ++- 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 tests/e2e/40-compose-pilot.e2e.ts diff --git a/tests/e2e/40-compose-pilot.e2e.ts b/tests/e2e/40-compose-pilot.e2e.ts new file mode 100644 index 0000000..466662c --- /dev/null +++ b/tests/e2e/40-compose-pilot.e2e.ts @@ -0,0 +1,56 @@ +// Статическая проверка 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[] }>; +}; + +function environmentOf(service: string): Record { + 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: [] }); + }); +}); diff --git a/tests/e2e/known-defects.json b/tests/e2e/known-defects.json index 49a3fc1..39086ed 100644 --- a/tests/e2e/known-defects.json +++ b/tests/e2e/known-defects.json @@ -25,6 +25,8 @@ "SYNC-H": "регистрация устройства по cookie без проверки Origin", "SYNC-I": "без токенов анонимно регистрируется устройство", "SYNC-J": "sync принимает отметки вне окна времени", - "SYNC-K": "ack принимает курсор больше выданного сервером" + "SYNC-K": "ack принимает курсор больше выданного сервером", + "PILOT-1": "deploy/pilot/compose.yaml: 14 подстановок без :? (пустые токены/пароли молча проходят)", + "PILOT-2": "deploy/pilot/compose.yaml: app с NODE_ENV=development" } }