Зачем: 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 по этому замеру.
941 lines
59 KiB
Text
941 lines
59 KiB
Text
$ 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
|