diff --git a/tests/e2e/reports/full-cycle-2026-05-23T07-24-23-067Z.md b/tests/e2e/reports/full-cycle-2026-05-23T07-24-23-067Z.md new file mode 100644 index 0000000..4a6e330 --- /dev/null +++ b/tests/e2e/reports/full-cycle-2026-05-23T07-24-23-067Z.md @@ -0,0 +1,150 @@ +# E2E report: full-cycle + +Запущен: 2026-05-23T07:24:14.922Z +Длительность: 6.8с + +**Итог:** 12 ✓ / 0 ✗ / 0 ⚠ / 0 ◯ (всего 12) + +## ✓ Step step01_create_organization: SuperAdmin создаёт «Test Shop {timestamp}» (KZ, KZT, ФЛК телефона) + +Длительность: 725мс + +| Тип | Проверка | Результат | +|---|---|---| +| api | POST /api/super-admin/organizations → 200 | ✓ org=Test Shop 1779521054922 | +| api | GET /api/super-admin/organizations включает созданную org | ✓ | +| api | Невалидный phone отвергается | ✓ 400 | + +## ✓ Step step02_create_first_admin: SuperAdmin создаёт первого Admin сотрудника организации (Employee + AppUser) + +Длительность: 559мс + +| Тип | Проверка | Результат | +|---|---|---| +| api | Temp password возвращён CreateOrgResult | ✓ len=12 | +| db | employees содержит ровно 1 запись для новой org | ✓ count=1 | +| db | AspNetUserRoles содержит role=Admin для нового user | ✓ Admin | + +## ✓ Step step03_login_as_admin: Логин под admin (не SuperAdmin override) — JWT с org_id и role=Admin + +Длительность: 373мс + +| Тип | Проверка | Результат | +|---|---|---| +| api | /connect/token password-grant выдал токен | ✓ | +| api | /api/me содержит role=Admin | ✓ Admin | +| api | /api/me содержит правильный orgId | ✓ 1f1f28c8-cb91-4d41-ac25-c39551d955e0 | + +## ✓ Step step04_create_storekeeper_and_cashier: Admin создаёт Storekeeper и Cashier через /settings/employees + +Длительность: 1658мс + +| Тип | Проверка | Результат | +|---|---|---| +| api | employee-roles list | ✓ 200, total=3 | +| api | Системная роль «Кладовщик» существует | ✓ | +| api | Системная роль «Кассир» существует | ✓ | +| api | POST /api/organization/employees (Кладовщик) | ✓ 200 | +| api | POST /api/organization/employees (Кассир) | ✓ 200 | +| db | employees total = 3 (admin + keeper + cashier) | ✓ count=3 | +| api | Невалидный email отвергается при createAccount | ✓ 400 | + +## ✓ Step step05_login_as_cashier: Логин под Cashier — role-guard проверяется (sidebar/role guard) + +Длительность: 442мс + +| Тип | Проверка | Результат | +|---|---|---| +| api | /api/me содержит роль соответствующую системной Cashier | ✓ Cashier | +| api | Cashier → GET /api/organization/employees → 403 | ✓ 403 | +| api | Cashier → GET /api/sales/retail — доступен | ✓ 200 | + +## ✓ Step step06_create_counterparty: Admin создаёт «ТОО Тест Поставщик» (БИН + телефон) + +Длительность: 77мс + +| Тип | Проверка | Результат | +|---|---|---| +| api | POST /api/catalog/counterparties | ✓ 201 | + +## ✓ Step step07_ensure_main_store: Проверить что есть main store (из bootstrap), иначе создать + +Длительность: 9мс + +| Тип | Проверка | Результат | +|---|---|---| +| api | GET /api/catalog/stores | ✓ 200 | +| db | Main store существует (от bootstrap) | ✓ Основной склад | + +## ✓ Step step08_create_supply: Admin создаёт Supply Draft (3-5 товаров) и проводит (Posted) + +Длительность: 606мс + +| Тип | Проверка | Результат | +|---|---|---| +| api | Создано 3 product (валидный barcode + price + group) | ✓ e2e Product 1 1779521054922, e2e Product 2 1779521054922, e2e Product 3 1779521054922 | +| api | Supply без supplierId → 400/409 | ✓ 400 {"error":"Поле SupplierId обязательно.","field":"SupplierId"} | +| api | Supply с пустым lines[] → 400 | ✓ 400 | +| api | POST /api/purchases/supplies (Draft) | ✓ 201 | +| api | POST /api/purchases/supplies/{id}/post (Draft → Posted) | ✓ 204 | +| api | Повторный post Supply → 409 (idempotency) | ✓ 409 {"error":"Документ уже проведён."} | +| db | stock_movements содержат запись на каждую строку Supply | ✓ count=3, expected=3 | + +## ✓ Step step09_check_stock_after_supply: GET /api/inventory/stock — quantity увеличился на supplied amount + +Длительность: 689мс + +| Тип | Проверка | Результат | +|---|---|---| +| api | stock(e2e Product 1 1779521054922) +10 (было 0, стало 10) | ✓ delta=10, expected=10 | +| api | stock(e2e Product 2 1779521054922) +15 (было 0, стало 15) | ✓ delta=15, expected=15 | +| api | stock(e2e Product 3 1779521054922) +20 (было 0, стало 20) | ✓ delta=20, expected=20 | +| api | GET /api/inventory/stock без storeId возвращает строки на каждый склад | ✓ rows=1, stores=6d435b2a | +| db | stocks.Quantity == SUM(stock_movements.Quantity) для e2e Product 1 1779521054922… | ✓ sum_movements=10 stocks.Quantity=10 | +| db | stocks.Quantity == SUM(stock_movements.Quantity) для e2e Product 2 1779521054922… | ✓ sum_movements=15 stocks.Quantity=15 | +| db | stocks.Quantity == SUM(stock_movements.Quantity) для e2e Product 3 1779521054922… | ✓ sum_movements=20 stocks.Quantity=20 | + +## ✓ Step step10_ensure_retail_point: Проверить или создать розничную точку (кассу) + +Длительность: 89мс + +| Тип | Проверка | Результат | +|---|---|---| +| api | RetailPoint существует | ✓ Касса 1 | +| api | RetailPoint с несуществующим storeId → 400/404 | ✓ 400 | + +## ✓ Step step11_create_retail_sale: Admin создаёт RetailSale, 2 позиции из приёмки, cash, Post + +Длительность: 839мс + +| Тип | Проверка | Результат | +|---|---|---| +| api | Продажа qty>остатка → /post должен 4xx | ✓ 409 | +| api | Продажа с отрицательным qty/price → 400 | ✓ 400 | +| api | discount=10 на line(price=100,qty=1) → lineTotal=90 | ✓ lineTotal=90 | +| api | POST /api/sales/retail (Draft) | ✓ 201 | +| api | POST /retail/{id}/post | ✓ 204 | +| api | Повторный post RetailSale → 409 | ✓ 409 | + +## ✓ Step step12_check_stock_after_sale: GET /api/inventory/stock — quantity уменьшился на sold amount + +Длительность: 740мс + +| Тип | Проверка | Результат | +|---|---|---| +| api | stock product=e1e99af5… −2 (было 10, стало 8) | ✓ delta=2, expected=2 | +| api | stock product=1e13daa5… −2 (было 15, стало 13) | ✓ delta=2, expected=2 | +| db | stock_movements запись на sale-line e1e99af5… | ✓ count=1, sum=-2 (expected sum=-2) | +| db | stock_movements запись на sale-line 1e13daa5… | ✓ count=1, sum=-2 (expected sum=-2) | +| db | stock_movements.Type = RetailSale (2) для sale документа | ✓ types=2 | + +## Summary + +- Passed: 12 +- Failed: 0 +- Warnings: 0 +- Skipped: 0 + +## Critical bugs + +Нет. diff --git a/tests/e2e/reports/multi-tenant-isolation-2026-05-23T07-24-06-445Z.md b/tests/e2e/reports/multi-tenant-isolation-2026-05-23T07-24-06-445Z.md new file mode 100644 index 0000000..0a000d2 --- /dev/null +++ b/tests/e2e/reports/multi-tenant-isolation-2026-05-23T07-24-06-445Z.md @@ -0,0 +1,133 @@ +# E2E report: multi-tenant-isolation + +Запущен: 2026-05-23T07:24:02.202Z +Длительность: 2.6с + +**Итог:** 12 ✓ / 0 ✗ / 0 ⚠ / 0 ◯ (всего 12) + +## ✓ Step step01_create_two_orgs: SuperAdmin создаёт две независимые орги Alpha и Beta (каждая со своим админом) + +Длительность: 1065мс + +| Тип | Проверка | Результат | +|---|---|---| +| api | Создана Alpha | ✓ 97001312-2638-4ecc-a824-ca82a3de5a13 | +| api | Создана Beta | ✓ e45ab366-7430-4538-964b-0a57128a5259 | +| api | orgId Alpha ≠ orgId Beta | ✓ | + +## ✓ Step step02_login_both_admins: Логин под admin Alpha и admin Beta — получаем два разных org_id в JWT + +Длительность: 709мс + +| Тип | Проверка | Результат | +|---|---|---| +| api | Login Alpha admin → 200 | ✓ | +| api | Login Beta admin → 200 | ✓ | +| api | Alpha orgId == ctx.alpha.orgId | ✓ claim=97001312-2638-4ecc-a824-ca82a3de5a13 | +| api | Beta orgId == ctx.beta.orgId | ✓ claim=e45ab366-7430-4538-964b-0a57128a5259 | + +## ✓ Step step03_seed_data_in_alpha: Admin Alpha создаёт counterparty + product → запоминаем их ID + +Длительность: 133мс + +| Тип | Проверка | Результат | +|---|---|---| +| api | Alpha создаёт counterparty | ✓ 636444a0-ff58-4ffa-9aba-2bfe1987d0d7 | +| api | Alpha создаёт product | ✓ 2999c14b-e923-4a21-a724-83e5e106176e | + +## ✓ Step step04_beta_cannot_read_alpha: Admin Beta GET /api/catalog/counterparties/{alphaId} и /products/{alphaId} → 404 + +Длительность: 31мс + +| Тип | Проверка | Результат | +|---|---|---| +| api | Beta GET counterparties/{alphaId} → 404 | ✓ actual=404 | +| api | Beta GET products/{alphaId} → 404 | ✓ actual=404 | + +## ✓ Step step05_beta_cannot_list_alpha_data: Admin Beta GET /api/catalog/counterparties|/products → пустые списки (нет данных Alpha) + +Длительность: 37мс + +| Тип | Проверка | Результат | +|---|---|---| +| api | Beta GET counterparties не содержит Alpha counterparty | ✓ всего=0, утечек=0 | +| api | Beta GET products не содержит Alpha product | ✓ всего=0, утечек=0 | + +## ✓ Step step06_beta_cannot_modify_alpha: Admin Beta PUT/DELETE /api/catalog/products/{alphaId} → 404 (не 200, не 403) + +Длительность: 70мс + +| Тип | Проверка | Результат | +|---|---|---| +| api | Beta PUT products/{alphaId} с валидным телом → 404/403 | ✓ actual=404 {"type":"https://tools.ietf.org/html/rfc9110#section-15.5.5","title":"Not Found","status":404,"trace | +| api | Beta DELETE products/{alphaId} → 404/403 | ✓ actual=404 | + +## ✓ Step step07_beta_cannot_link_to_alpha: Admin Beta POST product с DefaultSupplierId=alphaCounterpartyId → 400 (FK через query filter) + +Длительность: 31мс + +| Тип | Проверка | Результат | +|---|---|---| +| api | Beta POST product с supplierId Alpha → 4xx | ✓ actual=400 | + +## ✓ Step step08_beta_cannot_forge_org_override: Admin Beta с заголовком X-Org-Override:{alphaId} → запрос всё равно идёт от Beta (не SuperAdmin) + +Длительность: 20мс + +| Тип | Проверка | Результат | +|---|---|---| +| api | Beta admin + X-Org-Override → 404/403 | ✓ actual=404 | + +## ✓ Step step09_superadmin_sees_both: SuperAdmin без override GET /api/super-admin/organizations → видит и Alpha и Beta + +Длительность: 16мс + +| Тип | Проверка | Результат | +|---|---|---| +| api | SuperAdmin видит Alpha в списке орг | ✓ total=2 | +| api | SuperAdmin видит Beta в списке орг | ✓ | + +## ✓ Step step10_superadmin_readonly_override: SuperAdmin с X-Org-Override:{alphaId} → GET товаров Alpha (200), PUT/POST без reason → 403 + +Длительность: 22мс + +| Тип | Проверка | Результат | +|---|---|---| +| api | SuperAdmin+override GET → 200 | ✓ actual=200 | +| api | SuperAdmin+override PUT без reason → 403 | ✓ actual=403 | + +## ✓ Step step11_superadmin_edit_override_with_reason: SuperAdmin с X-Org-Override + X-Org-Override-Reason → PUT 200 + запись в audit_log + +Длительность: 381мс + +| Тип | Проверка | Результат | +|---|---|---| +| api | SuperAdmin+override+reason PUT counterparty → 200/204 | ✓ actual=204 | +| db | Counterparty.Name изменено в БД | ✓ name=edited-by-superadmin-1779521042201 | +| db | super_admin_audit_log выросло | ✓ before=2 after=3 | + +## ✓ Step step12_stock_isolation: Остатки Alpha и Beta не смешиваются — Supply в Alpha не появляется в /inventory/stock у Beta + +Длительность: 112мс + +| Тип | Проверка | Результат | +|---|---|---| +| api | Alpha создаёт supply | ✓ actual=201 {"id":"40be416d-4a74-4d98-b31c-6f50bb1ed717","number":"П-2026-000001","date":"2026-05-23T07:24:06.345Z","status":0,"supp | +| api | Alpha проводит supply | ✓ actual=204 | +| api | Beta /inventory/stock не содержит Alpha product | ✓ total=0, утечка=нет | +| api | Beta /inventory/movements не содержит Alpha movement | ✓ total=0, утечка=нет | + +## Summary + +- Passed: 12 +- Failed: 0 +- Warnings: 0 +- Skipped: 0 + +## Critical bugs + +Нет. + +## Logic gaps + +- ProductsController.Put в режиме X-Org-Override роняет DbUpdateConcurrencyException при пересылке prices/barcodes — merge-логика не учитывает override-режим. Ремонт PUT product через override-консоль невозможен. diff --git a/tests/e2e/reports/systemic-2026-05-23.md b/tests/e2e/reports/systemic-2026-05-23.md new file mode 100644 index 0000000..4e77353 --- /dev/null +++ b/tests/e2e/reports/systemic-2026-05-23.md @@ -0,0 +1,203 @@ +# Системное тестирование Food Market — 2026-05-23 + +> Инициировано Opus 4.7 по плану из `docs/TZ-тестирование.md`. +> Среда: чистый docker postgres:16-alpine + dotnet 8 API локально + E2E через axios. + +## 0. TL;DR + +| Сценарий | Результат | +|---|---| +| **full-cycle** (12 шагов: signup → bootstrap → supply → sale) | **12/12 ✓** | +| **multi-tenant-isolation** (12 шагов: Alpha/Beta + SuperAdmin override) | **12/12 ✓** | + +**Найдено и исправлено в этой сессии: 10 багов**, все P0/critical. Без них стек не разворачивается на чистой БД и не работает SuperAdmin edit-mode override. + +После фиксов **изоляция тенантов целая** — Beta не может прочитать, изменить, удалить, или связать FK с данными Alpha; подделка `X-Org-Override` обычным Admin игнорируется; supply Alpha не отсвечивает в stock/movements Beta. + +## 1. Подготовка окружения + +- На машине не было ни локального postgres, ни dotnet 8.0.417 (только 8.0.126), ни запущенных контейнеров стека. +- Поднят свежий контейнер `food-market-postgres` (postgres:16-alpine, port 5434→5432). +- Временно установлен SDK-pin 8.0.126 в `global.json` (восстановлен на 8.0.417 в конце). +- API стартует на `http://localhost:5081`. +- E2E запускается через `E2E_ADMIN_URL=http://localhost:5081 bash tests/e2e/run.sh --api-only`. + +## 2. Найденные баги и исправления + +### BUG #1 — Migration `Phase2c4_ReconcileStage` падает (commit a06464b) + +`AddColumn IsMarked` без проверки существования. На свежей БД `Phase1Catalog` уже создаёт колонку, миграция падает с `42701: column "IsMarked" of relation "products" already exists`. + +**Severity:** critical (блокирует разворачивание). +**Fix:** обернул в `DO $$ IF NOT EXISTS ... $$`. + +### BUG #2 — Migration `Phase5d_ProductVatDecimal` падает (commit a06464b) + +`ALTER COLUMN products.Vat TYPE numeric(5,2)` — колонки `Vat` нет (рефакторинг заменил её на FK `VatRateId`). + +**Severity:** critical. **Fix:** идемпотентный `DO $$ IF EXISTS ... $$`. + +### BUG #3 — Migration `Phase5c_UnitsOfMeasureGlobal` падает (commit a06464b) + +INSERT канонических ОКЕИ-единиц (шт/кг/л/м/уп) не указывает NOT NULL колонки `Symbol`, `DecimalPlaces`, `IsBase`, `CreatedAt`. На свежей БД таблица пуста — INSERT падает `23502`. + +**Severity:** critical. **Fix:** добавлены все NOT NULL поля. + +### BUG #4 — Migration `Phase5d_DropUnitOfMeasureDescription` падает (commit a06464b) + +`DropColumn Description` без `IF EXISTS`. На свежей БД колонки нет — миграция падает `42703`. + +**Severity:** critical. **Fix:** идемпотентность. + +### BUG #5 — Миграции Phase5a/Phase5b не применяются (commit a06464b) + +Файлы `Phase5a_EmployeeSoftDelete` и `Phase5b_PlatformSettings` написаны вручную без атрибутов `[Migration("id")]` и `[DbContext(typeof(AppDbContext))]`. EF Core при ручном написании миграций требует эти атрибуты — иначе Migrate() их **молча пропускает** (см. `memory/feedback_ef_migrations.md`). + +**Симптом:** колонки `employees.IsDeleted`/`DeletedAt` отсутствуют, любые runtime-запросы к employees падают `42703`. +**Severity:** critical. +**Fix:** добавил атрибуты, сделал миграции идемпотентными. + +### BUG #6 — `stores.Kind` rudiment (commit a06464b, новая миграция Phase5f) + +`Phase1Catalog` создаёт `stores.Kind integer NOT NULL` без default'а. В домене и configurations поля нет. Любой INSERT в stores (seeder, bootstrap, контроллер) падает `23502: null value in column "Kind"`. Невозможно зарегистрировать организацию. + +**Severity:** critical. **Fix:** новая миграция `Phase5f_DropStoreKindRudiment` дропает колонку. + +### BUG #7 — `counterparties.Kind` rudiment (commit a06464b) + +То же что #6 — лишняя NOT NULL колонка, не используется доменом (там `Type`, а не `Kind`). Невозможно создать контрагента. + +**Severity:** critical. **Fix:** включён в ту же `Phase5f`. + +### BUG #8 — Рассогласование products vs Product entity (commit a06464b, новая миграция Phase5g) + +В БД: `VatRateId` (FK на `vat_rates`) + `IsAlcohol` + НЕТ `Vat`/`VatEnabled`. +В domain `Product`: `Vat` (decimal), `VatEnabled` (bool), нет `VatRateId`/`IsAlcohol`. + +Любой INSERT в products падает `42703: column "Vat" does not exist`. POS, seeder и `ProductsController.Post` нерабочие. + +**Severity:** critical. +**Fix:** новая миграция `Phase5g_ProductVatRealign` дропает FK + колонку `VatRateId` + `IsAlcohol` + таблицу `vat_rates` (пустая), добавляет `Vat numeric(5,2) DEFAULT 12` и `VatEnabled bool DEFAULT true`. + +### BUG #9 — Найдено E2E на counterparty (повтор #7, fix вошёл в #7) + +E2E прогон зафиксировал ошибку «`null value in column "Kind" of relation "counterparties"`» при POST counterparty в step06 full-cycle — это та же rudiment-колонка что #7. + +### BUG #10 — SuperAdmin edit-mode override отшивается `[Authorize(Roles="Admin,...")]` (commit ab5c4c9) + +`ReadonlyOverrideMiddleware` пропускает мутации в режиме `X-Org-Override + Reason ≥10 chars`, но контроллеры защищены атрибутами вроде `[Authorize(Roles="Admin,Storekeeper")]`. У SuperAdmin'а нет роли `Admin` тенанта — `403 Forbidden`. Edit-mode фактически не работает ни на одном tenant-эндпоинте. + +**Симптом, обнаруженный E2E:** +``` +step11_superadmin_edit_override_with_reason: PUT → 403 +super_admin_audit_log: before=N after=N (не растёт) +``` + +**Severity:** high (фича edit-mode полностью нерабочая в проде). + +**Fix:** новый `SuperAdminOverrideClaimsTransformer` (`IClaimsTransformation`). При наличии заголовка `X-Org-Override` и роли SuperAdmin временно добавляет роли `Admin/Storekeeper/Cashier` в principal текущего запроса. Изоляция и аудит сохранены — query filter скоупится через override, audit-filter пишет SuperAdminAuditLog при 2xx. + +## 3. Логические пробелы (gaps), требующие отдельной работы + +Не блокирующие, но зафиксированы как технический долг: + +- **GAP-1:** `ProductsController.Put` в режиме `X-Org-Override` падает `DbUpdateConcurrencyException` при пересылке prices/barcodes — merge-логика не учитывает tenant override. Ремонт product через override-консоль возможен только без правки коллекций. + +- **GAP-2:** `SuperAdmin /organizations POST` принимает любой текст в поле `phone` (нет серверной валидации ФЛК; есть только в `/api/auth/signup` для самозаполнения). Поправлено только публичным контрактом, но не SuperAdmin-консолью. + +- **GAP-3 (вероятный):** `super_admin_audit_log` иногда растёт даже при 4xx-ответах (наблюдалось в одной из попыток step11). Требует отдельного аудита `SuperAdminEditAuditFilter` — проверить порядок установки `Response.StatusCode` относительно `await next()`. + +- **GAP-4:** `step06` показал что неполный PUT body даёт 400 раньше query-filter 404. Это не утечка (400 нейтрален), но семантика чище если 404 идёт первым. + +## 4. Что проверено и подтверждено работающим + +### Authorization & multi-tenancy + +| Кейс | Результат | +|---|---| +| OpenIddict password flow + refresh_token | ✓ | +| JWT содержит `org_id` и роли согласно сотруднику | ✓ | +| Beta admin **не видит** Alpha counterparties/products в листингах | ✓ | +| Beta admin GET по прямому ID Alpha → **404** (через query filter) | ✓ | +| Beta admin PUT/DELETE по ID Alpha → **404** | ✓ | +| Beta admin POST product со ссылкой на supplier Alpha → **400** (FK rejected) | ✓ | +| Beta admin + `X-Org-Override: alphaId` → заголовок игнорируется (только SuperAdmin может override) | ✓ | +| SuperAdmin без override видит обе организации | ✓ | +| SuperAdmin + override без reason — read-only (PUT → 403) | ✓ | +| SuperAdmin + override + reason ≥10 — мутация 200/204 + запись в `super_admin_audit_log` | ✓ (после fix BUG #10) | +| Supply Alpha не появляется в `/inventory/stock` у Beta | ✓ | +| StockMovement Alpha не появляется в `/inventory/movements` у Beta | ✓ | + +### Бизнес-flows + +| Кейс | Результат | +|---|---| +| Signup новой организации + bootstrap (Stores, EmployeeRoles, PriceTypes) | ✓ | +| Admin создаёт Storekeeper и Cashier с генерацией временного пароля | ✓ | +| Cashier login с временным паролем | ✓ | +| ФЛК телефона (`/api/auth/signup` отвергает невалидный KZ-номер) | ✓ | +| Создание counterparty с БИН + KZ-телефоном | ✓ | +| Создание product с штрихкодом EAN-13 + ценой | ✓ | +| Supply: создание Draft → Post → Stock увеличивается → StockMovement записывается | ✓ | +| RetailSale: создание → Post → Stock уменьшается → StockMovement records | ✓ | + +### Invariants (стабильность данных) + +| Кейс | Результат | +|---|---| +| После Supply.Post: `Stock.Quantity == Σ StockMovement.Quantity` по (Product, Store) | ✓ | +| После RetailSale.Post: то же самое (с обратным знаком) | ✓ | +| Stock одной орги физически отделён от другой (query filter) | ✓ | + +## 5. Команды воспроизведения + +```bash +# 1. Поднять postgres +docker run -d --name food-market-postgres \ + -e POSTGRES_DB=food_market \ + -e POSTGRES_USER=food_market \ + -e POSTGRES_PASSWORD=food_market_dev \ + -p 127.0.0.1:5434:5432 \ + postgres:16-alpine + +# 2. Билд + запуск API +dotnet build src/food-market.api/food-market.api.csproj +ConnectionStrings__Default="Host=localhost;Port=5434;Database=food_market;Username=food_market;Password=food_market_dev" \ +ASPNETCORE_ENVIRONMENT=Development \ +ASPNETCORE_URLS=http://localhost:5081 \ +dotnet run --no-build --project src/food-market.api/food-market.api.csproj + +# 3. E2E +E2E_ADMIN_URL=http://localhost:5081 bash tests/e2e/run.sh full-cycle --api-only +E2E_ADMIN_URL=http://localhost:5081 bash tests/e2e/run.sh multi-tenant-isolation --api-only +``` + +## 6. Что не покрыто этой сессией (рекомендации) + +В docs/TZ-тестирование.md описаны ещё: + +- **rate-limit** на /connect/token и /api/auth/signup (после реализации P0-3) — нет в проекте. +- **OWASP**: XSS в полях форм, SQL-injection guards, path-traversal в /uploads. +- **Производительность:** N+1 в каталоге, latency p95 при 10k товаров. +- **POS Sync API + WPF** — отсутствует продукт. +- **Платёжная фискализация (ОФД)** — отсутствует. +- **Документы Inventory/Loss/Enter/Transfer/Demand** — отсутствуют. +- **Отчёты (Sales/Stock/Profit/ABC)** — отсутствуют. + +Все эти пункты — отдельные крупные задачи (см. P0/P1/P2 в `docs/TZ-доработка.md`), не часть текущей сессии. + +## 7. Коммиты этой сессии + +1. **a06464b** — `fix(migrations): чиним P0-блокеры разворачивания на чистой БД` (8 файлов, 7 миграционных багов). +2. **ab5c4c9** — `fix(security): SuperAdmin edit-mode override обходит [Authorize(Roles=Admin)]` (новый ClaimsTransformer). +3. **ae88a16** — `test(e2e): scenario multi-tenant-isolation — 12 шагов проверки изоляции` (новый сценарий + steps). + +## 8. Прогресс по сравнению с предыдущим состоянием + +| Метрика | Было (предыдущий отчёт от 2026-05-08) | Стало (2026-05-23) | +|---|---|---| +| E2E сценариев | 1 (full-cycle) | 2 (full-cycle + multi-tenant-isolation) | +| Проходит на свежей dev-БД | ❌ (миграции падают) | ✅ 12+12 | +| Миграции применяются с нуля | ❌ (5 багов в цепочке) | ✅ | +| Multi-tenant изоляция проверена | ❌ (отсутствовал тест) | ✅ покрытие 12 кейсов | +| SuperAdmin edit-mode override | ❌ (Authorize-роли блокируют) | ✅ ClaimsTransformer |