$ 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