krona-staff/tests/e2e/REPORT.md
grok ba59a48ffd test(e2e): перепроверка приёмки на обновлённых ветках исполнителей
Зачем: во время приёмки обе ветки обновили force-push
(codex/fix-live-database ea68e12 → b289dd6, antigravity/fix-reject-device-shifts
795bba0 → 1df2019, теперь на f5ac179). Вердикт по старому SHA не должен
выдаваться за вердикт по новому — «на слово ничего не принимать».

Что: повторный прогон стенда на новых HEAD, результат дописан в
report-evidence/acceptance-*.txt, REPORT.md и NOTES. Итог тот же:
fix-live-database 41 red / 13 green (K3, K4, K13 зелёные),
fix-reject-device-shifts 43 red / 11 green (K13 зелёный).
2026-09-26 15:30:40 +07:00

256 lines
26 KiB
Markdown
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.

# PR: e2e-стенд на реальной PostgreSQL 17 и независимая приёмка (grok/e2e-harness)
База: `codex/bootstrap-platform-foundation` @ `f5ac179` (содержит `331e490`). Ветка перебазирована
без конфликтов. Сравнение:
<https://git.ochenstarik.ru/ochenstarik/krona-staff/compare/codex/bootstrap-platform-foundation...grok/e2e-harness>
Изменены только `tests/e2e/**`, `tests/integration/tenant-isolation.test.ts`,
`.forgejo/workflows/ci.yml`, `scripts/test/**` и одна строка в корневом `.gitignore`
(`tests/e2e/evidence/`, по требованию TASK-GROK-2 §1). `src/`, `apps/`, `migrations/`,
`deploy/`, `package.json` не тронуты. Всё, что требует правки продукта, — в
[`NOTES-FOR-EXECUTORS.md`](NOTES-FOR-EXECUTORS.md).
## Зачем
156 зелёных тестов на поддельной БД не заметили, что создание сотрудника, подтверждение
смены и расчёт зарплаты на реальной PostgreSQL падают всегда. Стенд запускает реальный сервер
под ролью приложения без привилегий на реальной PostgreSQL 17, миграции применяет мигратор без
SUPERUSER, все токены и пароли случайные (`crypto.randomBytes`). Каждый сценарий проверяет
**правильное** поведение: красный = открытый дефект.
## Что изменилось во втором круге (TASK-GROK-2)
| Пункт | Что сделано |
| ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| §1 | Запуск без `.cmd`-обёрток: `scripts/test/node-commands.mjs` — всегда `process.execPath` + JS-точка входа из `require.resolve` (`vitest.mjs`, `prettier.cjs`); сервер — один процесс `node --import tsx src/server.ts` (без дочернего процесса CLI tsx, чтобы `kill()` под Windows не оставлял сироту). Проверка на Linux — `tests/e2e/harness/node-commands.test.ts` (в `npm test`) |
| §1 | `tests/e2e/evidence/` убран из git и добавлен в `.gitignore`; в CI — артефакт `e2e-evidence`. Разовые доказательства (N5, N13, приёмка) — в `tests/e2e/report-evidence/`, скрипты туда не пишут |
| §1 | Задача `windows` в CI (включается переменной `KRONA_WINDOWS_RUNNER=true`, раннера пока нет) |
| §1 | Перебазирование на `f5ac179` — без конфликтов |
| §2 | Двойные ID `[владелец/мой]` в названиях тестов и ключах `known-defects.json` |
| §3 | Новые сценарии `[K6/D5]`, `[K7/D2]`, `[K8/D1]`, `[K9/D3]`, `[S9]`, `[A4]`, `[N12-DATE]` (`tests/e2e/50-money-and-errors.e2e.ts`), все красные и в `known-defects.json` |
| §4 | Приёмка `antigravity/fix-reject-device-shifts`, `codex/fix-format-baseline`, `codex/fix-live-database` — таблицы ниже |
| — | Синтетическая ставка 123,45 → 123,00 ₽/ч: расчёт округляет строку до рубля (`rounding_unit_minor`), тест не должен зависеть от округления (выяснилось на fix-live-database) |
## Как запустить
```sh
export E2E_PG_ADMIN_URL=postgres://<admin>:<pw>@127.0.0.1:55433/postgres # только создание ролей и БД
E2E_MIGRATIONS=roundtrip node scripts/test/run-e2e.mjs # храповик known-defects (как в CI)
node scripts/test/run-e2e.mjs --no-known-defects # строго: любой красный = exit 1
./scripts/test/ci-local.sh # локальный эквивалент verify/format/e2e (bash)
```
## Таблица по ID (основная ветка f5ac179 + этот стенд)
Команда: `./scripts/test/ci-local.sh` (внутри `E2E_MIGRATIONS=roundtrip node scripts/test/run-e2e.mjs`),
2026-09-26 ~15:28 (UTC+7), PostgreSQL 17.11:
```text
== roundtrip OK: rolledBack=11 reapplied=11
Test Files 5 failed | 1 passed (6)
Tests 45 failed | 9 passed (54)
[run-e2e] OK: стенд работает, все красные сценарии — известные открытые дефекты
```
| ID владельца / мой | Цвет | Фактический вывод (первая строка assert) |
| ---------------------------- | ----- | ------------------------------------------------------------------------------------------------- |
| HARNESS (×5), CTRL (×2) | GREEN | 7/7 passed — стенд и позитивные контроли |
| K1/F2 | RED | `{ portal: 200, payroll: 200, write: 201 }` вместо 401 |
| K1/SYNC-I | RED | анонимная регистрация устройства `HTTP 201` |
| K1/PILOT-1 | RED | 14 подстановок без `:?` (первая `STANDALONE_MANAGER_TOKEN`) |
| K2/PILOT-2 | RED | `NODE_ENV=development` у сервиса `app` |
| K3/N2 | RED | подтверждение смены `HTTP 500 code 23502 null value in column "occurred_at" of "staff_outbox"` |
| K4/N1 | RED | `HTTP 500 code 42P08 inconsistent types deduced for parameter $2` |
| K5/N4 | RED | блокирован K3 (500 на подтверждении) |
| K6/D5 | RED | блокирован K3 (500 на подтверждении) |
| K7/D2 | RED | `afterFirst: 22200, afterSecond: 22200` вместо `33300, 37700` |
| K8/D1 | RED | блокирован K3 (500 на подтверждении) |
| K9/D3 | RED | `params.shift_rate_minor` не сохранён (`HTTP 200`, в `params` нет ключа) |
| K10/F3 | RED | `rawTokenInCookie: true` |
| K11/F4 | RED | все попытки `401`, 429 нет |
| K12/N3 | RED | подписка на `https://127.0.0.1:<port>` `HTTP 201`, 1 входящее соединение |
| K13/SYNC-A, K13/SYNC-B | RED | `upsert_work_session` / `upsert_timesheet_entry` → `status: applied` |
| K17/OWN-10, K17/OWN-11 | RED | `404 Route GET .../terminal/employees/:id/enrollment` / `PUT .../biometric-consent` (нет API, C3) |
| K19/OWN-07 | RED | `404 Route POST /access-grants` (нет API, C1/C2) |
| D6/OWN-01, D6/OWN-02 | RED | `404 Route POST /access-grants` (нет API, C1) |
| OWN-03..06, OWN-08, OWN-09 | RED | `404 Route POST /access-grants` (нет API, C1/C2) |
| S2/SYNC-J | RED | отметка 2001 года → `status: applied` |
| S3/SYNC-G | RED | `HTTP 500 code 22007` на весь пакет |
| S4/SYNC-E | RED | отметка уволенного → `applied` |
| S4/SYNC-F | RED | `{ ownOrgUnit: null, foreign: "applied" }` |
| S5/SYNC-C | RED | `HTTP 500 code 42703 column "status" does not exist` (блокирован SYNC-C0) |
| S5/SYNC-D | RED | изменение из портала не видно в инкрементальном pull |
| S8/SYNC-H | RED | регистрация устройства по cookie с чужим Origin `HTTP 201` |
| S9 | RED | удаление точки с устройством: `23502 null value in column "tenant_id"` |
| A4 | RED | 2 тела 500 с SQLSTATE/текстом PostgreSQL (`POST /employees` 42P08, `month=2026-13` 22008) |
| N9 | RED | чужой push-endpoint перепривязан `HTTP 201` |
| N10 | RED | терминал: `{ professions: 200, units: 200 }` |
| N11 | RED | импорт с неверным kind `HTTP 500 code 23514` |
| N12 | RED | лишнее поле и нереальная сумма → `HTTP 200` |
| N12-DATE | RED | `month=2026-13` → `HTTP 500 code 22008`; `2026-02-30` уже 400 |
| F7 | RED | повтор Idempotency-Key создал второе событие |
| F10 | RED | мутация по cookie без Origin `HTTP 201` |
| SYNC-C0 (нет ID у владельца) | RED | bootstrap pull `HTTP 500 code 42703 column "status" does not exist` |
| SYNC-K (нет ID у владельца) | RED | ack курсора из будущего `HTTP 200` |
| SYNC-L | GREEN | отозванное устройство получает 403 (позитивный контроль) |
| PILOT-3 | GREEN | у `app` нет учётных данных мигратора |
| N5 (tenant-isolation) | GREEN | в `npm run check` с тестовой БД: `Test Files 20 passed (20)`, `Tests 196 passed (196)` |
| N13 (скрипт, не vitest) | — | не воспроизведено: 0 из 30 попыток при 10/25/50 (обход K3 в одноразовой БД), см. ниже |
Сверх таблицы TASK-GROK-2 §2 (по тексту AUDIT.md): K1/SYNC-I (анонимный доступ без токенов на
`/sync`), K1/PILOT-1 (подстановки без `:?` названы в K1), K2/PILOT-2 (development в пилоте — суть K2).
## Приёмка веток исполнителей (TASK-GROK-2 §4)
Способ: `git worktree add --detach <dir> origin/<ветка>`, поверх — `tests/e2e/` и `scripts/test/`
из этой ветки (в ветки исполнителей ничего не коммитилось), затем
`E2E_MIGRATIONS=roundtrip E2E_DATABASE_NAME=krona_staff_acc node scripts/test/run-e2e.mjs`.
«Было» — основная ветка `f5ac179` с тем же стендом (45 red / 9 green, храповик OK). Полные таблицы —
`tests/e2e/report-evidence/acceptance-*.txt`. Ни одна ветка не влита в основную
(`git merge-base --is-ancestor` → нет), поэтому `known-defects.json` не сокращён.
### `codex/fix-live-database` @ ea68e12, перепроверено на b289dd6
```text
== roundtrip OK: rolledBack=11 reapplied=11
Tests 41 failed | 13 passed (54)
| K13/SYNC-A | GREEN | 1/1 | зелёный — ИСПРАВЛЕН, уберите из known-defects.json |
| K13/SYNC-B | GREEN | 1/1 | зелёный — ИСПРАВЛЕН, уберите из known-defects.json |
| K3/N2 | GREEN | 1/1 | зелёный — ИСПРАВЛЕН, уберите из known-defects.json |
| K4/N1 | GREEN | 1/1 | зелёный — ИСПРАВЛЕН, уберите из known-defects.json |
```
| ID | Было | Стало | Доказательство |
| ---------- | ---- | ----- | ------------------------------------------------------------------------------------------------------------ |
| K3/N2 | RED | GREEN | было: подтверждение смены `500 23502 occurred_at`; стало: passed (8 ч × 123,00 ₽ = 98400 в ledger) |
| K4/N1 | RED | GREEN | было: `500 42P08`; стало: passed (201, сотрудник в БД) |
| K13/SYNC-A | RED | GREEN | было: `upsert_work_session → applied`; стало: passed (см. находку по fd1ec3d) |
| K13/SYNC-B | RED | GREEN | было: `upsert_timesheet_entry → applied`; стало: passed |
| K5/N4 | RED | RED | было: блокирован K3; стало: `смена должна быть начислена ровно один раз: expected 196800n to be 98400n` |
| K6/D5 | RED | RED | было: блокирован K3; стало: `после отклонения оплаченной смены должно быть сторно: expected 98400n to be 0n` |
| K8/D1 | RED | RED | было: блокирован K3; стало: `C4 verify: HTTP 404 Route GET .../payroll/runs/:id/verify not found` |
| A4 | RED | RED | было: 2 утечки; стало: 1 — `POST /payroll/calculate month=2026-13` → `code=22008` |
| F14 | RED | GREEN | `prettier --check .` → `All matched files use Prettier code style!` (без оверлея evidence) |
Ветку обновили force-push во время приёмки (`ea68e12` → `b289dd6`, изменён только
`tests/integration/live-postgres-remediation.test.ts`); повторный прогон на `b289dd6` дал те же
41 red / 13 green и ту же таблицу.
Вердикт: K3 и K4 исправлены (подтверждено на живой PostgreSQL 17, миграции roundtrip). После влития
убрать `K3/N2`, `K4/N1` из `known-defects.json`. Цепочка расчёта теперь доходит до денежных дефектов:
K5 и K6 подтверждены по существу.
### `codex/fix-format-baseline` @ fd1ec3d
```text
Tests 43 failed | 11 passed (54)
| K13/SYNC-A | GREEN | 1/1 | зелёный — ИСПРАВЛЕН, уберите из known-defects.json |
| K13/SYNC-B | GREEN | 1/1 | зелёный — ИСПРАВЛЕН, уберите из known-defects.json |
$ node node_modules/prettier/bin/prettier.cjs --check .
All matched files use Prettier code style!
```
| ID | Было | Стало | Доказательство |
| ---------- | -------------- | ----- | ------------------------------------------------- |
| F14 | RED (23 файла) | GREEN | `All matched files use Prettier code style!` |
| K13/SYNC-A | RED | GREEN | было: `applied`; стало: passed |
| K13/SYNC-B | RED | GREEN | было: `applied`; стало: passed |
| остальные | без изменений | — | compare по `e2e-results.json`: других отличий нет |
**Находка:** «style»-коммит `fd1ec3d` содержит функциональное исправление Antigravity.
После нормализации prettier `src/domain/sync/service.ts` отличается от `331e490` на 255 строк diff
и совпадает с `antigravity/fix-reject-device-shifts` (0 строк); `tests/unit/sync.test.ts` — 139 и 0.
Изменение поведения скрыто в коммите форматирования — смешение тем (AGENTS.md).
### `antigravity/fix-reject-device-shifts` @ 795bba0, перепроверено на 1df2019
```text
Tests 43 failed | 11 passed (54)
| K13/SYNC-A | GREEN | 1/1 | зелёный — ИСПРАВЛЕН, уберите из known-defects.json |
| K13/SYNC-B | GREEN | 1/1 | зелёный — ИСПРАВЛЕН, уберите из known-defects.json |
```
| ID | Было | Стало | Доказательство |
| ---------- | ---- | ----- | ------------------------------------------------------- |
| K13/SYNC-A | RED | GREEN | было: `upsert_work_session → applied`; стало: passed |
| K13/SYNC-B | RED | GREEN | было: `upsert_timesheet_entry → applied`; стало: passed |
| остальные | — | — | других отличий нет |
Ветку перебазировали на `f5ac179` во время приёмки (`795bba0` → `1df2019`); повторный прогон
на `1df2019`: те же 43 red / 11 green, отличия только K13/SYNC-A/B.
Вердикт: K13 исправлен. Убрать `K13/SYNC-A`,
`K13/SYNC-B` отдельным коммитом после влития (и после исправления очистки в
`sync-rejection.test.ts`, TASK-GROK-2 §4.1).
## Windows
- Запуск стенда переписан без `npx` и `node_modules/.bin` (раньше `spawn('npx')` и
`spawn('.../.bin/tsx')` падали на Windows с `ENOENT`). Проверка на Linux:
`tests/e2e/harness/node-commands.test.ts` — 8 тестов: команды начинаются с `process.execPath`,
без `.cmd`/`.bin`/`npx`, файлы существуют; под `win32` дочерний процесс получает
`SystemRoot`/`TEMP`, секреты родителя не наследуются.
- **Реальный запуск на Windows 11 — не проверено**: Windows-раннера у git.ochenstarik.ru нет.
В CI добавлена задача `windows` (`runs-on: windows`), включаемая переменной
`KRONA_WINDOWS_RUNNER=true`; для полного e2e нужен секрет `WINDOWS_E2E_PG_ADMIN_URL`.
- `scripts/test/ci-local.sh` — bash, на Windows не предназначен (под Windows запускать
`node scripts/test/run-e2e.mjs`).
## Evidence вне git
```text
$ E2E_MIGRATIONS=roundtrip node scripts/test/run-e2e.mjs # (повторный прогон)
[run-e2e] OK: стенд работает, все красные сценарии — известные открытые дефекты
$ git status --porcelain
# (пусто)
```
## N5 (tenant-isolation)
До (`778ba2d`, очистка ролью приложения) — [`report-evidence/n5-before.txt`](report-evidence/n5-before.txt):
`error: permission denied for table staff_employees`, `exit=1`. После (очистка ролью мигратора с
`app.tenant_id`) — [`report-evidence/n5-after.txt`](report-evidence/n5-after.txt): 2 прогона, `exit=0`.
На `f5ac179`: `npm run check` с тестовой БД — `Test Files 20 passed (20)`, `Tests 196 passed (196)`.
## CI (`.forgejo/workflows/ci.yml`)
- `verify`: `postgres:17-alpine@sha256:…`, роли как в pilot (мигратор NOSUPERUSER, app NOBYPASSRLS),
миграции `up → откат всех 11 → up → up`, `npm run check` с тестовой БД, `npm audit`.
- `format`: отдельно (F14 красный до влития `codex/fix-format-baseline`).
- `e2e`: PostgreSQL 17, `E2E_MIGRATIONS=roundtrip`, храповик; evidence — артефакт `e2e-evidence`
(`actions/upload-artifact@ea165f8… # v4.6.2`, Forgejo 9.0.3).
- `windows`: выключена без раннера (см. выше). `docker-build`: метка `docker-host`, `needs: verify`, только push.
- Все actions закреплены SHA (F13), SHA совпадают на github.com и code.forgejo.org.
Механизм красных сценариев — храповик `known-defects.json`, а не «allowed to fail»: сломанный стенд,
новый красный ID или исправленный, но не вычеркнутый ID делают задачу `e2e` красной.
Валидация: `actionlint -config-file actionlint.yaml .forgejo/workflows/ci.yml` (1.7.12, метки
`docker`, `docker-host`, `windows`) → 0 замечаний. Локально `./scripts/test/ci-local.sh`:
```text
::: [migrate-roundtrip] OK (== roundtrip OK: rolledBack=11 reapplied=11)
::: [check] OK (Test Files 20 passed (20), Tests 196 passed (196))
::: [audit] OK (found 0 vulnerabilities)
::: [format-check] FAIL (Code style issues found in 23 files — F14)
::: [e2e] OK (Tests 45 failed | 9 passed (54), храповик OK)
```
## N13 (гонка первого расчёта)
`node scripts/test/n13-race.mjs` (10/25/50 параллельных первых расчётов × 5 попыток) —
[`report-evidence/n13-race.txt`](report-evidence/n13-race.txt): как есть 15/15 → 500 (K3); с обходом K3
в одноразовой БД — 0 некорректных из 30 (с подтверждённой сменой и без). Не воспроизведено, как и у
владельца; вероятная причина (гипотеза по коду) — upsert `ensureDefaultPayRules` сериализует расчёты
тенанта. Прогон до перебазирования, на `778ba2d`.
## Не проверено
- Реальный Forgejo-раннер (workflow выполнялся только локальным эквивалентом), `docker build`.
- Запуск на Windows 11 (раннера нет); Windows-only: DPAPI, ACL каталога данных, установщик, WinUI,
named pipe — см. тесты Codex в `apps/KronaStaffTerminal.Tests`.
- OWN-01..11, K8 (C4) и K9 (C5) проверяют предполагаемые контракты из NOTES.
- K5/N4, K6/D5, K8/D1 на основной ветке блокированы K3; по существу проверены только на
`codex/fix-live-database`.
- S9 проверен удалением SQL-запросом мигратора (публичного API удаления точки нет).
- Причина невоспроизводимости N13 — гипотеза по коду.