krona-staff/tests/e2e/evidence/e2e-run.txt
grok 421da722c0 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 14:35:19 +07:00

941 lines
59 KiB
Text
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.

$ npx vitest run --config tests/e2e/vitest.e2e.config.ts --reporter=verbose --reporter=json --outputFile.json=/workspace/krona-staff/tests/e2e/evidence/e2e-results.json
RUN v5.0.1 /workspace/krona-staff
[e2e] provision:
provisioned database=krona_staff_e2e server_version=17.11
role=krona_migrator super=false createrole=false createdb=false bypassrls=false
role=krona_staff_app super=false createrole=false createdb=false bypassrls=false
[e2e] migrations (roundtrip) as krona_migrator:
== migrate up (1)
applied 0001_infrastructure.up.sql
applied 0002_security_hardening.up.sql
applied 0003_app_role_privileges.up.sql
applied 0004_revoke_broad_default_privileges.up.sql
applied 0005_attendance_portal.up.sql
applied 0006_payroll_foundation.up.sql
applied 0007_work_session_trace.up.sql
applied 0008_employee_access.up.sql
applied 0009_professions.up.sql
applied 0010_employee_push_notifications.up.sql
applied 0011_offline_sync_backend.up.sql
== rollback all (applied now: 11)
rolled back 0011_offline_sync_backend.down.sql
rolled back 0010_employee_push_notifications.down.sql
rolled back 0009_professions.down.sql
rolled back 0008_employee_access.down.sql
rolled back 0007_work_session_trace.down.sql
rolled back 0006_payroll_foundation.down.sql
rolled back 0005_attendance_portal.down.sql
rolled back 0004_revoke_broad_default_privileges.down.sql
rolled back 0003_app_role_privileges.down.sql
rolled back 0002_security_hardening.down.sql
rolled back 0001_infrastructure.down.sql
no migration to roll back
== rolled back 11 migrations
== migrate up (2)
applied 0001_infrastructure.up.sql
applied 0002_security_hardening.up.sql
applied 0003_app_role_privileges.up.sql
applied 0004_revoke_broad_default_privileges.up.sql
applied 0005_attendance_portal.up.sql
applied 0006_payroll_foundation.up.sql
applied 0007_work_session_trace.up.sql
applied 0008_employee_access.up.sql
applied 0009_professions.up.sql
applied 0010_employee_push_notifications.up.sql
applied 0011_offline_sync_backend.up.sql
== migrate up (3, must be no-op)
== roundtrip OK: rolledBack=11 reapplied=11
[e2e] server secured ready at http://127.0.0.1:35389 (log: tests/e2e/evidence/server-secured.txt)
[e2e] server no-tokens ready at http://127.0.0.1:33821 (log: tests/e2e/evidence/server-no-tokens.txt)
[e2e] server rate-limit ready at http://127.0.0.1:36299 (log: tests/e2e/evidence/server-rate-limit.txt)
× tests/e2e/10-core-defects.e2e.ts > Основной API на реальной PostgreSQL > [N1] менеджер создаёт сотрудника через POST /employees → 201 38ms
→ HTTP 500 {"statusCode":500,"code":"42P08","error":"Internal Server Error","message":"inconsistent types deduced for parameter $2"}: expected 500 to be 201 // Object.is equality
× tests/e2e/10-core-defects.e2e.ts > Основной API на реальной PostgreSQL > [N2] подтверждение смены, расчёт и утверждение зарплаты проходят (200) и начисляют 8 ч × ставка 111ms
→ подтверждение смены: HTTP 500 {"statusCode":500,"code":"23502","error":"Internal Server Error","message":"null value in column \"occurred_at\" of relation \"staff_outbox\" violates not-null constraint"}: expected 500 to be 200 // Object.is equality
× tests/e2e/10-core-defects.e2e.ts > Основной API на реальной PostgreSQL > [N4] пересекающиеся расчёты не начисляют одну смену дважды 66ms
→ подтверждение смены (предусловие, см. N2): HTTP 500 {"statusCode":500,"code":"23502","error":"Internal Server Error","message":"null value in column \"occurred_at\" of relation \"staff_outbox\" violates not-null constraint"}: expected 500 to be 200 // Object.is equality
× tests/e2e/10-core-defects.e2e.ts > Основной API на реальной PostgreSQL > [N3] push-подписка на внутренний адрес отклоняется, сервер не подключается к 127.0.0.1 2544ms
→ подписка: HTTP 201 {"id":"01M3EA4FH3FG4Q3YEKXYQ8Y4Q5","endpoint":"https://127.0.0.1:36027/e2e-internal-admin"}: expected { status: 201, connections: 1 } to deeply equal { status: toSatisfy{…}, …(1) }
× tests/e2e/10-core-defects.e2e.ts > Основной API на реальной PostgreSQL > [N9] чужой push-endpoint нельзя перепривязать к другому сотруднику 44ms
→ HTTP 201 {"id":"01M3EA4J0JSPFRDF22J6E2CEY1","endpoint":"https://fcm.googleapis.com/fcm/send/e2e-d464594734e6ddf8569ec571f5832dbf"}: expected { status: 201, …(1) } to deeply equal { status: toSatisfy{…}, …(1) }
× tests/e2e/10-core-defects.e2e.ts > Основной API на реальной PostgreSQL > [N12] PUT pay-assignment отвергает лишние поля и нереальные суммы (400) 31ms
→ HTTP 200 {"id":"01M3EA4J1PHXWHFMAMYYVRMK3E","employee_id":"01M3EA4J151B1H9D5AKAR29AN9","org_unit_id":null,"pay_rule_id":"01M3EA4FCDRQSS6V8H5Q6XS615","pay_rule_code":"hourly","pay_rule_name":"Почасовая (стажёр, дополнительный сотрудник)","rate_minor":12345,"percent_bp":0,"guarantee_minor":0,"pool_weight_bp":0… | HTTP 200 {"id":"01M3EA4J1PHXWHFMAMYYVRMK3E","employee_id":"01M3EA4J151B1H9D5AKAR29AN9","org_unit_id":null,"pay_rule_id":"01M3EA4FCDRQSS6V8H5Q6XS615","pay_rule_code":"hourly","pay_rule_name":"Почасовая (стажёр, дополнительный сотрудник)","rate_minor":9007199254740991,"percent_bp":0,"guarantee_minor":900719925…: expected { extra: 200, huge: 200 } to deeply equal { extra: 400, huge: 400 }
× tests/e2e/10-core-defects.e2e.ts > Основной API на реальной PostgreSQL > [N10] терминальный токен не читает справочники профессий и точек (403) 4ms
→ expected { professions: 200, units: 200 } to deeply equal { professions: 403, units: 403 }
× tests/e2e/10-core-defects.e2e.ts > Основной API на реальной PostgreSQL > [F2] standalone без токенов не выдаёт права менеджера анонимному запросу (401) 20ms
→ expected { portal: 200, payroll: 200, …(1) } to deeply equal { portal: 401, payroll: 401, …(1) }
× tests/e2e/10-core-defects.e2e.ts > Основной API на реальной PostgreSQL > [F3] cookie сессии не содержит сырой токен и отзывается при выходе 8ms
→ expected { rawTokenInCookie: true, …(1) } to deeply equal { rawTokenInCookie: false, …(1) }
× tests/e2e/10-core-defects.e2e.ts > Основной API на реальной PostgreSQL > [F10] мутация по cookie без Origin отклоняется (CSRF, 403) 5ms
→ HTTP 201 {"id":"01M3EA4J31CEM99Y9708PNVE90","code":"profession-01m3ea4j31cem99y9708pnve90","name":"E2E-CSRF-профессия-c30803","status":"active"}: expected 201 to be 403 // Object.is equality
× tests/e2e/10-core-defects.e2e.ts > Основной API на реальной PostgreSQL > [F7] повтор Idempotency-Key с другим телом не создаёт второе событие 31ms
→ expected { first: 201, secondCreated: true } to deeply equal { first: 201, secondCreated: false }
× tests/e2e/10-core-defects.e2e.ts > Основной API на реальной PostgreSQL > [N11] импорт с неверным kind начисления → 400, а не 500 25ms
→ HTTP 500 {"statusCode":500,"code":"23514","error":"Internal Server Error","message":"new row for relation \"staff_payroll_ledger\" violates check constraint \"staff_payroll_ledger_kind_check\""}: expected 500 to be 400 // Object.is equality
× tests/e2e/10-core-defects.e2e.ts > Основной API на реальной PostgreSQL > [F4] перебор ключей входа ограничивается (429 не позже 30 неверных попыток) 75ms
→ статусы: 401: expected false to be true // Object.is equality
× tests/e2e/30-owner-decisions.e2e.ts > Решения владельца: роли, пакеты зарплаты, согласие на биометрию > [OWN-01] менеджер точки A запускает расчёт для точки B → 403 110ms
→ C1 POST /access-grants (point_manager): HTTP 404 {"message":"Route POST:/api/v1/staff/access-grants not found","error":"Not Found","statusCode":404}: expected 404 to be 201 // Object.is equality
× tests/e2e/30-owner-decisions.e2e.ts > Решения владельца: роли, пакеты зарплаты, согласие на биометрию > [OWN-02] менеджер точки отправляет пакет на согласование → 403 64ms
→ C1 POST /access-grants (point_manager): HTTP 404 {"message":"Route POST:/api/v1/staff/access-grants not found","error":"Not Found","statusCode":404}: expected 404 to be 201 // Object.is equality
× tests/e2e/30-owner-decisions.e2e.ts > Решения владельца: роли, пакеты зарплаты, согласие на биометрию > [OWN-03] бухгалтер отправляет пакет → успех, каждый директор получает ровно одно уведомление 2ms
→ C1 POST /access-grants (accountant): HTTP 404 {"message":"Route POST:/api/v1/staff/access-grants not found","error":"Not Found","statusCode":404}: expected 404 to be 201 // Object.is equality
× tests/e2e/30-owner-decisions.e2e.ts > Решения владельца: роли, пакеты зарплаты, согласие на биометрию > [OWN-04] бухгалтер пытается подписать пакет → 403 2ms
→ C1 POST /access-grants (accountant): HTTP 404 {"message":"Route POST:/api/v1/staff/access-grants not found","error":"Not Found","statusCode":404}: expected 404 to be 201 // Object.is equality
× tests/e2e/30-owner-decisions.e2e.ts > Решения владельца: роли, пакеты зарплаты, согласие на биометрию > [OWN-05] до подписи директора в ledger нет начислений и выплат 2ms
→ C1 POST /access-grants (accountant): HTTP 404 {"message":"Route POST:/api/v1/staff/access-grants not found","error":"Not Found","statusCode":404}: expected 404 to be 201 // Object.is equality
× tests/e2e/30-owner-decisions.e2e.ts > Решения владельца: роли, пакеты зарплаты, согласие на биометрию > [OWN-06] подпись директора → начисления и выплаты в ledger, пакет и смены неизменяемы 2ms
→ C1 POST /access-grants (accountant): HTTP 404 {"message":"Route POST:/api/v1/staff/access-grants not found","error":"Not Found","statusCode":404}: expected 404 to be 201 // Object.is equality
× tests/e2e/30-owner-decisions.e2e.ts > Решения владельца: роли, пакеты зарплаты, согласие на биометрию > [OWN-07] смена изменена после отправки → подписать нельзя (409), пакет возвращён бухгалтеру 1ms
→ C1 POST /access-grants (accountant): HTTP 404 {"message":"Route POST:/api/v1/staff/access-grants not found","error":"Not Found","statusCode":404}: expected 404 to be 201 // Object.is equality
× tests/e2e/30-owner-decisions.e2e.ts > Решения владельца: роли, пакеты зарплаты, согласие на биометрию > [OWN-08] возврат с комментарием → пакет у бухгалтера, комментарий сохранён 2ms
→ C1 POST /access-grants (accountant): HTTP 404 {"message":"Route POST:/api/v1/staff/access-grants not found","error":"Not Found","statusCode":404}: expected 404 to be 201 // Object.is equality
× tests/e2e/30-owner-decisions.e2e.ts > Решения владельца: роли, пакеты зарплаты, согласие на биометрию > [OWN-09] сотрудник точки A отработал смену в точке B → смена попадает в зарплату точки B 2ms
→ C1 POST /access-grants (accountant): HTTP 404 {"message":"Route POST:/api/v1/staff/access-grants not found","error":"Not Found","statusCode":404}: expected 404 to be 201 // Object.is equality
× tests/e2e/30-owner-decisions.e2e.ts > Решения владельца: роли, пакеты зарплаты, согласие на биометрию > [OWN-10] сотрудник без согласия на биометрию → сервер сообщает терминалу «регистрация запрещена» 15ms
→ C3: HTTP 404 {"message":"Route GET:/api/v1/staff/terminal/employees/01M3EA4JGPPB7M0G78QQBQBJ1M/enrollment not found","error":"Not Found","statusCode":404}: expected 404 to be 200 // Object.is equality
× tests/e2e/30-owner-decisions.e2e.ts > Решения владельца: роли, пакеты зарплаты, согласие на биометрию > [OWN-11] согласие отозвано → статус для терминала меняется 15ms
→ C3 PUT biometric-consent: HTTP 404 {"message":"Route PUT:/api/v1/staff/employees/01M3EA4JH57XZ361XBC2Z7SA6J/biometric-consent not found","error":"Not Found","statusCode":404}: expected 404 to be 200 // Object.is equality
× tests/e2e/20-sync-defects.e2e.ts > Офлайн-синхронизация на реальной PostgreSQL > [SYNC-A] устройство не может само подтвердить смену (status=confirmed отклоняется) 87ms
→ HTTP 200 {"results":[{"idempotency_key":"e2e-01M3EA4JPXC3E39GZMHFH33H92","command_type":"upsert_work_session","status":"applied","result":{"id":"01M3EA4JPXQPVQDPGEA3YVFPXV","revision":1,"status":"applied"}}]}: expected { http: 200, result: 'applied', …(1) } to deeply equal { http: 200, result: 'rejected', …(1) }
× tests/e2e/20-sync-defects.e2e.ts > Офлайн-синхронизация на реальной PostgreSQL > [SYNC-B] устройство не может утвердить табель (status=approved отклоняется) 32ms
→ HTTP 200 {"results":[{"idempotency_key":"e2e-01M3EA4JR4CST4PE643FC043K9","command_type":"upsert_timesheet_entry","status":"applied","result":{"id":"01M3EA4JR43V65BMVJVX5DHSV0","revision":1,"status":"applied"}}]}: expected { result: 'applied', …(1) } to deeply equal { result: 'rejected', stored: null }
× tests/e2e/20-sync-defects.e2e.ts > Офлайн-синхронизация на реальной PostgreSQL > [SYNC-C0] начальная загрузка (pull без курсора) работает на реальной БД (200) 39ms
→ HTTP 500 {"statusCode":500,"code":"42703","error":"Internal Server Error","message":"column \"status\" does not exist"}: expected 500 to be 200 // Object.is equality
× tests/e2e/20-sync-defects.e2e.ts > Офлайн-синхронизация на реальной PostgreSQL > [SYNC-C] начальная загрузка >100 сущностей отдаёт все сущности постранично 55ms
→ HTTP 500 {"statusCode":500,"code":"42703","error":"Internal Server Error","message":"column \"status\" does not exist"}: expected 500 to be 200 // Object.is equality
× tests/e2e/20-sync-defects.e2e.ts > Офлайн-синхронизация на реальной PostgreSQL > [SYNC-D] изменения, сделанные через портал, попадают в инкрементальный pull 41ms
→ expected false to be true // Object.is equality
× tests/e2e/20-sync-defects.e2e.ts > Офлайн-синхронизация на реальной PostgreSQL > [SYNC-E] отметка уволенного сотрудника через sync отклоняется 24ms
→ HTTP 200 {"results":[{"idempotency_key":"e2e-01M3EA4JXB10Y1W7WAT3QXPJXR","command_type":"attendance_event","status":"applied","result":{"event_id":"01M3EA4JXD7TMSBAMMAK6HMXK2","status":"accepted"}}]}: expected 'applied' to be 'rejected' // Object.is equality
× tests/e2e/20-sync-defects.e2e.ts > Офлайн-синхронизация на реальной PostgreSQL > [SYNC-F] отметка через sync сохраняет точку устройства; чужая точка в payload отклоняется 42ms
→ HTTP 200 {"results":[{"idempotency_key":"e2e-01M3EA4JYFDZ0AAZYVEF6G72T2","command_type":"attendance_event","status":"applied","result":{"event_id":"01M3EA4JYH12S10KQ4NWN232FM","status":"accepted"}},{"idempotency_key":"e2e-01M3EA4JYFDXJXPH923SG1E832","command_type":"attendance_event","status":"applied","resul…: expected { Object (ownOrgUnit, foreign) } to deeply equal { …(2) }
× tests/e2e/20-sync-defects.e2e.ts > Офлайн-синхронизация на реальной PostgreSQL > [SYNC-G] битая команда в пакете отклоняется по отдельности, остальные применяются (не 500) 21ms
→ HTTP 500 {"statusCode":500,"code":"22007","error":"Internal Server Error","message":"invalid input syntax for type timestamp with time zone: \"e2e-not-a-timestamp\""}: expected { http: 500, statuses: [] } to deeply equal { http: 200, statuses: [ …(2) ] }
× tests/e2e/20-sync-defects.e2e.ts > Офлайн-синхронизация на реальной PostgreSQL > [SYNC-H] регистрация устройства по cookie с чужим Origin отклоняется (CSRF, 403) 9ms
→ HTTP 201 {"device":{"id":"01M3EA4JZKVD04KCMNCGGW1EAH","tenant_id":"01M3EA4DJNEY3HRCYSBSX43XW0","device_code":"E2E-5J7EDPFQV6","name":"E2E-CSRF-терминал-34e01a","token_hint":"kstaff_dev_01M3E","status":"active","allowed_scopes":["attendance:sync"],"registered_at":"2026-09-26T07:33:38.935Z","created_by":"01M3E…: expected 201 to be 403 // Object.is equality
× tests/e2e/20-sync-defects.e2e.ts > Офлайн-синхронизация на реальной PostgreSQL > [SYNC-I] без настроенных токенов анонимный запрос не регистрирует устройство (401) 7ms
→ HTTP 201 {"device":{"id":"01M3EA4JZWQF4S3SXWCX77J9ST","tenant_id":"01M3EA4DJNEY3HRCYSBSX43XW0","device_code":"E2E-KS5GF18F83","name":"E2E-Анонимный терминал-75d36d","token_hint":"kstaff_dev_01M3E","status":"active","allowed_scopes":["attendance:sync"],"registered_at":"2026-09-26T07:33:38.940Z","created_by":"…: expected 201 to be 401 // Object.is equality
× tests/e2e/20-sync-defects.e2e.ts > Офлайн-синхронизация на реальной PostgreSQL > [SYNC-J] отметка через sync вне окна времени (2001 год) отклоняется, как в POST /attendance/events 24ms
→ HTTP 200 {"results":[{"idempotency_key":"e2e-01M3EA4K0JQNMDJTDAM1PZ1J8K","command_type":"attendance_event","status":"applied","result":{"event_id":"01M3EA4K0NB5RS8GACHWC7N2C2","status":"accepted"}}]}: expected 'applied' to be 'rejected' // Object.is equality
× tests/e2e/20-sync-defects.e2e.ts > Офлайн-синхронизация на реальной PostgreSQL > [SYNC-K] ack курсора из будущего (больше выданного сервером) отклоняется (400) 7ms
→ HTTP 200 {"acknowledged":true,"cursor":"cur_999999999_fd163ccb01a6","acknowledged_at":"2026-09-26T07:33:38.972Z"}: expected 200 to be 400 // Object.is equality
✓ tests/e2e/20-sync-defects.e2e.ts > Офлайн-синхронизация на реальной PostgreSQL > [SYNC-L] отозванное устройство не может синхронизироваться (403) — позитивный контроль 8ms
✓ tests/e2e/00-harness.e2e.ts > HARNESS: реальный сервер + PostgreSQL 17 > [HARNESS] сервер отвечает live/ready (ready проверяет БД) 32ms
✓ tests/e2e/00-harness.e2e.ts > HARNESS: реальный сервер + PostgreSQL 17 > [HARNESS] БД: PostgreSQL 17, приложение под ролью без SUPERUSER/BYPASSRLS, мигратор без SUPERUSER 14ms
✓ tests/e2e/00-harness.e2e.ts > HARNESS: реальный сервер + PostgreSQL 17 > [HARNESS] миграции применены мигратором, app-роль не видит schema_migrations 14ms
✓ tests/e2e/00-harness.e2e.ts > HARNESS: реальный сервер + PostgreSQL 17 > [HARNESS] токены: без токена 401, случайный токен 401, manager-токен 200 14ms
✓ tests/e2e/00-harness.e2e.ts > HARNESS: реальный сервер + PostgreSQL 17 > [HARNESS] терминал: отметка сотрудника из импорта принимается (201) 25ms
✓ tests/e2e/00-harness.e2e.ts > HARNESS: реальный сервер + PostgreSQL 17 > [CTRL] RBAC: терминал не видит портал и не создаёт сотрудников; сотрудник видит только себя 24ms
✓ tests/e2e/00-harness.e2e.ts > HARNESS: реальный сервер + PostgreSQL 17 > [CTRL] уволенный сотрудник теряет доступ (401) 23ms
× tests/e2e/40-compose-pilot.e2e.ts > deploy/pilot/compose.yaml > [PILOT-1] все подставляемые переменные обязательны: ${VAR:?сообщение} 6ms
→ переменные без :? — пустое значение молча попадёт в контейнер: expected [ 'STANDALONE_MANAGER_TOKEN', …(13) ] to deeply equal []
× tests/e2e/40-compose-pilot.e2e.ts > deploy/pilot/compose.yaml > [PILOT-2] ни один сервис не запускается с NODE_ENV=development 1ms
→ expected [ 'app' ] to deeply equal []
✓ tests/e2e/40-compose-pilot.e2e.ts > deploy/pilot/compose.yaml > [PILOT-3] у сервиса app нет учётных данных мигратора 1ms
⎯⎯⎯⎯⎯⎯ Failed Tests 38 ⎯⎯⎯⎯⎯⎯⎯
FAIL tests/e2e/10-core-defects.e2e.ts > Основной API на реальной PostgreSQL > [N1] менеджер создаёт сотрудника через POST /employees → 201
AssertionError: HTTP 500 {"statusCode":500,"code":"42P08","error":"Internal Server Error","message":"inconsistent types deduced for parameter $2"}: expected 500 to be 201 // Object.is equality
- Expected
+ Received
- 201
+ 500
❯ tests/e2e/10-core-defects.e2e.ts:55:57
53| },
54| });
55| expect(response.status, describeResponse(response)).toBe(201);
| ^
56| const rows = await dbQuery('SELECT id FROM staff_employees WHERE p…
57| number,
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/38]⎯
FAIL tests/e2e/10-core-defects.e2e.ts > Основной API на реальной PostgreSQL > [N2] подтверждение смены, расчёт и утверждение зарплаты проходят (200) и начисляют 8 ч × ставка
AssertionError: подтверждение смены: HTTP 500 {"statusCode":500,"code":"23502","error":"Internal Server Error","message":"null value in column \"occurred_at\" of relation \"staff_outbox\" violates not-null constraint"}: expected 500 to be 200 // Object.is equality
- Expected
+ Received
- 200
+ 500
❯ tests/e2e/10-core-defects.e2e.ts:73:81
71| body: { status: 'confirmed' },
72| });
73| expect(confirm.status, `подтверждение смены: ${describeResponse(co…
| ^
74|
75| const calculate = await api('POST', '/payroll/calculate', {
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/38]⎯
FAIL tests/e2e/10-core-defects.e2e.ts > Основной API на реальной PostgreSQL > [N4] пересекающиеся расчёты не начисляют одну смену дважды
AssertionError: подтверждение смены (предусловие, см. N2): HTTP 500 {"statusCode":500,"code":"23502","error":"Internal Server Error","message":"null value in column \"occurred_at\" of relation \"staff_outbox\" violates not-null constraint"}: expected 500 to be 200 // Object.is equality
- Expected
+ Received
- 200
+ 500
❯ tests/e2e/10-core-defects.e2e.ts:104:7
102| confirm.status,
103| `подтверждение смены (предусловие, см. N2): ${describeResponse(c…
104| ).toBe(200);
| ^
105|
106| const month = monthOf(day);
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/38]⎯
FAIL tests/e2e/10-core-defects.e2e.ts > Основной API на реальной PostgreSQL > [N3] push-подписка на внутренний адрес отклоняется, сервер не подключается к 127.0.0.1
AssertionError: подписка: HTTP 201 {"id":"01M3EA4FH3FG4Q3YEKXYQ8Y4Q5","endpoint":"https://127.0.0.1:36027/e2e-internal-admin"}: expected { status: 201, connections: 1 } to deeply equal { status: toSatisfy{…}, …(1) }
- Expected
+ Received
{
- "connections": 0,
- "status": toSatisfy<[Function anonymous]>,
+ "connections": 1,
+ "status": 201,
}
❯ tests/e2e/10-core-defects.e2e.ts:161:9
159| { status: subscribe.status, connections },
160| `подписка: ${describeResponse(subscribe)}`,
161| ).toEqual({
| ^
162| status: expect.toSatisfy((s: number) => s === 400 || s === 422…
163| connections: 0,
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[4/38]⎯
FAIL tests/e2e/10-core-defects.e2e.ts > Основной API на реальной PostgreSQL > [N9] чужой push-endpoint нельзя перепривязать к другому сотруднику
AssertionError: HTTP 201 {"id":"01M3EA4J0JSPFRDF22J6E2CEY1","endpoint":"https://fcm.googleapis.com/fcm/send/e2e-d464594734e6ddf8569ec571f5832dbf"}: expected { status: 201, …(1) } to deeply equal { status: toSatisfy{…}, …(1) }
- Expected
+ Received
{
"owner": [
- "01M3EA4HZSY982GYTPDAVP0Z0Q",
+ "01M3EA4HZTSZ12MKP3SV134FKS",
],
- "status": toSatisfy<[Function anonymous]>,
+ "status": 201,
}
❯ tests/e2e/10-core-defects.e2e.ts:191:7
189| { status: second.status, owner: owner.map((row) => row.employee_…
190| describeResponse(second),
191| ).toEqual({
| ^
192| status: expect.toSatisfy((s: number) => s === 409 || s === 403),
193| owner: [employeeA!.id],
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[5/38]⎯
FAIL tests/e2e/10-core-defects.e2e.ts > Основной API на реальной PostgreSQL > [N12] PUT pay-assignment отвергает лишние поля и нереальные суммы (400)
AssertionError: HTTP 200 {"id":"01M3EA4J1PHXWHFMAMYYVRMK3E","employee_id":"01M3EA4J151B1H9D5AKAR29AN9","org_unit_id":null,"pay_rule_id":"01M3EA4FCDRQSS6V8H5Q6XS615","pay_rule_code":"hourly","pay_rule_name":"Почасовая (стажёр, дополнительный сотрудник)","rate_minor":12345,"percent_bp":0,"guarantee_minor":0,"pool_weight_bp":0… | HTTP 200 {"id":"01M3EA4J1PHXWHFMAMYYVRMK3E","employee_id":"01M3EA4J151B1H9D5AKAR29AN9","org_unit_id":null,"pay_rule_id":"01M3EA4FCDRQSS6V8H5Q6XS615","pay_rule_code":"hourly","pay_rule_name":"Почасовая (стажёр, дополнительный сотрудник)","rate_minor":9007199254740991,"percent_bp":0,"guarantee_minor":900719925…: expected { extra: 200, huge: 200 } to deeply equal { extra: 400, huge: 400 }
- Expected
+ Received
{
- "extra": 400,
- "huge": 400,
+ "extra": 200,
+ "huge": 200,
}
❯ tests/e2e/10-core-defects.e2e.ts:215:7
213| { extra: extra.status, huge: huge.status },
214| `${describeResponse(extra)} | ${describeResponse(huge)}`,
215| ).toEqual({
| ^
216| extra: 400,
217| huge: 400,
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[6/38]⎯
FAIL tests/e2e/10-core-defects.e2e.ts > Основной API на реальной PostgreSQL > [N10] терминальный токен не читает справочники профессий и точек (403)
AssertionError: expected { professions: 200, units: 200 } to deeply equal { professions: 403, units: 403 }
- Expected
+ Received
{
- "professions": 403,
- "units": 403,
+ "professions": 200,
+ "units": 200,
}
❯ tests/e2e/10-core-defects.e2e.ts:224:70
222| const professions = await api('GET', '/professions', { token: ctx(…
223| const units = await api('GET', '/org-units', { token: ctx().termin…
224| expect({ professions: professions.status, units: units.status }).t…
| ^
225| professions: 403,
226| units: 403,
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[7/38]⎯
FAIL tests/e2e/10-core-defects.e2e.ts > Основной API на реальной PostgreSQL > [F2] standalone без токенов не выдаёт права менеджера анонимному запросу (401)
AssertionError: expected { portal: 200, payroll: 200, …(1) } to deeply equal { portal: 401, payroll: 401, …(1) }
- Expected
+ Received
{
- "payroll": 401,
- "portal": 401,
- "write": 401,
+ "payroll": 200,
+ "portal": 200,
+ "write": 201,
}
❯ tests/e2e/10-core-defects.e2e.ts:237:85
235| body: { name: synthetic('Профессия') },
236| });
237| expect({ portal: portal.status, payroll: payroll.status, write: wr…
| ^
238| portal: 401,
239| payroll: 401,
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[8/38]⎯
FAIL tests/e2e/10-core-defects.e2e.ts > Основной API на реальной PostgreSQL > [F3] cookie сессии не содержит сырой токен и отзывается при выходе
AssertionError: expected { rawTokenInCookie: true, …(1) } to deeply equal { rawTokenInCookie: false, …(1) }
- Expected
+ Received
{
- "afterLogout": 401,
- "rawTokenInCookie": false,
+ "afterLogout": 200,
+ "rawTokenInCookie": true,
}
❯ tests/e2e/10-core-defects.e2e.ts:255:91
253| expect(logout.status).toBe(204);
254| const reuse = await api('GET', '/portal', { headers: { cookie } });
255| expect({ rawTokenInCookie: value === ctx().managerToken, afterLogo…
| ^
256| rawTokenInCookie: false,
257| afterLogout: 401,
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[9/38]⎯
FAIL tests/e2e/10-core-defects.e2e.ts > Основной API на реальной PostgreSQL > [F10] мутация по cookie без Origin отклоняется (CSRF, 403)
AssertionError: HTTP 201 {"id":"01M3EA4J31CEM99Y9708PNVE90","code":"profession-01m3ea4j31cem99y9708pnve90","name":"E2E-CSRF-профессия-c30803","status":"active"}: expected 201 to be 403 // Object.is equality
- Expected
+ Received
- 403
+ 201
❯ tests/e2e/10-core-defects.e2e.ts:269:57
267| body: { name: synthetic('CSRF-профессия') },
268| });
269| expect(response.status, describeResponse(response)).toBe(403);
| ^
270| });
271|
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[10/38]⎯
FAIL tests/e2e/10-core-defects.e2e.ts > Основной API на реальной PostgreSQL > [F7] повтор Idempotency-Key с другим телом не создаёт второе событие
AssertionError: expected { first: 201, secondCreated: true } to deeply equal { first: 201, secondCreated: false }
- Expected
+ Received
{
"first": 201,
- "secondCreated": false,
+ "secondCreated": true,
}
❯ tests/e2e/10-core-defects.e2e.ts:288:75
286| const first = await send(`e2e-${randomUlid()}`);
287| const second = await send(`e2e-${randomUlid()}`);
288| expect({ first: first.status, secondCreated: second.status === 201…
| ^
289| first: 201,
290| secondCreated: false,
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[11/38]⎯
FAIL tests/e2e/10-core-defects.e2e.ts > Основной API на реальной PostgreSQL > [N11] импорт с неверным kind начисления → 400, а не 500
AssertionError: HTTP 500 {"statusCode":500,"code":"23514","error":"Internal Server Error","message":"new row for relation \"staff_payroll_ledger\" violates check constraint \"staff_payroll_ledger_kind_check\""}: expected 500 to be 400 // Object.is equality
- Expected
+ Received
- 400
+ 500
❯ tests/e2e/10-core-defects.e2e.ts:311:57
309| },
310| });
311| expect(response.status, describeResponse(response)).toBe(400);
| ^
312| });
313|
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[12/38]⎯
FAIL tests/e2e/10-core-defects.e2e.ts > Основной API на реальной PostgreSQL > [F4] перебор ключей входа ограничивается (429 не позже 30 неверных попыток)
AssertionError: статусы: 401: expected false to be true // Object.is equality
- Expected
+ Received
- true
+ false
❯ tests/e2e/10-core-defects.e2e.ts:324:84
322| if (response.status === 429) break;
323| }
324| expect(statuses.includes(429), `статусы: ${[...new Set(statuses)].…
| ^
325| });
326| });
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[13/38]⎯
FAIL tests/e2e/20-sync-defects.e2e.ts > Офлайн-синхронизация на реальной PostgreSQL > [SYNC-A] устройство не может само подтвердить смену (status=confirmed отклоняется)
AssertionError: HTTP 200 {"results":[{"idempotency_key":"e2e-01M3EA4JPXC3E39GZMHFH33H92","command_type":"upsert_work_session","status":"applied","result":{"id":"01M3EA4JPXQPVQDPGEA3YVFPXV","revision":1,"status":"applied"}}]}: expected { http: 200, result: 'applied', …(1) } to deeply equal { http: 200, result: 'rejected', …(1) }
- Expected
+ Received
{
"http": 200,
- "result": "rejected",
- "stored": null,
+ "result": "applied",
+ "stored": "confirmed",
}
❯ tests/e2e/20-sync-defects.e2e.ts:86:7
84| },
85| describeResponse(response),
86| ).toEqual({ http: 200, result: 'rejected', stored: null });
| ^
87| });
88|
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[14/38]⎯
FAIL tests/e2e/20-sync-defects.e2e.ts > Офлайн-синхронизация на реальной PostgreSQL > [SYNC-B] устройство не может утвердить табель (status=approved отклоняется)
AssertionError: HTTP 200 {"results":[{"idempotency_key":"e2e-01M3EA4JR4CST4PE643FC043K9","command_type":"upsert_timesheet_entry","status":"applied","result":{"id":"01M3EA4JR43V65BMVJVX5DHSV0","revision":1,"status":"applied"}}]}: expected { result: 'applied', …(1) } to deeply equal { result: 'rejected', stored: null }
- Expected
+ Received
{
- "result": "rejected",
- "stored": null,
+ "result": "applied",
+ "stored": "approved",
}
❯ tests/e2e/20-sync-defects.e2e.ts:109:7
107| { result: response.body?.results?.[0]?.status, stored: stored[0]…
108| describeResponse(response),
109| ).toEqual({ result: 'rejected', stored: null });
| ^
110| });
111|
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[15/38]⎯
FAIL tests/e2e/20-sync-defects.e2e.ts > Офлайн-синхронизация на реальной PostgreSQL > [SYNC-C0] начальная загрузка (pull без курсора) работает на реальной БД (200)
AssertionError: HTTP 500 {"statusCode":500,"code":"42703","error":"Internal Server Error","message":"column \"status\" does not exist"}: expected 500 to be 200 // Object.is equality
- Expected
+ Received
- 200
+ 500
❯ tests/e2e/20-sync-defects.e2e.ts:120:57
118| body: { cursor: null },
119| });
120| expect(response.status, describeResponse(response)).toBe(200);
| ^
121| const types = new Set(
122| (response.body.changes as Array<{ entity_type: string }>).map((c…
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[16/38]⎯
FAIL tests/e2e/20-sync-defects.e2e.ts > Офлайн-синхронизация на реальной PostgreSQL > [SYNC-C] начальная загрузка >100 сущностей отдаёт все сущности постранично
AssertionError: HTTP 500 {"statusCode":500,"code":"42703","error":"Internal Server Error","message":"column \"status\" does not exist"}: expected 500 to be 200 // Object.is equality
- Expected
+ Received
- 200
+ 500
❯ pullAll tests/e2e/20-sync-defects.e2e.ts:51:57
49| body: { cursor: next, limit: 100 },
50| });
51| expect(response.status, describeResponse(response)).toBe(200);
| ^
52| changes.push(...(response.body.changes as Array<{ entity_type: str…
53| next = response.body.next_cursor as string;
❯ tests/e2e/20-sync-defects.e2e.ts:130:25
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[17/38]⎯
FAIL tests/e2e/20-sync-defects.e2e.ts > Офлайн-синхронизация на реальной PostgreSQL > [SYNC-D] изменения, сделанные через портал, попадают в инкрементальный pull
AssertionError: expected false to be true // Object.is equality
- Expected
+ Received
- true
+ false
❯ tests/e2e/20-sync-defects.e2e.ts:151:95
149| expect(patch.status, describeResponse(patch)).toBe(200);
150| const { changes } = await pullAll(device, cursor);
151| expect(changes.some((c) => c.entity_type === 'employee' && c.entit…
| ^
152| true,
153| );
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[18/38]⎯
FAIL tests/e2e/20-sync-defects.e2e.ts > Офлайн-синхронизация на реальной PostgreSQL > [SYNC-E] отметка уволенного сотрудника через sync отклоняется
AssertionError: HTTP 200 {"results":[{"idempotency_key":"e2e-01M3EA4JXB10Y1W7WAT3QXPJXR","command_type":"attendance_event","status":"applied","result":{"event_id":"01M3EA4JXD7TMSBAMMAK6HMXK2","status":"accepted"}}]}: expected 'applied' to be 'rejected' // Object.is equality
Expected: "rejected"
Received: "applied"
❯ tests/e2e/20-sync-defects.e2e.ts:171:77
169| }),
170| ]);
171| expect(response.body?.results?.[0]?.status, describeResponse(respo…
| ^
172| });
173|
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[19/38]⎯
FAIL tests/e2e/20-sync-defects.e2e.ts > Офлайн-синхронизация на реальной PostgreSQL > [SYNC-F] отметка через sync сохраняет точку устройства; чужая точка в payload отклоняется
AssertionError: HTTP 200 {"results":[{"idempotency_key":"e2e-01M3EA4JYFDZ0AAZYVEF6G72T2","command_type":"attendance_event","status":"applied","result":{"event_id":"01M3EA4JYH12S10KQ4NWN232FM","status":"accepted"}},{"idempotency_key":"e2e-01M3EA4JYFDXJXPH923SG1E832","command_type":"attendance_event","status":"applied","resul…: expected { Object (ownOrgUnit, foreign) } to deeply equal { …(2) }
- Expected
+ Received
{
- "foreign": "rejected",
- "ownOrgUnit": "01M3EA4JXPPT6RXP11RKV1VY20",
+ "foreign": "applied",
+ "ownOrgUnit": null,
}
❯ tests/e2e/20-sync-defects.e2e.ts:202:7
200| },
201| describeResponse(response),
202| ).toEqual({ ownOrgUnit: unitA!.id, foreign: 'rejected' });
| ^
203| });
204|
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[20/38]⎯
FAIL tests/e2e/20-sync-defects.e2e.ts > Офлайн-синхронизация на реальной PostgreSQL > [SYNC-G] битая команда в пакете отклоняется по отдельности, остальные применяются (не 500)
AssertionError: HTTP 500 {"statusCode":500,"code":"22007","error":"Internal Server Error","message":"invalid input syntax for type timestamp with time zone: \"e2e-not-a-timestamp\""}: expected { http: 500, statuses: [] } to deeply equal { http: 200, statuses: [ …(2) ] }
- Expected
+ Received
{
- "http": 200,
- "statuses": [
- "applied",
- "rejected",
- ],
+ "http": 500,
+ "statuses": [],
}
❯ tests/e2e/20-sync-defects.e2e.ts:225:7
223| },
224| describeResponse(response),
225| ).toEqual({ http: 200, statuses: ['applied', 'rejected'] });
| ^
226| });
227|
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[21/38]⎯
FAIL tests/e2e/20-sync-defects.e2e.ts > Офлайн-синхронизация на реальной PostgreSQL > [SYNC-H] регистрация устройства по cookie с чужим Origin отклоняется (CSRF, 403)
AssertionError: HTTP 201 {"device":{"id":"01M3EA4JZKVD04KCMNCGGW1EAH","tenant_id":"01M3EA4DJNEY3HRCYSBSX43XW0","device_code":"E2E-5J7EDPFQV6","name":"E2E-CSRF-терминал-34e01a","token_hint":"kstaff_dev_01M3E","status":"active","allowed_scopes":["attendance:sync"],"registered_at":"2026-09-26T07:33:38.935Z","created_by":"01M3E…: expected 201 to be 403 // Object.is equality
- Expected
+ Received
- 403
+ 201
❯ tests/e2e/20-sync-defects.e2e.ts:236:57
234| body: { device_code: `E2E-${randomUlid().slice(-10)}`, name: syn…
235| });
236| expect(response.status, describeResponse(response)).toBe(403);
| ^
237| });
238|
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[22/38]⎯
FAIL tests/e2e/20-sync-defects.e2e.ts > Офлайн-синхронизация на реальной PostgreSQL > [SYNC-I] без настроенных токенов анонимный запрос не регистрирует устройство (401)
AssertionError: HTTP 201 {"device":{"id":"01M3EA4JZWQF4S3SXWCX77J9ST","tenant_id":"01M3EA4DJNEY3HRCYSBSX43XW0","device_code":"E2E-KS5GF18F83","name":"E2E-Анонимный терминал-75d36d","token_hint":"kstaff_dev_01M3E","status":"active","allowed_scopes":["attendance:sync"],"registered_at":"2026-09-26T07:33:38.940Z","created_by":"…: expected 201 to be 401 // Object.is equality
- Expected
+ Received
- 401
+ 201
❯ tests/e2e/20-sync-defects.e2e.ts:247:57
245| },
246| });
247| expect(response.status, describeResponse(response)).toBe(401);
| ^
248| });
249|
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[23/38]⎯
FAIL tests/e2e/20-sync-defects.e2e.ts > Офлайн-синхронизация на реальной PostgreSQL > [SYNC-J] отметка через sync вне окна времени (2001 год) отклоняется, как в POST /attendance/events
AssertionError: HTTP 200 {"results":[{"idempotency_key":"e2e-01M3EA4K0JQNMDJTDAM1PZ1J8K","command_type":"attendance_event","status":"applied","result":{"event_id":"01M3EA4K0NB5RS8GACHWC7N2C2","status":"accepted"}}]}: expected 'applied' to be 'rejected' // Object.is equality
Expected: "rejected"
Received: "applied"
❯ tests/e2e/20-sync-defects.e2e.ts:260:77
258| }),
259| ]);
260| expect(response.body?.results?.[0]?.status, describeResponse(respo…
| ^
261| });
262|
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[24/38]⎯
FAIL tests/e2e/20-sync-defects.e2e.ts > Офлайн-синхронизация на реальной PostgreSQL > [SYNC-K] ack курсора из будущего (больше выданного сервером) отклоняется (400)
AssertionError: HTTP 200 {"acknowledged":true,"cursor":"cur_999999999_fd163ccb01a6","acknowledged_at":"2026-09-26T07:33:38.972Z"}: expected 200 to be 400 // Object.is equality
- Expected
+ Received
- 400
+ 200
❯ tests/e2e/20-sync-defects.e2e.ts:275:57
273| body: { cursor: `cur_${seq}_${hash}` },
274| });
275| expect(response.status, describeResponse(response)).toBe(400);
| ^
276| });
277|
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[25/38]⎯
FAIL tests/e2e/30-owner-decisions.e2e.ts > Решения владельца: роли, пакеты зарплаты, согласие на биометрию > [OWN-01] менеджер точки A запускает расчёт для точки B → 403
AssertionError: C1 POST /access-grants (point_manager): HTTP 404 {"message":"Route POST:/api/v1/staff/access-grants not found","error":"Not Found","statusCode":404}: expected 404 to be 201 // Object.is equality
- Expected
+ Received
- 201
+ 404
❯ grant tests/e2e/30-owner-decisions.e2e.ts:28:94
26| body: { role, org_unit_ids: orgUnitIds, display_name: synthetic(ro…
27| });
28| expect(response.status, `C1 POST /access-grants (${role}): ${describ…
| ^
29| 201,
30| );
❯ tests/e2e/30-owner-decisions.e2e.ts:94:22
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[26/38]⎯
FAIL tests/e2e/30-owner-decisions.e2e.ts > Решения владельца: роли, пакеты зарплаты, согласие на биометрию > [OWN-02] менеджер точки отправляет пакет на согласование → 403
AssertionError: C1 POST /access-grants (point_manager): HTTP 404 {"message":"Route POST:/api/v1/staff/access-grants not found","error":"Not Found","statusCode":404}: expected 404 to be 201 // Object.is equality
- Expected
+ Received
- 201
+ 404
❯ grant tests/e2e/30-owner-decisions.e2e.ts:28:94
26| body: { role, org_unit_ids: orgUnitIds, display_name: synthetic(ro…
27| });
28| expect(response.status, `C1 POST /access-grants (${role}): ${describ…
| ^
29| 201,
30| );
❯ tests/e2e/30-owner-decisions.e2e.ts:105:22
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[27/38]⎯
FAIL tests/e2e/30-owner-decisions.e2e.ts > Решения владельца: роли, пакеты зарплаты, согласие на биометрию > [OWN-03] бухгалтер отправляет пакет → успех, каждый директор получает ровно одно уведомление
AssertionError: C1 POST /access-grants (accountant): HTTP 404 {"message":"Route POST:/api/v1/staff/access-grants not found","error":"Not Found","statusCode":404}: expected 404 to be 201 // Object.is equality
- Expected
+ Received
- 201
+ 404
❯ grant tests/e2e/30-owner-decisions.e2e.ts:28:94
26| body: { role, org_unit_ids: orgUnitIds, display_name: synthetic(ro…
27| });
28| expect(response.status, `C1 POST /access-grants (${role}): ${describ…
| ^
29| 201,
30| );
❯ tests/e2e/30-owner-decisions.e2e.ts:118:24
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[28/38]⎯
FAIL tests/e2e/30-owner-decisions.e2e.ts > Решения владельца: роли, пакеты зарплаты, согласие на биометрию > [OWN-04] бухгалтер пытается подписать пакет → 403
AssertionError: C1 POST /access-grants (accountant): HTTP 404 {"message":"Route POST:/api/v1/staff/access-grants not found","error":"Not Found","statusCode":404}: expected 404 to be 201 // Object.is equality
- Expected
+ Received
- 201
+ 404
❯ grant tests/e2e/30-owner-decisions.e2e.ts:28:94
26| body: { role, org_unit_ids: orgUnitIds, display_name: synthetic(ro…
27| });
28| expect(response.status, `C1 POST /access-grants (${role}): ${describ…
| ^
29| 201,
30| );
❯ tests/e2e/30-owner-decisions.e2e.ts:133:24
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[29/38]⎯
FAIL tests/e2e/30-owner-decisions.e2e.ts > Решения владельца: роли, пакеты зарплаты, согласие на биометрию > [OWN-05] до подписи директора в ledger нет начислений и выплат
AssertionError: C1 POST /access-grants (accountant): HTTP 404 {"message":"Route POST:/api/v1/staff/access-grants not found","error":"Not Found","statusCode":404}: expected 404 to be 201 // Object.is equality
- Expected
+ Received
- 201
+ 404
❯ grant tests/e2e/30-owner-decisions.e2e.ts:28:94
26| body: { role, org_unit_ids: orgUnitIds, display_name: synthetic(ro…
27| });
28| expect(response.status, `C1 POST /access-grants (${role}): ${describ…
| ^
29| 201,
30| );
❯ tests/e2e/30-owner-decisions.e2e.ts:141:24
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[30/38]⎯
FAIL tests/e2e/30-owner-decisions.e2e.ts > Решения владельца: роли, пакеты зарплаты, согласие на биометрию > [OWN-06] подпись директора → начисления и выплаты в ledger, пакет и смены неизменяемы
AssertionError: C1 POST /access-grants (accountant): HTTP 404 {"message":"Route POST:/api/v1/staff/access-grants not found","error":"Not Found","statusCode":404}: expected 404 to be 201 // Object.is equality
- Expected
+ Received
- 201
+ 404
❯ grant tests/e2e/30-owner-decisions.e2e.ts:28:94
26| body: { role, org_unit_ids: orgUnitIds, display_name: synthetic(ro…
27| });
28| expect(response.status, `C1 POST /access-grants (${role}): ${describ…
| ^
29| 201,
30| );
❯ tests/e2e/30-owner-decisions.e2e.ts:148:24
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[31/38]⎯
FAIL tests/e2e/30-owner-decisions.e2e.ts > Решения владельца: роли, пакеты зарплаты, согласие на биометрию > [OWN-07] смена изменена после отправки → подписать нельзя (409), пакет возвращён бухгалтеру
AssertionError: C1 POST /access-grants (accountant): HTTP 404 {"message":"Route POST:/api/v1/staff/access-grants not found","error":"Not Found","statusCode":404}: expected 404 to be 201 // Object.is equality
- Expected
+ Received
- 201
+ 404
❯ grant tests/e2e/30-owner-decisions.e2e.ts:28:94
26| body: { role, org_unit_ids: orgUnitIds, display_name: synthetic(ro…
27| });
28| expect(response.status, `C1 POST /access-grants (${role}): ${describ…
| ^
29| 201,
30| );
❯ tests/e2e/30-owner-decisions.e2e.ts:173:24
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[32/38]⎯
FAIL tests/e2e/30-owner-decisions.e2e.ts > Решения владельца: роли, пакеты зарплаты, согласие на биометрию > [OWN-08] возврат с комментарием → пакет у бухгалтера, комментарий сохранён
AssertionError: C1 POST /access-grants (accountant): HTTP 404 {"message":"Route POST:/api/v1/staff/access-grants not found","error":"Not Found","statusCode":404}: expected 404 to be 201 // Object.is equality
- Expected
+ Received
- 201
+ 404
❯ grant tests/e2e/30-owner-decisions.e2e.ts:28:94
26| body: { role, org_unit_ids: orgUnitIds, display_name: synthetic(ro…
27| });
28| expect(response.status, `C1 POST /access-grants (${role}): ${describ…
| ^
29| 201,
30| );
❯ tests/e2e/30-owner-decisions.e2e.ts:195:24
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[33/38]⎯
FAIL tests/e2e/30-owner-decisions.e2e.ts > Решения владельца: роли, пакеты зарплаты, согласие на биометрию > [OWN-09] сотрудник точки A отработал смену в точке B → смена попадает в зарплату точки B
AssertionError: C1 POST /access-grants (accountant): HTTP 404 {"message":"Route POST:/api/v1/staff/access-grants not found","error":"Not Found","statusCode":404}: expected 404 to be 201 // Object.is equality
- Expected
+ Received
- 201
+ 404
❯ grant tests/e2e/30-owner-decisions.e2e.ts:28:94
26| body: { role, org_unit_ids: orgUnitIds, display_name: synthetic(ro…
27| });
28| expect(response.status, `C1 POST /access-grants (${role}): ${describ…
| ^
29| 201,
30| );
❯ tests/e2e/30-owner-decisions.e2e.ts:213:24
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[34/38]⎯
FAIL tests/e2e/30-owner-decisions.e2e.ts > Решения владельца: роли, пакеты зарплаты, согласие на биометрию > [OWN-10] сотрудник без согласия на биометрию → сервер сообщает терминалу «регистрация запрещена»
AssertionError: C3: HTTP 404 {"message":"Route GET:/api/v1/staff/terminal/employees/01M3EA4JGPPB7M0G78QQBQBJ1M/enrollment not found","error":"Not Found","statusCode":404}: expected 404 to be 200 // Object.is equality
- Expected
+ Received
- 200
+ 404
❯ tests/e2e/30-owner-decisions.e2e.ts:241:66
239| token: ctx().terminalToken,
240| });
241| expect(response.status, `C3: ${describeResponse(response)}`).toBe(…
| ^
242| expect(response.body).toMatchObject({ registration_allowed: false,…
243| });
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[35/38]⎯
FAIL tests/e2e/30-owner-decisions.e2e.ts > Решения владельца: роли, пакеты зарплаты, согласие на биометрию > [OWN-11] согласие отозвано → статус для терминала меняется
AssertionError: C3 PUT biometric-consent: HTTP 404 {"message":"Route PUT:/api/v1/staff/employees/01M3EA4JH57XZ361XBC2Z7SA6J/biometric-consent not found","error":"Not Found","statusCode":404}: expected 404 to be 200 // Object.is equality
- Expected
+ Received
- 200
+ 404
❯ tests/e2e/30-owner-decisions.e2e.ts:255:86
253| api('GET', `/terminal/employees/${employee!.id}/enrollment`, { t…
254| const granted = await setConsent('granted');
255| expect(granted.status, `C3 PUT biometric-consent: ${describeRespon…
| ^
256| const before = await enrollment();
257| const revoked = await setConsent('revoked');
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[36/38]⎯
FAIL tests/e2e/40-compose-pilot.e2e.ts > deploy/pilot/compose.yaml > [PILOT-1] все подставляемые переменные обязательны: ${VAR:?сообщение}
AssertionError: переменные без :? — пустое значение молча попадёт в контейнер: expected [ 'STANDALONE_MANAGER_TOKEN', …(13) ] to deeply equal []
- Expected
+ Received
- []
+ [
+ "STANDALONE_MANAGER_TOKEN",
+ "STANDALONE_TERMINAL_TOKEN",
+ "STANDALONE_TENANT_ID",
+ "STANDALONE_ACTOR_ID",
+ "STANDALONE_TERMINAL_ACTOR_ID",
+ "VAPID_PUBLIC_KEY",
+ "VAPID_PRIVATE_KEY",
+ "VAPID_SUBJECT",
+ "POSTGRES_APP_PASSWORD",
+ "HTTPS_PFX_PASSPHRASE",
+ "POSTGRES_MIGRATION_PASSWORD",
+ "POSTGRES_APP_PASSWORD",
+ "POSTGRES_MIGRATION_PASSWORD",
+ "POSTGRES_MIGRATION_PASSWORD",
+ ]
❯ tests/e2e/40-compose-pilot.e2e.ts:34:87
32| expect(interpolations.length).toBeGreaterThan(0);
33| const optional = interpolations.filter((inner) => !/^[A-Z0-9_]+:\?…
34| expect(optional, 'переменные без :? — пустое значение молча попадё…
| ^
35| });
36|
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[37/38]⎯
FAIL tests/e2e/40-compose-pilot.e2e.ts > deploy/pilot/compose.yaml > [PILOT-2] ни один сервис не запускается с NODE_ENV=development
AssertionError: expected [ 'app' ] to deeply equal []
- Expected
+ Received
- []
+ [
+ "app",
+ ]
❯ tests/e2e/40-compose-pilot.e2e.ts:41:23
39| (service) => environmentOf(service).NODE_ENV === 'development',
40| );
41| expect(offenders).toEqual([]);
| ^
42| });
43|
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[38/38]⎯
Test Files 4 failed | 1 passed (5)
Tests 38 failed | 9 passed (47)
Start at 14:33:32
Duration 7.22s (tests 95%, import 3%, transform 2%)
JSON report written to /workspace/krona-staff/tests/e2e/evidence/e2e-results.json