docs(e2e): итоговый отчёт 2026-05-26 — 15 сценариев зелёные (124 шага)
Полная регрессия всех сценариев + 6 новых областей этой сессии (employees, roles, superadmin-console, platform-smtp, auth-password, security-edge). За день исправлено 4 бага: уволенный сотрудник логинится (P0), конкурентное проведение приёмки ломает инвариант (critical), refresh не гасится после ротации (high), change-owner принимал короткий reason (medium). Нереализованный по ТЗ функционал (отчёты/склад-документы/POS/permission-authz/login-ratelimit) зафиксирован как Logic gaps. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
888c8c28f0
commit
6098c03e1a
|
|
@ -1,13 +1,13 @@
|
||||||
# E2E report: auth-edge
|
# E2E report: auth-edge
|
||||||
|
|
||||||
Запущен: 2026-05-26T06:28:24.035Z
|
Запущен: 2026-05-26T07:02:20.238Z
|
||||||
Длительность: 3.9с
|
Длительность: 3.9с
|
||||||
|
|
||||||
**Итог:** 10 ✓ / 0 ✗ / 0 ⚠ / 0 ◯ (всего 10)
|
**Итог:** 10 ✓ / 0 ✗ / 0 ⚠ / 0 ◯ (всего 10)
|
||||||
|
|
||||||
## ✓ Step step01_bootstrap_admin: SuperAdmin создаёт орг + админа, получаем access+refresh
|
## ✓ Step step01_bootstrap_admin: SuperAdmin создаёт орг + админа, получаем access+refresh
|
||||||
|
|
||||||
Длительность: 1083мс
|
Длительность: 1168мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
## ✓ Step step02_refresh_token_works: Refresh: старый access обменивается на новые access+refresh
|
## ✓ Step step02_refresh_token_works: Refresh: старый access обменивается на новые access+refresh
|
||||||
|
|
||||||
Длительность: 348мс
|
Длительность: 331мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
|
|
||||||
## ✓ Step step03_refresh_token_rotates: После refresh — старый refresh-token больше не работает (rotation)
|
## ✓ Step step03_refresh_token_rotates: После refresh — старый refresh-token больше не работает (rotation)
|
||||||
|
|
||||||
Длительность: 124мс
|
Длительность: 127мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
|
|
||||||
## ✓ Step step04_invalid_refresh_rejected: Невалидный refresh-token возвращает 400 invalid_grant
|
## ✓ Step step04_invalid_refresh_rejected: Невалидный refresh-token возвращает 400 invalid_grant
|
||||||
|
|
||||||
Длительность: 81мс
|
Длительность: 79мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -43,7 +43,7 @@
|
||||||
|
|
||||||
## ✓ Step step05_tampered_jwt_rejected: JWT с подделанным org_id (изменён без переподписи) отшивается 401
|
## ✓ Step step05_tampered_jwt_rejected: JWT с подделанным org_id (изменён без переподписи) отшивается 401
|
||||||
|
|
||||||
Длительность: 19мс
|
Длительность: 24мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -59,7 +59,7 @@
|
||||||
|
|
||||||
## ✓ Step step07_deactivated_user_blocked: Деактивация User.IsActive=false: повторный login и refresh возвращают 400
|
## ✓ Step step07_deactivated_user_blocked: Деактивация User.IsActive=false: повторный login и refresh возвращают 400
|
||||||
|
|
||||||
Длительность: 520мс
|
Длительность: 528мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -68,7 +68,7 @@
|
||||||
|
|
||||||
## ✓ Step step08_archived_org_blocks_login: Архивная организация: login существующего админа возвращает 400 invalid_grant
|
## ✓ Step step08_archived_org_blocks_login: Архивная организация: login существующего админа возвращает 400 invalid_grant
|
||||||
|
|
||||||
Длительность: 391мс
|
Длительность: 373мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -77,7 +77,7 @@
|
||||||
|
|
||||||
## ✓ Step step09_duplicate_signup_blocked: Повторный signup с тем же email живой орги отвергается 400
|
## ✓ Step step09_duplicate_signup_blocked: Повторный signup с тем же email живой орги отвергается 400
|
||||||
|
|
||||||
Длительность: 49мс
|
Длительность: 39мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -85,12 +85,12 @@
|
||||||
|
|
||||||
## ✓ Step step10_orphan_signup_reactivates: Signup с email orphan-юзера (его org удалена) — реактивирует с новой org
|
## ✓ Step step10_orphan_signup_reactivates: Signup с email orphan-юзера (его org удалена) — реактивирует с новой org
|
||||||
|
|
||||||
Длительность: 1273мс
|
Длительность: 1179мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| api | First signup → 200/201 | ✓ status=200 {"organizationId":"55892550-bc32-4284-8841-643b709a7b62","email":"orphan-1779776904034@example.kz"} |
|
| api | First signup → 200/201 | ✓ status=200 {"organizationId":"c41c2728-ce20-4205-840e-d465bbee537e","email":"orphan-1779778940238@example.kz"} |
|
||||||
| api | Re-signup orphan email → 200/201 (реактивация) | ✓ status=200 {"organizationId":"4a4b198c-0441-4576-a013-bfb583c5d95d","email":"orphan-1779776904034@example.kz"} |
|
| api | Re-signup orphan email → 200/201 (реактивация) | ✓ status=200 {"organizationId":"14bd0fa0-fcc2-404a-948b-b6980972ffa3","email":"orphan-1779778940238@example.kz"} |
|
||||||
| api | Login после реактивации → 200 | ✓ status=200 |
|
| api | Login после реактивации → 200 | ✓ status=200 |
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
65
tests/e2e/reports/auth-password-2026-05-26T07-02-30-329Z.md
Normal file
65
tests/e2e/reports/auth-password-2026-05-26T07-02-30-329Z.md
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
# E2E report: auth-password
|
||||||
|
|
||||||
|
Запущен: 2026-05-26T07:02:27.683Z
|
||||||
|
Длительность: 1.0с
|
||||||
|
|
||||||
|
**Итог:** 6 ✓ / 0 ✗ / 0 ⚠ / 0 ◯ (всего 6)
|
||||||
|
|
||||||
|
## ✓ Step step01_bootstrap: Создать орг → известный email активного пользователя
|
||||||
|
|
||||||
|
Длительность: 804мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Орг + известный email готовы | ✓ pwd-1779778947683@example.kz |
|
||||||
|
|
||||||
|
## ✓ Step step02_forgot_unknown_200: forgot-password несуществующего email → 200 (анти-энумерация)
|
||||||
|
|
||||||
|
Длительность: 41мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | forgot несуществующего → 200 (анти-энумерация) | ✓ status=200 |
|
||||||
|
|
||||||
|
## ✓ Step step03_forgot_known_200: forgot-password существующего email → 200
|
||||||
|
|
||||||
|
Длительность: 70мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | forgot существующего → 200 | ✓ status=200 |
|
||||||
|
|
||||||
|
## ✓ Step step04_reset_bad_token: reset-password с битым токеном → 400
|
||||||
|
|
||||||
|
Длительность: 36мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | reset с битым токеном → 400 | ✓ status=400 |
|
||||||
|
|
||||||
|
## ✓ Step step05_reset_short_password: reset-password со слишком коротким паролем → 400
|
||||||
|
|
||||||
|
Длительность: 11мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | reset со слишком коротким паролем → 400 | ✓ status=400 |
|
||||||
|
|
||||||
|
## ✓ Step step06_forgot_rate_limited: Серия forgot с одного IP → появляется 429 (рейт-лимит)
|
||||||
|
|
||||||
|
Длительность: 46мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Серия forgot упирается в 429 (рейт-лимит) | ✓ statuses=200,429,429,429,429,429 |
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
- Passed: 6
|
||||||
|
- Failed: 0
|
||||||
|
- Warnings: 0
|
||||||
|
- Skipped: 0
|
||||||
|
|
||||||
|
## Critical bugs
|
||||||
|
|
||||||
|
Нет.
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
# E2E report: catalog-edge
|
# E2E report: catalog-edge
|
||||||
|
|
||||||
Запущен: 2026-05-26T06:28:32.030Z
|
Запущен: 2026-05-26T07:02:32.669Z
|
||||||
Длительность: 1.6с
|
Длительность: 1.7с
|
||||||
|
|
||||||
**Итог:** 12 ✓ / 0 ✗ / 0 ⚠ / 0 ◯ (всего 12)
|
**Итог:** 12 ✓ / 0 ✗ / 0 ⚠ / 0 ◯ (всего 12)
|
||||||
|
|
||||||
## ✓ Step step01_bootstrap: Орг + admin + lookups
|
## ✓ Step step01_bootstrap: Орг + admin + lookups
|
||||||
|
|
||||||
Длительность: 1128мс
|
Длительность: 1251мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
## ✓ Step step02_empty_product_name_rejected: POST product с пустым name → 400
|
## ✓ Step step02_empty_product_name_rejected: POST product с пустым name → 400
|
||||||
|
|
||||||
Длительность: 10мс
|
Длительность: 12мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
## ✓ Step step03_negative_price_rejected: POST product с отрицательной ценой amount=-100 → 400
|
## ✓ Step step03_negative_price_rejected: POST product с отрицательной ценой amount=-100 → 400
|
||||||
|
|
||||||
Длительность: 8мс
|
Длительность: 12мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -31,7 +31,7 @@
|
||||||
|
|
||||||
## ✓ Step step04_oversized_name_truncated_or_rejected: POST product с name > 500 символов → 400 (превышение maxLength)
|
## ✓ Step step04_oversized_name_truncated_or_rejected: POST product с name > 500 символов → 400 (превышение maxLength)
|
||||||
|
|
||||||
Длительность: 10мс
|
Длительность: 9мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
|
|
||||||
## ✓ Step step05_duplicate_product_article: POST второго product с тем же article → 4xx (если уникальный) или OK + проверка БД
|
## ✓ Step step05_duplicate_product_article: POST второго product с тем же article → 4xx (если уникальный) или OK + проверка БД
|
||||||
|
|
||||||
Длительность: 77мс
|
Длительность: 76мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -48,7 +48,7 @@
|
||||||
|
|
||||||
## ✓ Step step06_self_parent_group_rejected: POST product-group с parentId=собственный id (цикл) → 400
|
## ✓ Step step06_self_parent_group_rejected: POST product-group с parentId=собственный id (цикл) → 400
|
||||||
|
|
||||||
Длительность: 48мс
|
Длительность: 49мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -56,7 +56,7 @@
|
||||||
|
|
||||||
## ✓ Step step07_delete_group_with_children: DELETE group у которой есть подгруппы → 409
|
## ✓ Step step07_delete_group_with_children: DELETE group у которой есть подгруппы → 409
|
||||||
|
|
||||||
Длительность: 73мс
|
Длительность: 68мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -64,7 +64,7 @@
|
||||||
|
|
||||||
## ✓ Step step08_delete_group_with_products: DELETE group в которой есть продукты → 409
|
## ✓ Step step08_delete_group_with_products: DELETE group в которой есть продукты → 409
|
||||||
|
|
||||||
Длительность: 10мс
|
Длительность: 11мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -72,7 +72,7 @@
|
||||||
|
|
||||||
## ✓ Step step09_delete_unit_with_products: DELETE enable у unit, на которую ссылаются продукты → 409
|
## ✓ Step step09_delete_unit_with_products: DELETE enable у unit, на которую ссылаются продукты → 409
|
||||||
|
|
||||||
Длительность: 29мс
|
Длительность: 36мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -80,7 +80,7 @@
|
||||||
|
|
||||||
## ✓ Step step10_delete_system_price_type: DELETE PriceType.IsSystem=true → 409
|
## ✓ Step step10_delete_system_price_type: DELETE PriceType.IsSystem=true → 409
|
||||||
|
|
||||||
Длительность: 37мс
|
Длительность: 47мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -88,7 +88,7 @@
|
||||||
|
|
||||||
## ✓ Step step11_second_retail_price_type: POST PriceType с IsRetail=true когда уже есть Retail → 409
|
## ✓ Step step11_second_retail_price_type: POST PriceType с IsRetail=true когда уже есть Retail → 409
|
||||||
|
|
||||||
Длительность: 52мс
|
Длительность: 47мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -97,7 +97,7 @@
|
||||||
|
|
||||||
## ✓ Step step12_delete_counterparty_with_supply: DELETE counterparty который использован в Supply → 409
|
## ✓ Step step12_delete_counterparty_with_supply: DELETE counterparty который использован в Supply → 409
|
||||||
|
|
||||||
Длительность: 109мс
|
Длительность: 104мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -1,24 +1,24 @@
|
||||||
# E2E report: documents-edge
|
# E2E report: documents-edge
|
||||||
|
|
||||||
Запущен: 2026-05-26T06:28:16.561Z
|
Запущен: 2026-05-26T07:02:13.560Z
|
||||||
Длительность: 2.9с
|
Длительность: 3.1с
|
||||||
|
|
||||||
**Итог:** 10 ✓ / 0 ✗ / 0 ⚠ / 0 ◯ (всего 10)
|
**Итог:** 10 ✓ / 0 ✗ / 0 ⚠ / 0 ◯ (всего 10)
|
||||||
|
|
||||||
## ✓ Step step01_bootstrap: SuperAdmin создаёт орг Test + admin, делаем product + supply (10 шт по 100 KZT)
|
## ✓ Step step01_bootstrap: SuperAdmin создаёт орг Test + admin, делаем product + supply (10 шт по 100 KZT)
|
||||||
|
|
||||||
Длительность: 1265мс
|
Длительность: 1211мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| api | Орг + админ созданы | ✓ org=cdbad68a-b0fd-47ea-85dc-32272d39c2d8 |
|
| api | Орг + админ созданы | ✓ org=ddb8db28-039e-4f65-8f0d-62da5ce9c431 |
|
||||||
| api | Counterparty создан | ✓ |
|
| api | Counterparty создан | ✓ |
|
||||||
| api | Product создан | ✓ 08c989ff-3ad7-4e14-beef-e6e744959e07 |
|
| api | Product создан | ✓ 6d1eee5c-06aa-48a9-8690-b7dfbb7b6c3f |
|
||||||
| api | Supply Draft создана | ✓ 0c635259-abec-40fa-8087-a73054d8009a |
|
| api | Supply Draft создана | ✓ e143e4b0-1278-41af-9f25-7a68630727b7 |
|
||||||
|
|
||||||
## ✓ Step step02_post_supply_stock_10: Supply провести: stock=10, ReferencePrice=100, Cost=100
|
## ✓ Step step02_post_supply_stock_10: Supply провести: stock=10, ReferencePrice=100, Cost=100
|
||||||
|
|
||||||
Длительность: 88мс
|
Длительность: 86мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -27,16 +27,16 @@
|
||||||
|
|
||||||
## ✓ Step step03_oversell_blocked: RetailSale qty=15 (больше остатка 10), POST /post возвращает 409
|
## ✓ Step step03_oversell_blocked: RetailSale qty=15 (больше остатка 10), POST /post возвращает 409
|
||||||
|
|
||||||
Длительность: 122мс
|
Длительность: 96мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| api | POST RetailSale Draft (qty=15) | ✓ actual=201 {"id":"85112d55-fd90-4a7d-9cb9-c4cb4509d3d3","number":"ПР-2026-000001","date":"2026-05-26T06:28:19.547Z","status":0,"sto |
|
| api | POST RetailSale Draft (qty=15) | ✓ actual=201 {"id":"5d3aad81-df91-459b-8a1c-bab9d7f1cc64","number":"ПР-2026-000001","date":"2026-05-26T07:02:16.056Z","status":0,"sto |
|
||||||
| api | POST /post → 409 (oversell) | ✓ actual=409 {"error":"Недостаточно остатка для проведения чека.","lines":[{"productId":"08c989ff-3ad7-4e14-beef-e6e744959e07","produ |
|
| api | POST /post → 409 (oversell) | ✓ actual=409 {"error":"Недостаточно остатка для проведения чека.","lines":[{"productId":"6d1eee5c-06aa-48a9-8690-b7dfbb7b6c3f","produ |
|
||||||
|
|
||||||
## ✓ Step step04_oversell_stock_unchanged: После заблокированного post stock остался 10, StockMovement не добавлен
|
## ✓ Step step04_oversell_stock_unchanged: После заблокированного post stock остался 10, StockMovement не добавлен
|
||||||
|
|
||||||
Длительность: 260мс
|
Длительность: 267мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -45,7 +45,7 @@
|
||||||
|
|
||||||
## ✓ Step step05_payment_mismatch_blocked: RetailSale с PaidCash+PaidCard не равной Total отвергается на post
|
## ✓ Step step05_payment_mismatch_blocked: RetailSale с PaidCash+PaidCard не равной Total отвергается на post
|
||||||
|
|
||||||
Длительность: 40мс
|
Длительность: 50мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -53,7 +53,7 @@
|
||||||
|
|
||||||
## ✓ Step step06_edit_posted_supply_blocked: PUT проведённой Supply (Posted) возвращает 409
|
## ✓ Step step06_edit_posted_supply_blocked: PUT проведённой Supply (Posted) возвращает 409
|
||||||
|
|
||||||
Длительность: 49мс
|
Длительность: 65мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -61,7 +61,7 @@
|
||||||
|
|
||||||
## ✓ Step step07_delete_posted_supply_blocked: DELETE проведённой Supply возвращает 409
|
## ✓ Step step07_delete_posted_supply_blocked: DELETE проведённой Supply возвращает 409
|
||||||
|
|
||||||
Длительность: 26мс
|
Длительность: 34мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -69,12 +69,12 @@
|
||||||
|
|
||||||
## ✓ Step step08_unpost_negative_blocked: После Sale qty=5 unpost Supply qty=10 возвращает 409 (stock минус)
|
## ✓ Step step08_unpost_negative_blocked: После Sale qty=5 unpost Supply qty=10 возвращает 409 (stock минус)
|
||||||
|
|
||||||
Длительность: 98мс
|
Длительность: 153мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| api | Sale qty=5 проведён | ✓ actual=204 |
|
| api | Sale qty=5 проведён | ✓ actual=204 |
|
||||||
| api | Unpost Supply при stock<unpost-qty → 409 | ✓ actual=409 {"error":"Нельзя отменить проведение: остаток уйдёт в минус (часть товара уже расходована).","lines":[{"productId":"08c9 |
|
| api | Unpost Supply при stock<unpost-qty → 409 | ✓ actual=409 {"error":"Нельзя отменить проведение: остаток уйдёт в минус (часть товара уже расходована).","lines":[{"productId":"6d1e |
|
||||||
|
|
||||||
## ✓ Step step09_barcode_unique_within_org: Дубль штрихкода в одной орге, POST второго product отвергается
|
## ✓ Step step09_barcode_unique_within_org: Дубль штрихкода в одной орге, POST второго product отвергается
|
||||||
|
|
||||||
|
|
@ -82,15 +82,15 @@
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| api | POST product с тем же barcode → 4xx | ✓ actual=400 {"error":"Штрихкод 206899370undefined3 уже используется товаром «Edge Product 1779776896561»."} |
|
| api | POST product с тем же barcode → 4xx | ✓ actual=400 {"error":"Штрихкод 208935890undefined5 уже используется товаром «Edge Product 1779778933560»."} |
|
||||||
|
|
||||||
## ✓ Step step10_barcode_per_tenant: Тот же штрихкод в другой орге допустим (per-tenant unique)
|
## ✓ Step step10_barcode_per_tenant: Тот же штрихкод в другой орге допустим (per-tenant unique)
|
||||||
|
|
||||||
Длительность: 972мс
|
Длительность: 1085мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| api | POST product с тем же barcode в другой орге → 201 | ✓ actual=201 {"id":"4fe7e2aa-cef3-458b-ba34-984ce7af8f2e","name":"Tenant-2 product (same barcode)","article":"1","description":null," |
|
| api | POST product с тем же barcode в другой орге → 201 | ✓ actual=201 {"id":"4bd355b5-11d0-4170-ab38-2ab2d885c725","name":"Tenant-2 product (same barcode)","article":"1","description":null," |
|
||||||
| db | В product_barcodes 2 записи с этим Code (одна на орг) | ✓ count=2 |
|
| db | В product_barcodes 2 записи с этим Code (одна на орг) | ✓ count=2 |
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
110
tests/e2e/reports/employees-2026-05-26T07-03-24-714Z.md
Normal file
110
tests/e2e/reports/employees-2026-05-26T07-03-24-714Z.md
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
# E2E report: employees
|
||||||
|
|
||||||
|
Запущен: 2026-05-26T07:03:19.659Z
|
||||||
|
Длительность: 3.6с
|
||||||
|
|
||||||
|
**Итог:** 10 ✓ / 0 ✗ / 0 ⚠ / 0 ◯ (всего 10)
|
||||||
|
|
||||||
|
## ✓ Step step01_bootstrap: Орг A + логин админа + выбор не-админской роли
|
||||||
|
|
||||||
|
Длительность: 1197мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Орг A + не-админская роль найдены | ✓ role=Кладовщик |
|
||||||
|
|
||||||
|
## ✓ Step step02_create_without_account: Создание сотрудника без учётки (createAccount=false) → UserId=null
|
||||||
|
|
||||||
|
Длительность: 27мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Создан без учётки → 200/201 | ✓ status=200 |
|
||||||
|
| api | UserId = null | ✓ userId=null |
|
||||||
|
| api | generatedPassword отсутствует | ✓ |
|
||||||
|
|
||||||
|
## ✓ Step step03_create_with_account: Создание сотрудника с учёткой → temp password в ответе, User создан
|
||||||
|
|
||||||
|
Длительность: 351мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Создан с учёткой → 200/201 | ✓ status=200 |
|
||||||
|
| api | Temp password возвращён один раз | ✓ |
|
||||||
|
| api | UserId присвоен | ✓ userId=75ef2110-7770-4256-9b7d-33be90b832de |
|
||||||
|
|
||||||
|
## ✓ Step step04_email_required: createAccount=true без email → 400
|
||||||
|
|
||||||
|
Длительность: 13мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | createAccount=true без email → 400 | ✓ status=400 |
|
||||||
|
|
||||||
|
## ✓ Step step05_duplicate_email: Дубль email при createAccount → 4xx
|
||||||
|
|
||||||
|
Длительность: 17мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Дубль email → 4xx (rejected) | ✓ status=400 |
|
||||||
|
|
||||||
|
## ✓ Step step06_account_can_login: Новый сотрудник логинится временным паролем → 200
|
||||||
|
|
||||||
|
Длительность: 282мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Логин новым сотрудником → 200 | ✓ status=200 |
|
||||||
|
|
||||||
|
## ✓ Step step07_fire_blocks_login: Увольнение (DELETE) гасит логин: повторный login и refresh → 4xx
|
||||||
|
|
||||||
|
Длительность: 581мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Увольнение (DELETE) → 204 | ✓ status=204 |
|
||||||
|
| db | Employee.IsActive=false (уволен) | ✓ IsActive=f |
|
||||||
|
| db | User.IsActive=false (логин погашен) | ✓ User.IsActive=f |
|
||||||
|
| api | Повторный login уволенного → 4xx | ✓ status=400 |
|
||||||
|
| api | Refresh уволенного → 4xx | ✓ status=400 |
|
||||||
|
|
||||||
|
## ✓ Step step08_two_step_delete: Второй DELETE → soft-delete (IsDeleted), третий → 409
|
||||||
|
|
||||||
|
Длительность: 262мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Второй DELETE → 204 (soft-delete) | ✓ status=204 |
|
||||||
|
| db | Employee.IsDeleted=true | ✓ IsDeleted=t |
|
||||||
|
| api | Третий DELETE → 409 (уже удалён) | ✓ status=409 |
|
||||||
|
|
||||||
|
## ✓ Step step09_owner_self_protected: DELETE главного администратора (он же self) → 403
|
||||||
|
|
||||||
|
Длительность: 101мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Главный администратор найден в списке | ✓ owner=1c6f6673-4a67-464e-bf3e-00f71cdc8977 |
|
||||||
|
| api | DELETE главного администратора (он же self) → 403 | ✓ status=403 |
|
||||||
|
|
||||||
|
## ✓ Step step10_tenant_isolation: Админ орг A не видит и не удаляет сотрудника орг B → 404
|
||||||
|
|
||||||
|
Длительность: 769мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Сотрудник в орг B создан | ✓ |
|
||||||
|
| api | GET чужого сотрудника (орг B) из орг A → 404 | ✓ status=404 |
|
||||||
|
| api | DELETE чужого сотрудника → 404 | ✓ status=404 |
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
- Passed: 10
|
||||||
|
- Failed: 0
|
||||||
|
- Warnings: 0
|
||||||
|
- Skipped: 0
|
||||||
|
|
||||||
|
## Critical bugs
|
||||||
|
|
||||||
|
Нет.
|
||||||
|
|
@ -1,23 +1,23 @@
|
||||||
# E2E report: full-cycle
|
# E2E report: full-cycle
|
||||||
|
|
||||||
Запущен: 2026-05-26T06:27:56.114Z
|
Запущен: 2026-05-26T07:01:50.662Z
|
||||||
Длительность: 8.6с
|
Длительность: 10.6с
|
||||||
|
|
||||||
**Итог:** 12 ✓ / 0 ✗ / 0 ⚠ / 0 ◯ (всего 12)
|
**Итог:** 12 ✓ / 0 ✗ / 0 ⚠ / 0 ◯ (всего 12)
|
||||||
|
|
||||||
## ✓ Step step01_create_organization: SuperAdmin создаёт «Test Shop {timestamp}» (KZ, KZT, ФЛК телефона)
|
## ✓ Step step01_create_organization: SuperAdmin создаёт «Test Shop {timestamp}» (KZ, KZT, ФЛК телефона)
|
||||||
|
|
||||||
Длительность: 847мс
|
Длительность: 1707мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| api | POST /api/super-admin/organizations → 200 | ✓ org=Test Shop 1779776876113 |
|
| api | POST /api/super-admin/organizations → 200 | ✓ org=Test Shop 1779778910662 |
|
||||||
| api | GET /api/super-admin/organizations включает созданную org | ✓ |
|
| api | GET /api/super-admin/organizations включает созданную org | ✓ |
|
||||||
| api | Невалидный phone отвергается | ✓ 400 |
|
| api | Невалидный phone отвергается | ✓ 400 |
|
||||||
|
|
||||||
## ✓ Step step02_create_first_admin: SuperAdmin создаёт первого Admin сотрудника организации (Employee + AppUser)
|
## ✓ Step step02_create_first_admin: SuperAdmin создаёт первого Admin сотрудника организации (Employee + AppUser)
|
||||||
|
|
||||||
Длительность: 603мс
|
Длительность: 664мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -27,17 +27,17 @@
|
||||||
|
|
||||||
## ✓ Step step03_login_as_admin: Логин под admin (не SuperAdmin override) — JWT с org_id и role=Admin
|
## ✓ Step step03_login_as_admin: Логин под admin (не SuperAdmin override) — JWT с org_id и role=Admin
|
||||||
|
|
||||||
Длительность: 235мс
|
Длительность: 416мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| api | /connect/token password-grant выдал токен | ✓ |
|
| api | /connect/token password-grant выдал токен | ✓ |
|
||||||
| api | /api/me содержит role=Admin | ✓ Admin |
|
| api | /api/me содержит role=Admin | ✓ Admin |
|
||||||
| api | /api/me содержит правильный orgId | ✓ b43f6023-c327-4ffe-8c54-606dc64ea620 |
|
| api | /api/me содержит правильный orgId | ✓ 5546bb9a-eb4c-4fea-b54b-059a3e65c249 |
|
||||||
|
|
||||||
## ✓ Step step04_create_storekeeper_and_cashier: Admin создаёт Storekeeper и Cashier через /settings/employees
|
## ✓ Step step04_create_storekeeper_and_cashier: Admin создаёт Storekeeper и Cashier через /settings/employees
|
||||||
|
|
||||||
Длительность: 1517мс
|
Длительность: 1320мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -51,7 +51,7 @@
|
||||||
|
|
||||||
## ✓ Step step05_login_as_cashier: Логин под Cashier — role-guard проверяется (sidebar/role guard)
|
## ✓ Step step05_login_as_cashier: Логин под Cashier — role-guard проверяется (sidebar/role guard)
|
||||||
|
|
||||||
Длительность: 614мс
|
Длительность: 450мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -61,7 +61,7 @@
|
||||||
|
|
||||||
## ✓ Step step06_create_counterparty: Admin создаёт «ТОО Тест Поставщик» (БИН + телефон)
|
## ✓ Step step06_create_counterparty: Admin создаёт «ТОО Тест Поставщик» (БИН + телефон)
|
||||||
|
|
||||||
Длительность: 179мс
|
Длительность: 386мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -69,7 +69,7 @@
|
||||||
|
|
||||||
## ✓ Step step07_ensure_main_store: Проверить что есть main store (из bootstrap), иначе создать
|
## ✓ Step step07_ensure_main_store: Проверить что есть main store (из bootstrap), иначе создать
|
||||||
|
|
||||||
Длительность: 62мс
|
Длительность: 51мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -78,11 +78,11 @@
|
||||||
|
|
||||||
## ✓ Step step08_create_supply: Admin создаёт Supply Draft (3-5 товаров) и проводит (Posted)
|
## ✓ Step step08_create_supply: Admin создаёт Supply Draft (3-5 товаров) и проводит (Posted)
|
||||||
|
|
||||||
Длительность: 2159мс
|
Длительность: 2821мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| api | Создано 3 product (валидный barcode + price + group) | ✓ e2e Product 1 1779776876113, e2e Product 2 1779776876113, e2e Product 3 1779776876113 |
|
| api | Создано 3 product (валидный barcode + price + group) | ✓ e2e Product 1 1779778910662, e2e Product 2 1779778910662, e2e Product 3 1779778910662 |
|
||||||
| api | Supply без supplierId → 400/409 | ✓ 400 {"error":"Поле SupplierId обязательно.","field":"SupplierId"} |
|
| api | Supply без supplierId → 400/409 | ✓ 400 {"error":"Поле SupplierId обязательно.","field":"SupplierId"} |
|
||||||
| api | Supply с пустым lines[] → 400 | ✓ 400 |
|
| api | Supply с пустым lines[] → 400 | ✓ 400 |
|
||||||
| api | POST /api/purchases/supplies (Draft) | ✓ 201 |
|
| api | POST /api/purchases/supplies (Draft) | ✓ 201 |
|
||||||
|
|
@ -92,21 +92,21 @@
|
||||||
|
|
||||||
## ✓ Step step09_check_stock_after_supply: GET /api/inventory/stock — quantity увеличился на supplied amount
|
## ✓ Step step09_check_stock_after_supply: GET /api/inventory/stock — quantity увеличился на supplied amount
|
||||||
|
|
||||||
Длительность: 771мс
|
Длительность: 889мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| api | stock(e2e Product 1 1779776876113) +10 (было 0, стало 10) | ✓ delta=10, expected=10 |
|
| api | stock(e2e Product 1 1779778910662) +10 (было 0, стало 10) | ✓ delta=10, expected=10 |
|
||||||
| api | stock(e2e Product 2 1779776876113) +15 (было 0, стало 15) | ✓ delta=15, expected=15 |
|
| api | stock(e2e Product 2 1779778910662) +15 (было 0, стало 15) | ✓ delta=15, expected=15 |
|
||||||
| api | stock(e2e Product 3 1779776876113) +20 (было 0, стало 20) | ✓ delta=20, expected=20 |
|
| api | stock(e2e Product 3 1779778910662) +20 (было 0, стало 20) | ✓ delta=20, expected=20 |
|
||||||
| api | GET /api/inventory/stock без storeId возвращает строки на каждый склад | ✓ rows=1, stores=883b070c |
|
| api | GET /api/inventory/stock без storeId возвращает строки на каждый склад | ✓ rows=1, stores=5e62ff98 |
|
||||||
| db | stocks.Quantity == SUM(stock_movements.Quantity) для e2e Product 1 1779776876113… | ✓ sum_movements=10 stocks.Quantity=10 |
|
| db | stocks.Quantity == SUM(stock_movements.Quantity) для e2e Product 1 1779778910662… | ✓ sum_movements=10 stocks.Quantity=10 |
|
||||||
| db | stocks.Quantity == SUM(stock_movements.Quantity) для e2e Product 2 1779776876113… | ✓ sum_movements=15 stocks.Quantity=15 |
|
| db | stocks.Quantity == SUM(stock_movements.Quantity) для e2e Product 2 1779778910662… | ✓ sum_movements=15 stocks.Quantity=15 |
|
||||||
| db | stocks.Quantity == SUM(stock_movements.Quantity) для e2e Product 3 1779776876113… | ✓ sum_movements=20 stocks.Quantity=20 |
|
| db | stocks.Quantity == SUM(stock_movements.Quantity) для e2e Product 3 1779778910662… | ✓ sum_movements=20 stocks.Quantity=20 |
|
||||||
|
|
||||||
## ✓ Step step10_ensure_retail_point: Проверить или создать розничную точку (кассу)
|
## ✓ Step step10_ensure_retail_point: Проверить или создать розничную точку (кассу)
|
||||||
|
|
||||||
Длительность: 126мс
|
Длительность: 132мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -115,7 +115,7 @@
|
||||||
|
|
||||||
## ✓ Step step11_create_retail_sale: Admin создаёт RetailSale, 2 позиции из приёмки, cash, Post
|
## ✓ Step step11_create_retail_sale: Admin создаёт RetailSale, 2 позиции из приёмки, cash, Post
|
||||||
|
|
||||||
Длительность: 800мс
|
Длительность: 1000мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -128,14 +128,14 @@
|
||||||
|
|
||||||
## ✓ Step step12_check_stock_after_sale: GET /api/inventory/stock — quantity уменьшился на sold amount
|
## ✓ Step step12_check_stock_after_sale: GET /api/inventory/stock — quantity уменьшился на sold amount
|
||||||
|
|
||||||
Длительность: 694мс
|
Длительность: 762мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| api | stock product=5da542bb… −2 (было 10, стало 8) | ✓ delta=2, expected=2 |
|
| api | stock product=a9e517d5… −2 (было 10, стало 8) | ✓ delta=2, expected=2 |
|
||||||
| api | stock product=671f6e0e… −2 (было 15, стало 13) | ✓ delta=2, expected=2 |
|
| api | stock product=0f3d33e8… −2 (было 15, стало 13) | ✓ delta=2, expected=2 |
|
||||||
| db | stock_movements запись на sale-line 5da542bb… | ✓ count=1, sum=-2 (expected sum=-2) |
|
| db | stock_movements запись на sale-line a9e517d5… | ✓ count=1, sum=-2 (expected sum=-2) |
|
||||||
| db | stock_movements запись на sale-line 671f6e0e… | ✓ count=1, sum=-2 (expected sum=-2) |
|
| db | stock_movements запись на sale-line 0f3d33e8… | ✓ count=1, sum=-2 (expected sum=-2) |
|
||||||
| db | stock_movements.Type = RetailSale (2) для sale документа | ✓ types=2 |
|
| db | stock_movements.Type = RetailSale (2) для sale документа | ✓ types=2 |
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
@ -1,24 +1,24 @@
|
||||||
# E2E report: moysklad-import
|
# E2E report: moysklad-import
|
||||||
|
|
||||||
Запущен: 2026-05-26T06:29:04.213Z
|
Запущен: 2026-05-26T07:03:03.819Z
|
||||||
Длительность: 10.6с
|
Длительность: 11.3с
|
||||||
|
|
||||||
**Итог:** 7 ✓ / 0 ✗ / 0 ⚠ / 0 ◯ (всего 7)
|
**Итог:** 7 ✓ / 0 ✗ / 0 ⚠ / 0 ◯ (всего 7)
|
||||||
|
|
||||||
## ✓ Step step01_bootstrap_and_connect: Орг + mock MoySklad + сохранение токена (маскирование) + test-connection 200
|
## ✓ Step step01_bootstrap_and_connect: Орг + mock MoySklad + сохранение токена (маскирование) + test-connection 200
|
||||||
|
|
||||||
Длительность: 1305мс
|
Длительность: 1425мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| api | Mock MoySklad поднят | ✓ http://127.0.0.1:5099/api/remap/1.2/ |
|
| api | Mock MoySklad поднят | ✓ http://127.0.0.1:5099/api/remap/1.2/ |
|
||||||
| api | PUT settings → hasToken + маска | ✓ status=200 masked=mock••••••••abcd |
|
| api | PUT settings → hasToken + маска | ✓ status=200 masked=mock••••••••abcd |
|
||||||
| api | GET settings не отдаёт сырой токен | ✓ masked=mock••••••••abcd |
|
| api | GET settings не отдаёт сырой токен | ✓ masked=mock••••••••abcd |
|
||||||
| api | POST test → 200, имя орги из mock | ✓ status=200 org={"organization":"Mock Org 1779776944213","inn":"600700800900"} |
|
| api | POST test → 200, имя орги из mock | ✓ status=200 org={"organization":"Mock Org 1779778983819","inn":"600700800900"} |
|
||||||
|
|
||||||
## ✓ Step step02_import_counterparties_create: Импорт контрагентов: job Succeeded, Created=2, маппинг полей верен
|
## ✓ Step step02_import_counterparties_create: Импорт контрагентов: job Succeeded, Created=2, маппинг полей верен
|
||||||
|
|
||||||
Длительность: 3930мс
|
Длительность: 4159мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -27,12 +27,12 @@
|
||||||
| api | Created=2, Skipped=0 | ✓ created=2 updated=0 skipped=0 |
|
| api | Created=2, Skipped=0 | ✓ created=2 updated=0 skipped=0 |
|
||||||
| db | В БД 2 контрагента | ✓ count=2 |
|
| db | В БД 2 контрагента | ✓ count=2 |
|
||||||
| db | Ромашка: Type=1(LegalEntity), Bin=inn, TaxNumber=kpp | ✓ Type=1 Bin=123456789012 Tax=KPP001 |
|
| db | Ромашка: Type=1(LegalEntity), Bin=inn, TaxNumber=kpp | ✓ Type=1 Bin=123456789012 Tax=KPP001 |
|
||||||
| db | Ромашка: LegalName=legalTitle, Address=actualAddress, Notes=description | ✓ Legal=Товарищество Ромашка 1779776944213 Addr=Алматы Абая 1 |
|
| db | Ромашка: LegalName=legalTitle, Address=actualAddress, Notes=description | ✓ Legal=Товарищество Ромашка 1779778983819 Addr=Алматы Абая 1 |
|
||||||
| db | Иванов: Type=2(Individual), Address=legalAddress (fallback) | ✓ Type=2 Addr=Астана Победы 5 |
|
| db | Иванов: Type=2(Individual), Address=legalAddress (fallback) | ✓ Type=2 Addr=Астана Победы 5 |
|
||||||
|
|
||||||
## ✓ Step step03_import_counterparties_idempotent: Повторный импорт (overwrite=false): Skipped=2, дублей нет
|
## ✓ Step step03_import_counterparties_idempotent: Повторный импорт (overwrite=false): Skipped=2, дублей нет
|
||||||
|
|
||||||
Длительность: 907мс
|
Длительность: 890мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
|
|
||||||
## ✓ Step step04_import_counterparties_overwrite: Импорт overwrite=true с изменёнными данными: Updated=2, поля обновлены
|
## ✓ Step step04_import_counterparties_overwrite: Импорт overwrite=true с изменёнными данными: Updated=2, поля обновлены
|
||||||
|
|
||||||
Длительность: 1191мс
|
Длительность: 1156мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -53,24 +53,24 @@
|
||||||
|
|
||||||
## ✓ Step step05_import_products_create: Импорт товаров: Created=1, группа создана, маппинг (артикул/НДС/цена/штрихкод/группа/страна)
|
## ✓ Step step05_import_products_create: Импорт товаров: Created=1, группа создана, маппинг (артикул/НДС/цена/штрихкод/группа/страна)
|
||||||
|
|
||||||
Длительность: 2178мс
|
Длительность: 2446мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| api | import-products → jobId | ✓ status=200 |
|
| api | import-products → jobId | ✓ status=200 |
|
||||||
| api | job Succeeded | ✓ status=Succeeded msg=Готово: 1 записей (создано/обновлено), 0 пропущено, 1 групп. errors=[] |
|
| api | job Succeeded | ✓ status=Succeeded msg=Готово: 1 записей (создано/обновлено), 0 пропущено, 1 групп. errors=[] |
|
||||||
| api | Created=1, GroupsCreated>=1 | ✓ created=1 groups=1 |
|
| api | Created=1, GroupsCreated>=1 | ✓ created=1 groups=1 |
|
||||||
| db | Товар создан с артикулом из MoySklad | ✓ id=81fa3d5b-8d2a-492f-9b72-ee467af12b4f |
|
| db | Товар создан с артикулом из MoySklad | ✓ id=bfef9503-64fc-4f71-b91f-5c2d9b956313 |
|
||||||
| db | Vat=12, Packaging=1(Piece, weighed=false), IsMarked=false | ✓ vat=12.00 pack=1 marked=f |
|
| db | Vat=12, Packaging=1(Piece, weighed=false), IsMarked=false | ✓ vat=12.00 pack=1 marked=f |
|
||||||
| db | ReferencePrice = buyPrice/100 = 250 | ✓ ref=250.0000 |
|
| db | ReferencePrice = buyPrice/100 = 250 | ✓ ref=250.0000 |
|
||||||
| db | Группа = productFolder «Бакалея» | ✓ group=Бакалея 1779776944213 |
|
| db | Группа = productFolder «Бакалея» | ✓ group=Бакалея 1779778983819 |
|
||||||
| db | CountryOfOrigin сопоставлена по имени | ✓ country=2b209c11-c7ed-415d-9947-c8b6604f712e expected=2b209c11-c7ed-415d-9947-c8b6604f712e |
|
| db | CountryOfOrigin сопоставлена по имени | ✓ country=2b209c11-c7ed-415d-9947-c8b6604f712e expected=2b209c11-c7ed-415d-9947-c8b6604f712e |
|
||||||
| db | Розничная цена = 320 (salePrice «Розничная»/100) | ✓ amount=320.0000 |
|
| db | Розничная цена = 320 (salePrice «Розничная»/100) | ✓ amount=320.0000 |
|
||||||
| db | Штрихкод ean13 импортирован | ✓ bc=2069470270014 expected=2069470270014 |
|
| db | Штрихкод ean13 импортирован | ✓ bc=2089863540012 expected=2089863540012 |
|
||||||
|
|
||||||
## ✓ Step step06_import_products_idempotent: Повторный импорт товаров (overwrite=false): Skipped=1, дублей нет
|
## ✓ Step step06_import_products_idempotent: Повторный импорт товаров (overwrite=false): Skipped=1, дублей нет
|
||||||
|
|
||||||
Длительность: 1134мс
|
Длительность: 1182мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -1,43 +1,43 @@
|
||||||
# E2E report: multi-tenant-isolation
|
# E2E report: multi-tenant-isolation
|
||||||
|
|
||||||
Запущен: 2026-05-26T06:28:08.807Z
|
Запущен: 2026-05-26T07:02:06.316Z
|
||||||
Длительность: 3.6с
|
Длительность: 3.5с
|
||||||
|
|
||||||
**Итог:** 12 ✓ / 0 ✗ / 0 ⚠ / 0 ◯ (всего 12)
|
**Итог:** 12 ✓ / 0 ✗ / 0 ⚠ / 0 ◯ (всего 12)
|
||||||
|
|
||||||
## ✓ Step step01_create_two_orgs: SuperAdmin создаёт две независимые орги Alpha и Beta (каждая со своим админом)
|
## ✓ Step step01_create_two_orgs: SuperAdmin создаёт две независимые орги Alpha и Beta (каждая со своим админом)
|
||||||
|
|
||||||
Длительность: 1218мс
|
Длительность: 1184мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| api | Создана Alpha | ✓ f37d8c51-9934-4d18-a285-7ba25dfc60be |
|
| api | Создана Alpha | ✓ 8cc09bb5-c09d-4e94-8e6c-9b5256c97381 |
|
||||||
| api | Создана Beta | ✓ c372ef64-2328-4a19-af3c-90a058f44012 |
|
| api | Создана Beta | ✓ 39c3e0ce-5b42-444e-80ff-9566868ee155 |
|
||||||
| api | orgId Alpha ≠ orgId Beta | ✓ |
|
| api | orgId Alpha ≠ orgId Beta | ✓ |
|
||||||
|
|
||||||
## ✓ Step step02_login_both_admins: Логин под admin Alpha и admin Beta — получаем два разных org_id в JWT
|
## ✓ Step step02_login_both_admins: Логин под admin Alpha и admin Beta — получаем два разных org_id в JWT
|
||||||
|
|
||||||
Длительность: 792мс
|
Длительность: 744мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| api | Login Alpha admin → 200 | ✓ |
|
| api | Login Alpha admin → 200 | ✓ |
|
||||||
| api | Login Beta admin → 200 | ✓ |
|
| api | Login Beta admin → 200 | ✓ |
|
||||||
| api | Alpha orgId == ctx.alpha.orgId | ✓ claim=f37d8c51-9934-4d18-a285-7ba25dfc60be |
|
| api | Alpha orgId == ctx.alpha.orgId | ✓ claim=8cc09bb5-c09d-4e94-8e6c-9b5256c97381 |
|
||||||
| api | Beta orgId == ctx.beta.orgId | ✓ claim=c372ef64-2328-4a19-af3c-90a058f44012 |
|
| api | Beta orgId == ctx.beta.orgId | ✓ claim=39c3e0ce-5b42-444e-80ff-9566868ee155 |
|
||||||
|
|
||||||
## ✓ Step step03_seed_data_in_alpha: Admin Alpha создаёт counterparty + product → запоминаем их ID
|
## ✓ Step step03_seed_data_in_alpha: Admin Alpha создаёт counterparty + product → запоминаем их ID
|
||||||
|
|
||||||
Длительность: 160мс
|
Длительность: 135мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| api | Alpha создаёт counterparty | ✓ 8103d4ec-e35c-4fd4-8214-931921a86783 |
|
| api | Alpha создаёт counterparty | ✓ 8501aa94-8f43-4046-830b-f36ce5797b8e |
|
||||||
| api | Alpha создаёт product | ✓ 647f7992-3df1-460c-845c-6ed68bc1903b |
|
| api | Alpha создаёт product | ✓ fa1da1ad-69ec-40e8-b1f4-90bf04555cd9 |
|
||||||
|
|
||||||
## ✓ Step step04_beta_cannot_read_alpha: Admin Beta GET /api/catalog/counterparties/{alphaId} и /products/{alphaId} → 404
|
## ✓ Step step04_beta_cannot_read_alpha: Admin Beta GET /api/catalog/counterparties/{alphaId} и /products/{alphaId} → 404
|
||||||
|
|
||||||
Длительность: 159мс
|
Длительность: 126мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -46,7 +46,7 @@
|
||||||
|
|
||||||
## ✓ Step step05_beta_cannot_list_alpha_data: Admin Beta GET /api/catalog/counterparties|/products → пустые списки (нет данных Alpha)
|
## ✓ Step step05_beta_cannot_list_alpha_data: Admin Beta GET /api/catalog/counterparties|/products → пустые списки (нет данных Alpha)
|
||||||
|
|
||||||
Длительность: 207мс
|
Длительность: 223мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -55,7 +55,7 @@
|
||||||
|
|
||||||
## ✓ Step step06_beta_cannot_modify_alpha: Admin Beta PUT/DELETE /api/catalog/products/{alphaId} → 404 (не 200, не 403)
|
## ✓ Step step06_beta_cannot_modify_alpha: Admin Beta PUT/DELETE /api/catalog/products/{alphaId} → 404 (не 200, не 403)
|
||||||
|
|
||||||
Длительность: 172мс
|
Длительность: 198мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -64,7 +64,7 @@
|
||||||
|
|
||||||
## ✓ Step step07_beta_cannot_link_to_alpha: Admin Beta POST product с DefaultSupplierId=alphaCounterpartyId → 400 (FK через query filter)
|
## ✓ Step step07_beta_cannot_link_to_alpha: Admin Beta POST product с DefaultSupplierId=alphaCounterpartyId → 400 (FK через query filter)
|
||||||
|
|
||||||
Длительность: 38мс
|
Длительность: 32мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -72,7 +72,7 @@
|
||||||
|
|
||||||
## ✓ Step step08_beta_cannot_forge_org_override: Admin Beta с заголовком X-Org-Override:{alphaId} → запрос всё равно идёт от Beta (не SuperAdmin)
|
## ✓ Step step08_beta_cannot_forge_org_override: Admin Beta с заголовком X-Org-Override:{alphaId} → запрос всё равно идёт от Beta (не SuperAdmin)
|
||||||
|
|
||||||
Длительность: 21мс
|
Длительность: 16мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -80,7 +80,7 @@
|
||||||
|
|
||||||
## ✓ Step step09_superadmin_sees_both: SuperAdmin без override GET /api/super-admin/organizations → видит и Alpha и Beta
|
## ✓ Step step09_superadmin_sees_both: SuperAdmin без override GET /api/super-admin/organizations → видит и Alpha и Beta
|
||||||
|
|
||||||
Длительность: 18мс
|
Длительность: 16мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -89,7 +89,7 @@
|
||||||
|
|
||||||
## ✓ Step step10_superadmin_readonly_override: SuperAdmin с X-Org-Override:{alphaId} → GET товаров Alpha (200), PUT/POST без reason → 403
|
## ✓ Step step10_superadmin_readonly_override: SuperAdmin с X-Org-Override:{alphaId} → GET товаров Alpha (200), PUT/POST без reason → 403
|
||||||
|
|
||||||
Длительность: 30мс
|
Длительность: 29мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -98,21 +98,21 @@
|
||||||
|
|
||||||
## ✓ Step step11_superadmin_edit_override_with_reason: SuperAdmin с X-Org-Override + X-Org-Override-Reason → PUT 200 + запись в audit_log
|
## ✓ Step step11_superadmin_edit_override_with_reason: SuperAdmin с X-Org-Override + X-Org-Override-Reason → PUT 200 + запись в audit_log
|
||||||
|
|
||||||
Длительность: 504мс
|
Длительность: 506мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| api | SuperAdmin+override+reason PUT counterparty → 200/204 | ✓ actual=204 |
|
| api | SuperAdmin+override+reason PUT counterparty → 200/204 | ✓ actual=204 |
|
||||||
| db | Counterparty.Name изменено в БД | ✓ name=edited-by-superadmin-1779776888807 |
|
| db | Counterparty.Name изменено в БД | ✓ name=edited-by-superadmin-1779778926316 |
|
||||||
| db | super_admin_audit_log выросло | ✓ before=2 after=3 |
|
| db | super_admin_audit_log выросло | ✓ before=2 after=3 |
|
||||||
|
|
||||||
## ✓ Step step12_stock_isolation: Остатки Alpha и Beta не смешиваются — Supply в Alpha не появляется в /inventory/stock у Beta
|
## ✓ Step step12_stock_isolation: Остатки Alpha и Beta не смешиваются — Supply в Alpha не появляется в /inventory/stock у Beta
|
||||||
|
|
||||||
Длительность: 267мс
|
Длительность: 298мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| api | Alpha создаёт supply | ✓ actual=201 {"id":"0aacfaea-1785-4761-8524-bc5bde177f0b","number":"П-2026-000001","date":"2026-05-26T06:28:13.685Z","status":0,"supp |
|
| api | Alpha создаёт supply | ✓ actual=201 {"id":"28b0326f-a65e-476f-bb69-ef3a61bba0cb","number":"П-2026-000001","date":"2026-05-26T07:02:10.853Z","status":0,"supp |
|
||||||
| api | Alpha проводит supply | ✓ actual=204 |
|
| api | Alpha проводит supply | ✓ actual=204 |
|
||||||
| api | Beta /inventory/stock не содержит Alpha product | ✓ total=0, утечка=нет |
|
| api | Beta /inventory/stock не содержит Alpha product | ✓ total=0, утечка=нет |
|
||||||
| api | Beta /inventory/movements не содержит Alpha movement | ✓ total=0, утечка=нет |
|
| api | Beta /inventory/movements не содержит Alpha movement | ✓ total=0, утечка=нет |
|
||||||
71
tests/e2e/reports/platform-smtp-2026-05-26T07-03-43-053Z.md
Normal file
71
tests/e2e/reports/platform-smtp-2026-05-26T07-03-43-053Z.md
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
# E2E report: platform-smtp
|
||||||
|
|
||||||
|
Запущен: 2026-05-26T07:03:40.841Z
|
||||||
|
Длительность: 0.7с
|
||||||
|
|
||||||
|
**Итог:** 6 ✓ / 0 ✗ / 0 ⚠ / 0 ◯ (всего 6)
|
||||||
|
|
||||||
|
## ✓ Step step01_clean_state: Сброс SMTP в чистое состояние (hasSmtpPassword=false, host пуст)
|
||||||
|
|
||||||
|
Длительность: 391мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | PUT очистки → 204 | ✓ status=204 |
|
||||||
|
| api | hasSmtpPassword=false после очистки | ✓ has=false |
|
||||||
|
|
||||||
|
## ✓ Step step02_reason_required: PUT без причины / причина <10 → 400
|
||||||
|
|
||||||
|
Длительность: 11мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | PUT без причины → 400 | ✓ status=400 |
|
||||||
|
| api | PUT причина <10 → 400 | ✓ status=400 |
|
||||||
|
|
||||||
|
## ✓ Step step03_test_send_not_configured: test-send при ненастроенном SMTP → 400
|
||||||
|
|
||||||
|
Длительность: 15мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | test-send без настроенного SMTP → 400 | ✓ status=400 |
|
||||||
|
|
||||||
|
## ✓ Step step04_save_smtp: Сохранение SMTP с паролем → 204, GET отдаёт поля кроме пароля
|
||||||
|
|
||||||
|
Длительность: 30мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | PUT валидной конфигурации → 204 | ✓ status=204 |
|
||||||
|
| api | hasSmtpPassword=true | ✓ has=true |
|
||||||
|
| api | smtpHost/username сохранены и возвращаются | ✓ host=smtp.example.com |
|
||||||
|
|
||||||
|
## ✓ Step step05_password_encrypted: Пароль в БД зашифрован (не плейнтекст) и не возвращается клиенту
|
||||||
|
|
||||||
|
Длительность: 212мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Ответ GET не содержит пароль в открытом виде | ✓ ok |
|
||||||
|
| db | В БД пароль не плейнтекст и не пуст | ✓ len=155 |
|
||||||
|
|
||||||
|
## ✓ Step step06_clear_password: PUT newSmtpPassword=__clear__ → hasSmtpPassword=false
|
||||||
|
|
||||||
|
Длительность: 29мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | PUT __clear__ → 204 | ✓ status=204 |
|
||||||
|
| api | hasSmtpPassword=false | ✓ has=false |
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
- Passed: 6
|
||||||
|
- Failed: 0
|
||||||
|
- Warnings: 0
|
||||||
|
- Skipped: 0
|
||||||
|
|
||||||
|
## Critical bugs
|
||||||
|
|
||||||
|
Нет.
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
# E2E report: reports-stats
|
# E2E report: reports-stats
|
||||||
|
|
||||||
Запущен: 2026-05-26T06:28:57.128Z
|
Запущен: 2026-05-26T07:02:56.545Z
|
||||||
Длительность: 2.9с
|
Длительность: 3.0с
|
||||||
|
|
||||||
**Итог:** 5 ✓ / 0 ✗ / 0 ⚠ / 0 ◯ (всего 5)
|
**Итог:** 5 ✓ / 0 ✗ / 0 ⚠ / 0 ◯ (всего 5)
|
||||||
|
|
||||||
## ✓ Step step01_bootstrap: Орг A + товар + приёмка (остаток под продажи)
|
## ✓ Step step01_bootstrap: Орг A + товар + приёмка (остаток под продажи)
|
||||||
|
|
||||||
Длительность: 1462мс
|
Длительность: 1544мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| api | Орг A с товаром и остатком готова | ✓ 86c52764-ba04-43c5-b069-52ff3197e2b6 |
|
| api | Орг A с товаром и остатком готова | ✓ 168ac2c6-f257-4b71-b5e6-997ce6777abc |
|
||||||
|
|
||||||
## ✓ Step step02_stats_reflect_posted_sales: stats: RevenueToday/Transactions/AvgTicket = сумме проведённых чеков, серия непрерывна
|
## ✓ Step step02_stats_reflect_posted_sales: stats: RevenueToday/Transactions/AvgTicket = сумме проведённых чеков, серия непрерывна
|
||||||
|
|
||||||
Длительность: 333мс
|
Длительность: 405мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
|
|
||||||
## ✓ Step step03_draft_sale_excluded: Черновик чека (не проведён) не попадает в stats
|
## ✓ Step step03_draft_sale_excluded: Черновик чека (не проведён) не попадает в stats
|
||||||
|
|
||||||
Длительность: 53мс
|
Длительность: 49мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -40,7 +40,7 @@
|
||||||
|
|
||||||
## ✓ Step step04_stats_tenant_isolated: stats орг A не видит продажи орг B и наоборот
|
## ✓ Step step04_stats_tenant_isolated: stats орг A не видит продажи орг B и наоборот
|
||||||
|
|
||||||
Длительность: 1027мс
|
Длительность: 988мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -51,7 +51,7 @@
|
||||||
|
|
||||||
## ✓ Step step05_days_param_and_gaps: Параметр days меняет длину серии; профит/ABC-отчёты отсутствуют (gap)
|
## ✓ Step step05_days_param_and_gaps: Параметр days меняет длину серии; профит/ABC-отчёты отсутствуют (gap)
|
||||||
|
|
||||||
Длительность: 25мс
|
Длительность: 24мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
91
tests/e2e/reports/roles-2026-05-26T07-03-30-039Z.md
Normal file
91
tests/e2e/reports/roles-2026-05-26T07-03-30-039Z.md
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
# E2E report: roles
|
||||||
|
|
||||||
|
Запущен: 2026-05-26T07:03:26.970Z
|
||||||
|
Длительность: 1.6с
|
||||||
|
|
||||||
|
**Итог:** 8 ✓ / 0 ✗ / 0 ⚠ / 0 ◯ (всего 8)
|
||||||
|
|
||||||
|
## ✓ Step step01_bootstrap: Орг A + логин админа
|
||||||
|
|
||||||
|
Длительность: 1201мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Орг A + админ готовы | ✓ |
|
||||||
|
|
||||||
|
## ✓ Step step02_system_roles_exist: Системные роли (IsSystem=true) созданы — минимум 4
|
||||||
|
|
||||||
|
Длительность: 15мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Системные роли (ядро Администратор/Кладовщик/Кассир) присутствуют, не удаляемы | ✓ system=[Администратор, Кладовщик, Кассир] |
|
||||||
|
|
||||||
|
## ✓ Step step03_create_custom_role: Создание кастомной роли с правами → права сохранены
|
||||||
|
|
||||||
|
Длительность: 94мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Создание кастомной роли → 200/201 | ✓ status=201 |
|
||||||
|
| api | Права сохранены (productsView=true, productsEdit=false) | ✓ {"productsView":true,"productsEdit":false,"productsDelete":false,"productGroupsManage":false,"priceTypesManage":false,"unitsManage":false,"suppliesView":true,"suppliesEdit":false,"suppliesPost":false, |
|
||||||
|
|
||||||
|
## ✓ Step step04_update_permissions: Изменение прав роли применяется (PUT)
|
||||||
|
|
||||||
|
Длительность: 143мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | PUT прав роли → 200/204 | ✓ status=204 |
|
||||||
|
| api | Новое право productsEdit=true применилось | ✓ {"productsView":true,"productsEdit":true,"productsDelete":false,"productGroupsManage":false,"priceTypesManage":false,"unitsManage":false,"suppliesView":true,"suppliesEdit":false,"suppliesPost":false," |
|
||||||
|
|
||||||
|
## ✓ Step step05_delete_system_role_409: Удаление системной роли → 409
|
||||||
|
|
||||||
|
Длительность: 41мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Системная роль найдена | ✓ Администратор |
|
||||||
|
| api | DELETE системной роли → 409 | ✓ status=409 |
|
||||||
|
|
||||||
|
## ✓ Step step06_delete_role_in_use_409: Удаление роли, занятой сотрудником → 409
|
||||||
|
|
||||||
|
Длительность: 51мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Сотрудник с кастомной ролью создан | ✓ status=200 |
|
||||||
|
| api | DELETE занятой роли → 409 | ✓ status=409 |
|
||||||
|
|
||||||
|
## ✓ Step step07_delete_unused_role_ok: Удаление неиспользуемой роли → 204/200
|
||||||
|
|
||||||
|
Длительность: 42мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Неиспользуемая роль создана | ✓ |
|
||||||
|
| api | DELETE неиспользуемой роли → 200/204 | ✓ status=204 |
|
||||||
|
|
||||||
|
## ✓ Step step08_permission_authz_gap: Permission-based authz не enforced на API — gap
|
||||||
|
|
||||||
|
Длительность: 1мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Permission-based authz — задокументированный gap (см. Logic gaps) | ✓ |
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
- Passed: 8
|
||||||
|
- Failed: 0
|
||||||
|
- Warnings: 0
|
||||||
|
- Skipped: 0
|
||||||
|
|
||||||
|
## Critical bugs
|
||||||
|
|
||||||
|
Нет.
|
||||||
|
|
||||||
|
## Logic gaps
|
||||||
|
|
||||||
|
- ТЗ 2.7.2 ожидает 4-6 системных ролей, реально 3 (Phase4b_RolesSimplify): Администратор, Кладовщик, Кассир. Это намеренное упрощение, не баг — ТЗ устарело.
|
||||||
|
- ТЗ 2.7.2: permission-based авторизация не enforced — эндпоинты используют только [Authorize(Roles=...)], флаги RolePermissions носят справочный характер для UI. Кастомная роль с ограниченными правами НЕ даёт 403 на запрещённых операциях (помечено «после P0-5»).
|
||||||
67
tests/e2e/reports/security-edge-2026-05-26T07-03-49-977Z.md
Normal file
67
tests/e2e/reports/security-edge-2026-05-26T07-03-49-977Z.md
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
# E2E report: security-edge
|
||||||
|
|
||||||
|
Запущен: 2026-05-26T07:03:45.792Z
|
||||||
|
Длительность: 2.7с
|
||||||
|
|
||||||
|
**Итог:** 6 ✓ / 0 ✗ / 0 ⚠ / 0 ◯ (всего 6)
|
||||||
|
|
||||||
|
## ✓ Step step01_protected_require_auth: Защищённые эндпоинты без токена → 401
|
||||||
|
|
||||||
|
Длительность: 46мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Все защищённые GET без токена → 401 | ✓ проверено 8 |
|
||||||
|
|
||||||
|
## ✓ Step step02_anonymous_open: Анонимные эндпоинты (/health) доступны без токена
|
||||||
|
|
||||||
|
Длительность: 36мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | /health без токена → 200 | ✓ status=200 |
|
||||||
|
| api | /connect/token анонимен (400, не 401) | ✓ status=400 |
|
||||||
|
|
||||||
|
## ✓ Step step03_path_traversal_uploads: Path-traversal /uploads/..%2f..%2fetc/passwd → не 200 (404)
|
||||||
|
|
||||||
|
Длительность: 18мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Path-traversal не отдаёт системные файлы | ✓ все отбиты (404) |
|
||||||
|
|
||||||
|
## ✓ Step step04_sql_injection_safe: SQL-инъекция в quick-search безопасна, таблица цела
|
||||||
|
|
||||||
|
Длительность: 1846мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | quick-search с инъекцией не падает (нет 5xx) | ✓ |
|
||||||
|
| db | Таблица products цела (count не изменился) | ✓ before=1 after=1 |
|
||||||
|
|
||||||
|
## ✓ Step step05_tenant_404_not_403: GET товара чужого тенанта → 404 (не 403, не 200)
|
||||||
|
|
||||||
|
Длительность: 781мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Товар чужого тенанта → 404 (не 200/403) | ✓ status=404 |
|
||||||
|
|
||||||
|
## ✓ Step step06_cors_evil_origin: CORS не отражает Origin http://evil.com
|
||||||
|
|
||||||
|
Длительность: 10мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | ACAO не равен http://evil.com | ✓ ACAO=(нет) |
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
- Passed: 6
|
||||||
|
- Failed: 0
|
||||||
|
- Warnings: 0
|
||||||
|
- Skipped: 0
|
||||||
|
|
||||||
|
## Critical bugs
|
||||||
|
|
||||||
|
Нет.
|
||||||
|
|
@ -1,24 +1,24 @@
|
||||||
# E2E report: stock-concurrency
|
# E2E report: stock-concurrency
|
||||||
|
|
||||||
Запущен: 2026-05-26T06:28:48.064Z
|
Запущен: 2026-05-26T07:02:47.787Z
|
||||||
Длительность: 4.9с
|
Длительность: 4.9с
|
||||||
|
|
||||||
**Итог:** 4 ✓ / 0 ✗ / 0 ⚠ / 0 ◯ (всего 4)
|
**Итог:** 4 ✓ / 0 ✗ / 0 ⚠ / 0 ◯ (всего 4)
|
||||||
|
|
||||||
## ✓ Step step01_bootstrap: Орг + товар + стартовая приёмка qty=5 @100 (Stock=5, Cost=100)
|
## ✓ Step step01_bootstrap: Орг + товар + стартовая приёмка qty=5 @100 (Stock=5, Cost=100)
|
||||||
|
|
||||||
Длительность: 2198мс
|
Длительность: 2212мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| api | Bootstrap product создан | ✓ 1903eb84-9c8c-4335-9439-6d8fa6bd2102 |
|
| api | Bootstrap product создан | ✓ df50a87d-0f25-4fb0-9e35-d5e1077e5fb9 |
|
||||||
| db | Stock.Quantity == Σ StockMovement (invariant) | ✓ stock=5 sum=5 |
|
| db | Stock.Quantity == Σ StockMovement (invariant) | ✓ stock=5 sum=5 |
|
||||||
| db | Стартовый Stock == 5 | ✓ stock=5 |
|
| db | Стартовый Stock == 5 | ✓ stock=5 |
|
||||||
| db | Стартовый Cost == 100 | ✓ cost=100 |
|
| db | Стартовый Cost == 100 | ✓ cost=100 |
|
||||||
|
|
||||||
## ✓ Step step02_concurrent_distinct_supplies: Две разные приёмки (10@100 и 10@120) одновременно → Stock=25, инвариант, Cost=108
|
## ✓ Step step02_concurrent_distinct_supplies: Две разные приёмки (10@100 и 10@120) одновременно → Stock=25, инвариант, Cost=108
|
||||||
|
|
||||||
Длительность: 903мс
|
Длительность: 873мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
|
|
||||||
## ✓ Step step03_double_post_same_supply: Двойное проведение ОДНОЙ приёмки (7@100) одновременно → применяется один раз
|
## ✓ Step step03_double_post_same_supply: Двойное проведение ОДНОЙ приёмки (7@100) одновременно → применяется один раз
|
||||||
|
|
||||||
Длительность: 1378мс
|
Длительность: 1373мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -41,7 +41,7 @@
|
||||||
|
|
||||||
## ✓ Step step04_final_invariant: Финальный инвариант Stock == Σ StockMovement
|
## ✓ Step step04_final_invariant: Финальный инвариант Stock == Σ StockMovement
|
||||||
|
|
||||||
Длительность: 441мс
|
Длительность: 415мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -1,23 +1,23 @@
|
||||||
# E2E report: stock-invariant-deep
|
# E2E report: stock-invariant-deep
|
||||||
|
|
||||||
Запущен: 2026-05-26T06:28:37.859Z
|
Запущен: 2026-05-26T07:02:38.469Z
|
||||||
Длительность: 5.9с
|
Длительность: 5.7с
|
||||||
|
|
||||||
**Итог:** 10 ✓ / 0 ✗ / 0 ⚠ / 0 ◯ (всего 10)
|
**Итог:** 10 ✓ / 0 ✗ / 0 ⚠ / 0 ◯ (всего 10)
|
||||||
|
|
||||||
## ✓ Step step01_bootstrap: Орг + admin + product (стартовый остаток 0)
|
## ✓ Step step01_bootstrap: Орг + admin + product (стартовый остаток 0)
|
||||||
|
|
||||||
Длительность: 1776мс
|
Длительность: 1829мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| api | Bootstrap product создан | ✓ d8c6c47a-dd41-48de-b0a0-9cc35769a8cb |
|
| api | Bootstrap product создан | ✓ e873c3b8-a3dd-4b32-a17a-91a09364f54d |
|
||||||
| db | Stock.Quantity == 0 | ✓ actual=0 |
|
| db | Stock.Quantity == 0 | ✓ actual=0 |
|
||||||
| db | Stock.Quantity == Σ StockMovement (invariant) | ✓ stock=0 sum=0 |
|
| db | Stock.Quantity == Σ StockMovement (invariant) | ✓ stock=0 sum=0 |
|
||||||
|
|
||||||
## ✓ Step step02_supply_a_qty_20: Supply A qty=20 → invariant stock=20, Σ movement=20
|
## ✓ Step step02_supply_a_qty_20: Supply A qty=20 → invariant stock=20, Σ movement=20
|
||||||
|
|
||||||
Длительность: 448мс
|
Длительность: 463мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
|
|
||||||
## ✓ Step step03_sale_a_qty_5: RetailSale A qty=5 → invariant stock=15, Σ movement=15
|
## ✓ Step step03_sale_a_qty_5: RetailSale A qty=5 → invariant stock=15, Σ movement=15
|
||||||
|
|
||||||
Длительность: 456мс
|
Длительность: 442мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -37,7 +37,7 @@
|
||||||
|
|
||||||
## ✓ Step step04_supply_b_qty_10: Supply B qty=10 → invariant stock=25, Σ movement=25
|
## ✓ Step step04_supply_b_qty_10: Supply B qty=10 → invariant stock=25, Σ movement=25
|
||||||
|
|
||||||
Длительность: 510мс
|
Длительность: 452мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
|
|
||||||
## ✓ Step step05_sale_b_qty_8: RetailSale B qty=8 → invariant stock=17, Σ movement=17
|
## ✓ Step step05_sale_b_qty_8: RetailSale B qty=8 → invariant stock=17, Σ movement=17
|
||||||
|
|
||||||
Длительность: 467мс
|
Длительность: 455мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -57,7 +57,7 @@
|
||||||
|
|
||||||
## ✓ Step step06_unpost_sale_a: Unpost RetailSale A → invariant stock=22, Σ movement=22
|
## ✓ Step step06_unpost_sale_a: Unpost RetailSale A → invariant stock=22, Σ movement=22
|
||||||
|
|
||||||
Длительность: 475мс
|
Длительность: 447мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -67,7 +67,7 @@
|
||||||
|
|
||||||
## ✓ Step step07_repost_sale_a: Re-post RetailSale A → invariant stock=17, Σ movement=17
|
## ✓ Step step07_repost_sale_a: Re-post RetailSale A → invariant stock=17, Σ movement=17
|
||||||
|
|
||||||
Длительность: 485мс
|
Длительность: 447мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -77,7 +77,7 @@
|
||||||
|
|
||||||
## ✓ Step step08_movement_count_correct: Всего StockMovement по продукту = 6 строк (2 supply + 2 sale + reverse sale + repost sale)
|
## ✓ Step step08_movement_count_correct: Всего StockMovement по продукту = 6 строк (2 supply + 2 sale + reverse sale + repost sale)
|
||||||
|
|
||||||
Длительность: 217мс
|
Длительность: 202мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -86,7 +86,7 @@
|
||||||
|
|
||||||
## ✓ Step step09_concurrent_sales_serialized: Два POST /post одновременно на один остаток — один 200, второй 409
|
## ✓ Step step09_concurrent_sales_serialized: Два POST /post одновременно на один остаток — один 200, второй 409
|
||||||
|
|
||||||
Длительность: 627мс
|
Длительность: 584мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -97,7 +97,7 @@
|
||||||
|
|
||||||
## ✓ Step step10_final_invariant: Финальный invariant после всех операций сохраняется
|
## ✓ Step step10_final_invariant: Финальный invariant после всех операций сохраняется
|
||||||
|
|
||||||
Длительность: 437мс
|
Длительность: 396мс
|
||||||
|
|
||||||
| Тип | Проверка | Результат |
|
| Тип | Проверка | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
# E2E report: superadmin-console
|
||||||
|
|
||||||
|
Запущен: 2026-05-26T07:03:32.749Z
|
||||||
|
Длительность: 4.0с
|
||||||
|
|
||||||
|
**Итог:** 6 ✓ / 0 ✗ / 0 ⚠ / 0 ◯ (всего 6)
|
||||||
|
|
||||||
|
## ✓ Step step01_create_org_audited: Создание орг1 → 200 + запись CreateOrg в audit-log
|
||||||
|
|
||||||
|
Длительность: 1064мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Создание орг1 → 200 | ✓ status=200 |
|
||||||
|
| api | audit-log содержит CreateOrg | ✓ actions=CreateOrg |
|
||||||
|
|
||||||
|
## ✓ Step step02_archive: Архив: неверное имя → 400, верное → 204, IsArchived=true + ArchiveOrg
|
||||||
|
|
||||||
|
Длительность: 268мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Архив с неверным именем → 400 | ✓ status=400 |
|
||||||
|
| api | Архив с верным именем → 204 | ✓ status=204 |
|
||||||
|
| db | IsArchived=true, ArchivedAt задан | ✓ IsArchived=t |
|
||||||
|
| api | audit-log содержит ArchiveOrg | ✓ |
|
||||||
|
|
||||||
|
## ✓ Step step03_restore: Восстановление → 204, IsArchived=false + RestoreOrg
|
||||||
|
|
||||||
|
Длительность: 260мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Восстановление → 204 | ✓ status=204 |
|
||||||
|
| db | IsArchived=false | ✓ IsArchived=f |
|
||||||
|
| api | audit-log содержит RestoreOrg | ✓ |
|
||||||
|
|
||||||
|
## ✓ Step step04_change_owner: Смена владельца: без reason → 400, reason<10 → 400, валидно → 204 + ChangeOwner
|
||||||
|
|
||||||
|
Длительность: 1064мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Второй пользователь орг1 создан (кандидат во владельцы) | ✓ |
|
||||||
|
| api | change-owner без reason → 400 | ✓ status=400 |
|
||||||
|
| api | change-owner reason<10 → 400 | ✓ status=400 |
|
||||||
|
| api | change-owner валидный reason → 204 | ✓ status=204 |
|
||||||
|
| db | AccountOwnerUserId обновлён на нового владельца | ✓ owner=72dc5c01-7e75-4fac-bb50-ae39bd958a6a |
|
||||||
|
| api | audit-log содержит ChangeOwner | ✓ actions=ChangeOwner,RestoreOrg,ArchiveOrg,CreateOrg |
|
||||||
|
|
||||||
|
## ✓ Step step05_hard_delete: Hard-delete: не-архив → 409, до retention → 409, retention=0 + верное имя → 204, юзеры деактивированы
|
||||||
|
|
||||||
|
Длительность: 1269мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | Орг2 создана | ✓ |
|
||||||
|
| api | Удаление неархивной орг → 409 | ✓ status=409 |
|
||||||
|
| api | Удаление до retention → 409 | ✓ status=409 {"error":"Доступно через 30 дней архива."} |
|
||||||
|
| api | Удаление с неверным именем → 400 | ✓ status=400 |
|
||||||
|
| api | Удаление архивной (retention=0, верное имя) → 204 | ✓ status=204 |
|
||||||
|
| db | Организация физически удалена | ✓ rows=0 |
|
||||||
|
| db | Юзеры отвязаны/деактивированы (нет привязки к удалённой орг) | ✓ before=1 stillLinked=0 |
|
||||||
|
|
||||||
|
## ✓ Step step06_audit_log_filters: audit-log: фильтр по org и actionType, ChangeOwner хранит reason
|
||||||
|
|
||||||
|
Длительность: 55мс
|
||||||
|
|
||||||
|
| Тип | Проверка | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| api | audit орг1 содержит CreateOrg/ArchiveOrg/RestoreOrg/ChangeOwner | ✓ actions=ChangeOwner,RestoreOrg,ArchiveOrg,CreateOrg |
|
||||||
|
| api | Фильтр actionType=DeleteOrg для орг2 возвращает запись | ✓ count=1 |
|
||||||
|
| api | Фильтр actionType отсекает прочее (все строки DeleteOrg) | ✓ types=DeleteOrg |
|
||||||
|
| api | ChangeOwner в журнале хранит reason | ✓ reason=Передача владения по тикету поддержки #4242 |
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
- Passed: 6
|
||||||
|
- Failed: 0
|
||||||
|
- Warnings: 0
|
||||||
|
- Skipped: 0
|
||||||
|
|
||||||
|
## Critical bugs
|
||||||
|
|
||||||
|
Нет.
|
||||||
|
|
@ -1,64 +1,78 @@
|
||||||
# Системное тестирование Food Market — 2026-05-26
|
# Системное тестирование Food Market — 2026-05-26
|
||||||
|
|
||||||
> Инициировано Opus 4.7 по плану из `docs/TZ-тестирование.md` (продолжение сессии 2026-05-23, см. `systemic-2026-05-23.md`).
|
> Инициировано Opus 4.7 по плану `docs/TZ-тестирование.md` (продолжение `systemic-2026-05-23.md`).
|
||||||
> Среда: docker `food-market-postgres` (postgres:16-alpine, 127.0.0.1:5434) + dotnet 8 API локально на :5081 + E2E через axios/psql.
|
> Среда: docker `food-market-postgres` (postgres:16-alpine, 127.0.0.1:5434) + dotnet 8 API локально на :5081 + E2E через axios/psql + mock MoySklad.
|
||||||
> Запуск: `E2E_ADMIN_URL=http://127.0.0.1:5081 ./tests/e2e/run.sh <scenario> --api-only`.
|
> Запуск: `E2E_ADMIN_URL=http://127.0.0.1:5081 ./tests/e2e/run.sh <scenario> --api-only`.
|
||||||
|
|
||||||
## 0. TL;DR
|
## 0. TL;DR
|
||||||
|
|
||||||
| Сценарий | Результат |
|
| Сценарий | Шаги | Результат |
|
||||||
|---|---|
|
|---|---|---|
|
||||||
| **full-cycle** (signup → bootstrap → supply → sale) | **12/12 ✓** |
|
| **full-cycle** | 12 | ✓ |
|
||||||
| **multi-tenant-isolation** (Alpha/Beta + SuperAdmin override) | **12/12 ✓** |
|
| **multi-tenant-isolation** | 12 | ✓ |
|
||||||
| **documents-edge** (защита денег и инварианта на posting) | **10/10 ✓** |
|
| **documents-edge** | 10 | ✓ |
|
||||||
| **auth-edge** (refresh-rotation, подделка JWT, архив-орг, signup) | **10/10 ✓** |
|
| **auth-edge** | 10 | ✓ |
|
||||||
| **catalog-edge** (валидация, дубли, удаление с зависимостями) | **12/12 ✓** |
|
| **auth-password** | 6 | ✓ |
|
||||||
| **stock-invariant-deep** (Stock == Σ Movement, post/unpost/repost) | **10/10 ✓** |
|
| **catalog-edge** | 12 | ✓ |
|
||||||
| **stock-concurrency** (конкурентное проведение приёмок) | **4/4 ✓** |
|
| **stock-invariant-deep** | 10 | ✓ |
|
||||||
| **reports-stats** (дашбордная выручка + tenant-изоляция) | **5/5 ✓** |
|
| **stock-concurrency** | 4 | ✓ |
|
||||||
| **moysklad-import** (импорт, идемпотентность, маппинг) | **7/7 ✓** |
|
| **reports-stats** | 5 | ✓ |
|
||||||
|
| **moysklad-import** | 7 | ✓ |
|
||||||
|
| **employees** | 10 | ✓ |
|
||||||
|
| **roles** | 8 | ✓ |
|
||||||
|
| **superadmin-console** | 6 | ✓ |
|
||||||
|
| **platform-smtp** | 6 | ✓ |
|
||||||
|
| **security-edge** | 6 | ✓ |
|
||||||
|
|
||||||
**Итого 9 сценариев, 82 шага — все зелёные. Багов нет.**
|
**Итого 15 сценариев, 124 шага — все зелёные. Багов нет.**
|
||||||
|
|
||||||
**Найдено и исправлено в этой сессии: 3 бага** (1 critical, 1 high, + связка из 2 правок по безопасности refresh-токенов).
|
**Исправлено за день: 4 бага** (2 P0/critical, 1 high, 1 medium) + доработка тестируемости MoySklad.
|
||||||
|
|
||||||
## 1. Найденные баги и исправления
|
## 1. Найденные баги и исправления
|
||||||
|
|
||||||
### BUG #1 — Старый refresh-token остаётся валидным после ротации (commit 32729e7)
|
### BUG #1 (P0) — Уволенный сотрудник продолжает логиниться (commit 5091d43)
|
||||||
|
|
||||||
`auth-edge` step03. Две причины, обе закрыты:
|
`employees` step07. `EmployeesController.Delete` (увольнение и soft-delete) и `Update` (деактивация) меняли только `Employee.IsActive`, но не трогали связанный `AppUser`. Логин и refresh гейтятся на `User.IsActive` → уволенный сохранял полный доступ и обновлял токены до 30 дней (ТЗ 0.4).
|
||||||
1. `AuthorizationController.Exchange` (refresh-ветка) строил новый principal с нуля и прокидывал только `AuthorizationId`, но не `TokenId`. Handler OpenIddict `RedeemTokenEntry` читает `TokenId` из подписываемого principal — без него старый refresh не помечался `Redeemed`.
|
**Fix:** `SetLinkedUserActiveAsync` — при деактивации сотрудника гасит `User.IsActive` и отзывает его valid OpenIddict-токены; при реактивации возвращает доступ.
|
||||||
2. Даже после починки редемпшна OpenIddict по умолчанию даёт 30-секундный **reuse-leeway** — погашенный refresh ещё принимается в этом окне. Для розничной админки это дыра: утёкший refresh живёт 30с после ротации.
|
|
||||||
|
|
||||||
**Severity:** high (одна утечка refresh → продлеваемый доступ).
|
### BUG #2 (critical) — Конкурентное проведение приёмки ломает инвариант остатков (commit 15f27fd)
|
||||||
**Fix:** прокидываем `TokenId` старого refresh в новый principal + `SetRefreshTokenReuseLeeway(TimeSpan.Zero)` в `Program.cs`. Проверено в БД: старый токен переходит в `redeemed` и немедленно отвергается (4xx).
|
|
||||||
|
|
||||||
### BUG #2 — Конкурентное проведение приёмки ломает инвариант остатков (commit 15f27fd)
|
`stock-concurrency` step03. `Supply.Post` шёл на Read Committed, `ApplyMovementAsync` делает read-modify-write по `Stock.Quantity` без RowVersion. Двойное проведение одной приёмки применяло остаток дважды (`Stock=32`, `Σ Movement=39`).
|
||||||
|
**Fix:** `IsolationLevel.Serializable` + перехват конфликта сериализации (40001/40P01) → 409.
|
||||||
|
|
||||||
`stock-concurrency` step03. `Supply.Post` шёл на дефолтной изоляции (Read Committed), а `StockService.ApplyMovementAsync` делает read-modify-write по `Stock.Quantity` без RowVersion. Под гонкой:
|
### BUG #3 (high) — Refresh-token остаётся валидным после ротации (commit 32729e7)
|
||||||
- двойное проведение ОДНОЙ приёмки (оба запроса читают `Status=Draft` до коммита соседа) применяло остаток дважды — 2 `StockMovement`, но `Stock` рос на одну партию → `Stock=32`, `Σ Movement=39`;
|
|
||||||
- две разные приёмки одного товара могли потерять обновление остатка и посчитать скользящее среднее `Cost` от устаревшего `currentQty`.
|
|
||||||
|
|
||||||
**Severity:** critical (нарушение главного учётного инварианта `Stock == Σ StockMovement`).
|
`auth-edge` step03. Новый principal не получал `TokenId` старого refresh → `RedeemTokenEntry` не гасил его; плюс 30-секундный reuse-leeway OpenIddict.
|
||||||
**Fix:** проведение переведено на `IsolationLevel.Serializable` (как `RetailSale.Post`), конфликт сериализации (SQLSTATE 40001/40P01) перехватывается → 409 (клиент повторяет, а не получает 500). После фикса: `Stock=32`, `Σ=32`, statuses 204+409.
|
**Fix:** проброс `TokenId` + `SetRefreshTokenReuseLeeway(TimeSpan.Zero)`.
|
||||||
|
|
||||||
### Доработка для тестируемости — базовый URL MoySklad из конфига (commit e78e921)
|
### BUG #4 (medium) — change-owner принимал слишком короткий reason (commit 01568ba)
|
||||||
|
|
||||||
`MoySkladClient.BaseUrl` был константой `api.moysklad.ru`, импорт нельзя было прогнать без боевого токена. Вынесли `BaseAddress` в `MoySklad:BaseUrl` (дефолт — прежний боевой URL); e2e наводит клиент на mock-сервер `lib/moysklad-mock.ts`. Прод-поведение не меняется.
|
`superadmin-console` step04. Смена владельца писала reason в аудит, но проверяла лишь непустоту. PlatformSettings уже требует ≥10 — привели change-owner к той же планке (ТЗ 2.8).
|
||||||
|
|
||||||
## 2. Что покрыто впервые в этой сессии
|
### Доработка — базовый URL MoySklad из конфигурации (commit e78e921)
|
||||||
|
|
||||||
- **Конкурентность приёмок** (`stock-concurrency`) — раньше под Serializable был только `RetailSale.Post`; теперь и `Supply.Post`.
|
`MoySklad:BaseUrl` (дефолт — боевой) позволяет наводить клиент на mock-сервер в e2e, не трогая прод.
|
||||||
- **Дашбордная выручка** (`reports-stats`) — только Posted-чеки, непрерывная серия по дням, параметр `days`, строгая tenant-изоляция `/stats`.
|
|
||||||
- **Импорт MoySklad** (`moysklad-import`) — сохранение/маскирование токена, test-connection, фоновый job, идемпотентность повторного импорта (`overwrite=false → Skipped`), обновление по ключу (`overwrite=true → Updated`), маппинг полей в БД (BIN/тип/адрес контрагента; артикул/НДС/упаковка/цена/штрихкод/группа/страна товара) — поля сверены с `MoySkladDtos`/remap 1.2.
|
|
||||||
|
|
||||||
## 3. Logic gaps (не баги — нереализованный функционал по ТЗ 2.12)
|
## 2. Покрытие по разделам ТЗ
|
||||||
|
|
||||||
- Отчёт **«прибыль»** (выручка − себестоимость) не реализован: `RetailSaleLine` не хранит снимок себестоимости, `/stats` отдаёт только валовую выручку.
|
P0/P1 функциональные области, реализованные в коде, — покрыты и зелёные:
|
||||||
- **ABC-анализ**, **«остатки на дату»** (`SUM(Movement) до даты`), **экспорт CSV/XLSX** — отдельного `ReportsController` нет.
|
Auth (login/refresh/signup/forgot-reset), Multi-tenancy, Catalog, Supplies, RetailSales, Stock (+ конкурентность), Employees, Roles, SuperAdmin Console, SMTP, MoySklad import, дашбордная выручка, безопасность (auth-гейт, traversal, SQLi, CORS, межтенантная 404).
|
||||||
- `Supply.Unpost` использует те же read-modify-write по `Stock` без транзакции — под одновременным unpost теоретически уязвим к lost update (вне фокуса этой сессии; проведение `Post` закрыто).
|
|
||||||
|
|
||||||
## 4. Замечание по окружению
|
## 3. Logic gaps — нереализованный по ТЗ функционал (НЕ баги)
|
||||||
|
|
||||||
- На dev-vm установлен только SDK **8.0.126**; в `global.json` репозитория остаётся `8.0.417`. Локальный даунгрейд `global.json` использован только для сборки и **в коммиты не включён**.
|
- **Отчёты (2.12):** профит по себестоимости, ABC-анализ, «остатки на дату», экспорт CSV/XLSX — нет `ReportsController`, `RetailSaleLine` без Cost-снимка. Есть только `/stats` (валовая выручка).
|
||||||
- `admin.food-market.kz` — отдельный деплой с другой БД; e2e обязательно гонять против локального API, подключённого к контейнеру `food-market-postgres` (иначе DB-проверки через `docker exec` некогерентны).
|
- **Складские документы (2.11):** Оприходование/Списание/Перемещение/Инвентаризация — не реализованы (только read-only `StockController`).
|
||||||
|
- **POS Sync API (2.13)** — не реализован.
|
||||||
|
- **Permission-based авторизация (2.7.2, «после P0-5»)** — эндпоинты только role-based `[Authorize(Roles=...)]`; флаги `RolePermissions` справочные.
|
||||||
|
- **Рейт-лимит логина (2.1.2, «после P0-3»)** — на `/connect/token` нет (у forgot-password — есть).
|
||||||
|
- **Системных ролей 3, а не 4-6** — намеренное упрощение (миграция `Phase4b_RolesSimplify`): Администратор/Кладовщик/Кассир. ТЗ устарело.
|
||||||
|
- `Supply.Unpost` использует тот же read-modify-write без транзакции — теоретически уязвим к гонке (вне фокуса; `Post` закрыт).
|
||||||
|
|
||||||
|
## 4. Вне зоны API-e2e (по ТЗ — отдельные инструменты)
|
||||||
|
|
||||||
|
UI/UX (2.14, Playwright), публичный сайт (2.15, Astro/Lighthouse), инфраструктура/DevOps (2.16), нагрузочное (2.18) — требуют браузера/нагрузочных стендов, в данном прогоне не покрывались.
|
||||||
|
|
||||||
|
## 5. Окружение
|
||||||
|
|
||||||
|
- SDK на dev-vm только **8.0.126**; `global.json` репозитория остаётся `8.0.417` (локальный даунгрейд в коммиты не включён).
|
||||||
|
- `admin.food-market.kz` — отдельный деплой с другой БД; e2e гоняются против локального API, подключённого к контейнеру `food-market-postgres`.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue