# Системное тестирование Food Market — 2026-05-26

> Инициировано 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 + mock MoySklad.
> Запуск: `E2E_ADMIN_URL=http://127.0.0.1:5081 ./tests/e2e/run.sh <scenario> --api-only`.

## 0. TL;DR

| Сценарий | Шаги | Результат |
|---|---|---|
| **full-cycle** | 12 | ✓ |
| **multi-tenant-isolation** | 12 | ✓ |
| **documents-edge** | 10 | ✓ |
| **auth-edge** | 10 | ✓ |
| **auth-password** | 6 | ✓ |
| **catalog-edge** | 12 | ✓ |
| **stock-invariant-deep** | 10 | ✓ |
| **stock-concurrency** | 4 | ✓ |
| **reports-stats** | 5 | ✓ |
| **moysklad-import** | 7 | ✓ |
| **employees** | 10 | ✓ |
| **roles** | 8 | ✓ |
| **superadmin-console** | 6 | ✓ |
| **platform-smtp** | 6 | ✓ |
| **security-edge** | 6 | ✓ |

**Итого 15 сценариев, 124 шага — все зелёные. Багов нет.**

**Исправлено за день: 4 бага** (2 P0/critical, 1 high, 1 medium) + доработка тестируемости MoySklad.

## 1. Найденные баги и исправления

### BUG #1 (P0) — Уволенный сотрудник продолжает логиниться (commit 5091d43)

`employees` step07. `EmployeesController.Delete` (увольнение и soft-delete) и `Update` (деактивация) меняли только `Employee.IsActive`, но не трогали связанный `AppUser`. Логин и refresh гейтятся на `User.IsActive` → уволенный сохранял полный доступ и обновлял токены до 30 дней (ТЗ 0.4).
**Fix:** `SetLinkedUserActiveAsync` — при деактивации сотрудника гасит `User.IsActive` и отзывает его valid OpenIddict-токены; при реактивации возвращает доступ.

### BUG #2 (critical) — Конкурентное проведение приёмки ломает инвариант остатков (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.

### BUG #3 (high) — Refresh-token остаётся валидным после ротации (commit 32729e7)

`auth-edge` step03. Новый principal не получал `TokenId` старого refresh → `RedeemTokenEntry` не гасил его; плюс 30-секундный reuse-leeway OpenIddict.
**Fix:** проброс `TokenId` + `SetRefreshTokenReuseLeeway(TimeSpan.Zero)`.

### BUG #4 (medium) — change-owner принимал слишком короткий reason (commit 01568ba)

`superadmin-console` step04. Смена владельца писала reason в аудит, но проверяла лишь непустоту. PlatformSettings уже требует ≥10 — привели change-owner к той же планке (ТЗ 2.8).

### Доработка — базовый URL MoySklad из конфигурации (commit e78e921)

`MoySklad:BaseUrl` (дефолт — боевой) позволяет наводить клиент на mock-сервер в e2e, не трогая прод.

## 2. Покрытие по разделам ТЗ

P0/P1 функциональные области, реализованные в коде, — покрыты и зелёные:
Auth (login/refresh/signup/forgot-reset), Multi-tenancy, Catalog, Supplies, RetailSales, Stock (+ конкурентность), Employees, Roles, SuperAdmin Console, SMTP, MoySklad import, дашбордная выручка, безопасность (auth-гейт, traversal, SQLi, CORS, межтенантная 404).

## 3. Logic gaps — нереализованный по ТЗ функционал (НЕ баги)

- **Отчёты (2.12):** профит по себестоимости, ABC-анализ, «остатки на дату», экспорт CSV/XLSX — нет `ReportsController`, `RetailSaleLine` без Cost-снимка. Есть только `/stats` (валовая выручка).
- **Складские документы (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`.
