Commit graph

13 commits

Author SHA1 Message Date
3475133b10 test(e2e): синтетические суммы кратны рублю (учёт округления расчёта)
Зачем: на ветке codex/fix-live-database цепочка подтверждение → расчёт →
утверждение впервые дошла до конца, и K3/N2 остался красным не из-за
дефекта, а из-за теста: 8 ч × 123,45 ₽ = 98760 коп., а расчёт округляет
строку до rounding_unit_minor (по умолчанию 100 коп., service.ts:695) и
начисляет 98800. Округление до рубля — настройка продукта, не дефект;
тест не должен от него зависеть.

Что: SYNTHETIC_RATE_MINOR 12345 → 12300 (123,00 ₽/ч), оплата смены
официанта в K9 111111 → 111100. Суммы остаются заведомо синтетическими.
2026-09-26 15:25:47 +07:00
ac8ab3cc02 test(e2e): сценарии K6–K9, S9, A4 и N12-DATE из аудита владельца
Зачем: TASK-GROK-2 §3 — денежные дефекты K6–K9, S9, A4 и дата расчёта
были в AUDIT.md, но не покрыты стендом; без красных сценариев исправления
Codex нечем принимать.

Что (tests/e2e/50-money-and-errors.e2e.ts, всё красное и внесено в
known-defects.json):
- K6/D5: отклонение смены из подписанной ведомости → 409 или сторно.
  Сейчас падает на предусловии: подтверждение смены 500 (K3).
- K7/D2: две записи начисления за один день (две точки) и повторный
  импорт с другой суммой → итог = сумма по книге. Факт: 22200 вместо 33300.
- K8/D1: после подписи меняется ставка, затем проверочный пересчёт
  (предполагаемый контракт C4 GET /payroll/runs/:id/verify). Блокирован K3.
- K9/D3: официант по пресету waiter_upsell: params.shift_rate_minor не
  сохраняется (сам D3, до K3); далее начисление = оплата смены (после K3).
- S9: удаление точки с устройством мигратором → 23502 вместо удаления или
  23503 (публичного API удаления нет — так же, как в live-sync.mjs владельца).
- A4: тела 500 не должны содержать SQLSTATE и текст PostgreSQL.
- N12-DATE: month=2026-13 → 400 (факт 500, 22008); 2026-02-30 уже 400.
Прогон run-e2e: 45 red / 9 green, храповик OK.
2026-09-26 15:24:02 +07:00
0453168d70 test(e2e): двойные ID «владелец/мой» в названиях сценариев и known-defects
Зачем: аудит владельца (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 — те же сценарии, новые имена.
2026-09-26 15:21:22 +07:00
fc20f75b55 test(e2e): evidence прогонов вне git, в CI — артефакт
Зачем: tests/e2e/evidence/ перезаписывается каждым прогоном (логи серверов
с ULID и портами, JSON vitest), поэтому любой запуск стенда менял
отслеживаемые файлы и засорял дифф (приёмка владельца, TASK-GROK-2 п.1).

Что: tests/e2e/evidence/ убран из индекса (git rm --cached) и добавлен в
корневой .gitignore (его же читает prettier, поэтому форматировать
сгенерированные файлы больше не нужно — вызов prettier из run-e2e убран).
В CI задача e2e выгружает папку артефактом e2e-evidence
(actions/upload-artifact, закреплён SHA v4.6.2; Forgejo 9.0.3 поддерживает
v4). Разовые доказательства, которые скрипты не перезаписывают (N5 до/после,
замер N13), перенесены в tests/e2e/report-evidence/ — на них ссылается
REPORT.md.
2026-09-26 15:20:28 +07:00
0aaa2f4d2d test(e2e): запуск vitest/tsx/prettier без .cmd-обёрток (Windows)
Зачем: проект разрабатывается под Windows, а стенд там не стартовал:
spawn('npx') и spawn('node_modules/.bin/tsx') без shell падают с ENOENT,
потому что в Windows это .cmd-файлы (приёмка владельца, TASK-GROK-2 п.1).

Что: scripts/test/node-commands.mjs находит JS-точки входа через
require.resolve (tsx/cli, vitest.mjs, prettier.cjs) и всегда запускает их
текущим node (process.execPath). Сервер запускается одним процессом
`node --import tsx src/server.ts`: CLI tsx порождает дочерний node, и под
Windows kill() родителя оставил бы сервер-сироту на порту. baseEnv()
передаёт дочерним процессам PATH и под win32 системные переменные
(SystemRoot, TEMP и др.), без которых node на Windows не работает с сетью,
но не наследует секреты родителя.

Проверка на Linux: tests/e2e/harness/node-commands.test.ts (входит в
npm test) — команды начинаются с process.execPath, без .cmd/.bin/npx,
файлы существуют; исходники стенда не вызывают npx и node_modules/.bin.
Реальный прогон на Windows 11 — не проверено (нет Windows-раннера).
2026-09-26 15:19:47 +07:00
537b784da3 docs(e2e): REPORT.md (описание PR) и свежие evidence прогона
Зачем: PR в Forgejo создать нельзя (нет API-токена), поэтому описание
изменений, таблица green/red по ID с командами и фактическим выводом, итог
N5 до/после, валидация CI и N13 лежат в репозитории рядом с тестами.
Evidence перезаписаны финальным прогоном ./scripts/test/ci-local.sh, чтобы
отчёт ссылался на те же числа, что в файлах (38 red / 9 green, храповик OK,
format красный из-за F14). Число файлов F14 в комментарии ci.yml и в
заметках уточнено до 23 по этому замеру.
2026-09-26 15:18:31 +07:00
045f696425 test(e2e): N13 — нагрузочная проверка гонки первого расчёта зарплаты
Зачем: владелец пометил N13 как «не воспроизведено», а в коде окно гонки
(SELECT ... FOR UPDATE по отсутствующей строке, затем INSERT ... ON CONFLICT
DO NOTHING со своим runId) видно. Нужен повторяемый замер на живом сервере
и реальной PostgreSQL 17, а не рассуждение.

Что: scripts/test/n13-race.mjs — 10/25/50 параллельных первых
POST /payroll/calculate, каждая попытка на новой точке, проверка статусов,
числа run_id, строк в staff_payroll_runs и «фантомных» run_id. Режим as-is
показывает, что сейчас всё упирается в N2 (500). Режим n2-bypass ставит
DEFAULT now() только в одноразовой БД скрипта (продукт и миграции не
меняются), чтобы увидеть саму гонку. Итог: 0 некорректных из 30 попыток;
причина (из кода) — случайная сериализация через upsert
ensureDefaultPayRules. Это записано в NOTES-FOR-EXECUTORS вместе с
рекомендацией явной блокировки и замечанием об утечке текста ошибок БД в 500.
2026-09-26 15:18:31 +07:00
82a2a7ca37 test(e2e): сценарии решений владельца (OWN-01..11) и заметки исполнителям
Зачем: решения владельца (роли точки/бухгалтера/директора, пакет зарплаты
с подписью директора, неизменяемость после подписи, зарплата по точке
фактической смены, согласие на биометрию) должны стать исполняемыми
критериями приёмки до того, как Codex начнёт этап 4, иначе реализацию
будет нечем проверить.

API ещё нет, поэтому тесты написаны против наиболее вероятного контракта
C1 (access-grants), C2 (payroll/batches, submit/sign/return,
notifications), C3 (biometric-consent, terminal enrollment); контракт и
инварианты описаны в tests/e2e/NOTES-FOR-EXECUTORS.md. Сейчас все OWN
красные (404 Route not found) и внесены в known-defects.json.
В NOTES также: найденные сценариями дефекты с указанием файлов,
конфликт NODE_ENV/standalone для pilot, N6, требования к раннеру docker-host.
2026-09-26 15:18:31 +07:00
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
2548bd7bdf test(e2e): сценарии офлайн-синхронизации (/sync/*)
Зачем: бэкенд офлайн-синхронизации (миграция 0011) покрыт только
юнит-тестами без реальной БД. Сценарии на живом сервере показывают, как
терминал реально взаимодействует с сервером, и фиксируют границы
доверия к устройству.

Проверяется правильное поведение: устройство не подтверждает смены и не
утверждает табель сам (SYNC-A/B); начальная загрузка работает (SYNC-C0,
сейчас 500 «column status does not exist» — у staff_org_units нет
status) и не теряет сущности после 100-й (SYNC-C); правки из портала
видны в инкрементальном pull (SYNC-D); отметки уволенных и вне окна
времени отклоняются (SYNC-E/J); точка устройства сохраняется, чужая
отклоняется (SYNC-F); битая команда не роняет пакет (SYNC-G); CSRF и
анонимная регистрация устройств запрещены (SYNC-H/I); ack не принимает
курсор из будущего (SYNC-K). SYNC-L — позитивный контроль отзыва.
2026-09-26 15:18:31 +07:00
397954be0a test(e2e): сценарии дефектов основного API (N1-N4, N9-N12, F2-F4, F7, F10)
Зачем: каждый открытый дефект аудита должен иметь исполняемый сценарий
на реальной БД, который проверяет ПРАВИЛЬНОЕ поведение. Тогда красный
тест = открытый дефект, а исправление видно по смене цвета, без ручной
перепроверки.

- N1 POST /employees -> 201; N2 подтверждение смены, расчёт, утверждение
  и сумма начисления 8 ч x синтетическая ставка; N4 пересекающиеся
  периоды не начисляют смену дважды (сейчас блокирован N2 — это видно в
  сообщении assert как «предусловие, см. N2»);
- N3 SSRF: подписка на https://127.0.0.1:<порт> должна отклоняться, и
  локальный слушатель не должен получить соединение;
- N9, N12, N10, N11, F2 (процесс без токенов), F3, F4 (отдельный процесс),
  F7, F10.
Сотрудники для сценариев, не относящихся к N1, создаются импортом, чтобы
один дефект не маскировал остальные. Все данные синтетические (E2E-*),
ставка 123,45 ₽/ч заведомо не реальная.
2026-09-26 15:18:31 +07:00
646e8d4f22 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 15:18:31 +07:00
5ac248660a test(db): стенд PostgreSQL с мигратором без SUPERUSER
Зачем: аудит показал, что 156 зелёных тестов на fakeDatabase не заметили,
что создание сотрудника, подтверждение смены и расчёт зарплаты всегда
падают на реальной PostgreSQL. Чтобы тесты и CI проверяли реальный SQL,
нужен воспроизводимый стенд с ролями как в pilot, но с минимальными
привилегиями:

- scripts/test/provision-pg.mjs: bootstrap-админ (только как DBA/entrypoint)
  создаёт krona_migrator (NOSUPERUSER NOCREATEDB NOCREATEROLE, владелец БД и
  схемы public) и krona_staff_app (NOSUPERUSER NOBYPASSRLS NOINHERIT); пароли
  crypto.randomBytes, пишутся только в env-файл 0600, в вывод не попадают.
  scripts/create-app-role.mjs здесь не используется, т.к. без SUPERUSER он
  падает (находка N6, продуктовый код не трогаем).
- scripts/test/migrate-roundtrip.mjs: up -> откат всех миграций -> up ->
  повторный up должен быть no-op; ролью мигратора, как требует AGENTS.md.

Проверено на PostgreSQL 17.11: 11 миграций применены, откачены, применены
повторно, третий up пустой.
2026-09-26 15:18:31 +07:00