arm-ka-docs/dev/qa-guide.md
Andrey Epifancev 0600c7c5a1 dev/qa-guide.md: гайд тестировщика — ПО, запуск стенда Срезов 0–1, чек-лист сценариев, ограничения, баги
Путь проверен 05.10.2026 на чистом worktree: compose, migrate/seed, бэкенд, фронт через rsbuild dev с
REACT_APP_API_URL/REACT_APP_ROOT_PATH, dev-вход, /admin (headless Chromium), API и импорт (curl).

Closes #11

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 12:17:38 +04:00

296 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Гайд тестировщика: локальный стенд АРМ КА (Срезы 0–1)
Документ для ручного тестирования текущего `main`. Путь проверен 05.10.2026 на macOS (Docker 28, compose 2.34,
Node 24, uv 0.10): от пустого каталога до экрана администратора — около 15 минут, из них 10 — скачивание образов.
## 1. Что сейчас есть и чего нет
**Есть (Срез 0 + Срез 1):**
- Бэкенд `arm-core` (FastAPI, `/api/v1`): вход по email в dev-режиме, профиль пользователя, права как данные.
- API справочников: группы компаний (ГК), юрлица и ИП (ЮЛ) с историей, переводом между ГК и закрытием, алиасы
контрагентов, группы связанных компаний и заёмщиков (ГСК/ГСЗ) с составом, филиалы. Видимость по филиалу: головной
офис видит всё, филиал — только свои ГК.
- Импорт справочников из выгрузки ЕЦП CRM (xlsx/csv) с пробным прогоном и отчётом.
- Администрирование: пользователи, роли, матрица «роль × право», защита последнего администратора.
- Фронтенд: страница `/admin` («Пользователи и роли»). Другого UI нет.
- Журнал аудита в БД (`dkb.audit_event`).
**Нет (это не баги):**
- Экранов справочников ГК/ЮЛ/ГСК — проверяются только через Swagger (`/docs`) или curl.
- Загрузки файлов отчётности, парсера ОСВ, распознавания PDF, консолидации, КФО — следующие срезы.
- Настоящего входа через SSO банка. Вход — dev-форма по email без пароля.
## 2. Что установить и проверить из ПО
| Что | Версия | Проверка | Если нет |
|---|---|---|---|
| ОС | macOS или Linux. Windows — только через WSL2 (Ubuntu); нативно не проверялось | — | — |
| Git + SSH-ключ в Gitea | любая свежая | `ssh -T -p 2222 git@git.aepif.ru` отвечает приветствием | добавить публичный ключ в профиль Gitea `git.aepif.ru` |
| Docker + compose v2 | Docker ≥ 24, compose ≥ 2.20 (проверено 28.0 / 2.34) | `docker compose version` | Docker Desktop (macOS) или docker-ce + docker-compose-plugin (Linux) |
| `uv` | ≥ 0.4 (проверено 0.10) | `uv --version` | `curl -LsSf https://astral.sh/uv/install.sh \| sh`; Python 3.11 `uv` скачает сам |
| Node.js + npm | Node ≥ 20 LTS (проверено 24.7) | `node --version && npm --version` | nodejs.org или `nvm install --lts` |
| `make`, `curl` | любые | `make --version`, `curl --version` | macOS: Xcode Command Line Tools (`xcode-select --install`); Linux: `build-essential` |
| Свободные порты | 5432, 4222, 8222, 9000, 9001 (compose), 8000 (бэкенд), 3000 (фронт) | `lsof -i :5432` и т. д. пусто | закрыть локальный PostgreSQL/MinIO или сменить порты в `docs/dev/.env` и `arm-core/api/.env` |
| Ресурсы | Docker: ≥ 4 ГБ RAM, ≥ 3 ГБ диска под образы и тома | — | — |
| Доступ в интернет | нужен один раз: образы Docker Hub, PyPI, npm | — | Nexus банка не требуется |
Необязательно, но удобно: клиент PostgreSQL (DBeaver или `psql`) для просмотра аудита; Postman или аналог — Swagger
на `/docs` достаточно.
Браузер — любой современный (Chrome, Firefox, Safari, Edge).
## 3. Клонирование
Три репозитория должны лежать **рядом**, в одном каталоге, под именами `docs`, `arm-core`, `fixtures` (в коде пути
вида `../docs/...`):
```bash
mkdir arm-ka && cd arm-ka
git clone ssh://git@git.aepif.ru:2222/RSHB/arm-ka-docs.git docs
git clone ssh://git@git.aepif.ru:2222/RSHB/arm-core.git arm-core
git clone ssh://git@git.aepif.ru:2222/RSHB/arm-ka-fixtures.git fixtures
```
`fixtures` содержит реальные данные (внутренний доступ) — нужен для файла импорта. Если доступа нет, пункт 6.3
чек-листа можно выполнить на полной выгрузке из `docs/source/biq/`.
## 4. Запуск стенда
Все команды — из каталога `arm-core`, если не сказано иное.
### 4.1 Инфраструктура (PostgreSQL, NATS, MinIO)
```bash
cp ../docs/dev/.env.example ../docs/dev/.env
make up
docker compose -f ../docs/dev/docker-compose.yml up -d --wait postgres nats minio
docker compose -f ../docs/dev/docker-compose.yml ps
```
Ожидание: `postgres`, `nats`, `minio` — `Up (healthy)`; `minio-init` — `Exited (0)` (это нормально: он создаёт бакет и
завершается). Первый запуск скачивает образы, это самая долгая часть.
### 4.2 Бэкенд
```bash
make venv # api/.venv на Python 3.11, зависимости из PyPI
cp api/.env.example api/.env # dev-настройки: DEBUG=true, AUTH_DEV_ISSUER=true
make migrate # схема БД
make seed-dev # справочники + 7 dev-пользователей
cd api && .venv/bin/python run_server.py
```
Ожидание: в логе нет трейсбэков; `curl -s localhost:8000/healthcheck` отвечает `{"uptime": ..., "nats": "ok"}`.
Swagger: <http://localhost:8000/docs>. Сервер слушает только `127.0.0.1`, так и оставьте.
### 4.3 Фронтенд (второй терминал)
```bash
cd arm-core/web
npm install
REACT_APP_API_URL=http://localhost:8000 REACT_APP_ROOT_PATH=/ npm run start
```
Откроется <http://localhost:3000/> с формой «Вход для разработки». Две переменные окружения обязательны: без них
фронт ищет API на своём порту и путь приложения как на платформе банка.
Не используйте `npm run build` для тестирования: в production-сборке dev-формы входа нет, страница показывает
«Вход выполняется через РАИСА».
### 4.4 Вход
В форме введите email dev-пользователя и нажмите «Войти (dev)». Пароля нет. Токен живёт в текущей вкладке
(`sessionStorage`), новая вкладка — новый вход. Токен действует 8 часов.
| Email | Роль | Филиал | Что видит |
|---|---|---|---|
| `admin@armka.local` | ADMIN | Головной офис | всё; единственный с правом администрирования и импорта |
| `ka_go@armka.local` | KA_GO | Головной офис | все ГК и ЮЛ, может создавать и менять |
| `curator_go@armka.local` | CURATOR_GO | Головной офис | все ГК и ЮЛ, может создавать и менять |
| `expert@armka.local` | EXPERT | Головной офис | всё, только чтение |
| `ka_rf@armka.local` | KA_RF | Ростовский РФ | только ГК своего филиала, может создавать и менять |
| `rka@armka.local` | RKA | Ростовский РФ | только ГК своего филиала |
| `client_manager@armka.local` | CLIENT_MANAGER | Краснодарский РФ | только ГК своего филиала, чтение |
### 4.5 Токен для Swagger и curl
```bash
TOKEN=$(curl -s -X POST localhost:8000/api/v1/dev/token -H 'Content-Type: application/json' \
-d '{"email":"ka_go@armka.local"}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["access_token"])')
curl -s localhost:8000/api/v1/me -H "Authorization: Bearer $TOKEN"
```
В Swagger: кнопка **Authorize** → вставить токен (без слова Bearer) → все запросы пойдут от этого пользователя.
Чтобы проверить видимость разными ролями, получите второй токен другим email и переавторизуйтесь.
## 5. Как читать ответы
- Успех: 200/201, тело JSON. Удаление — 204 без тела.
- Нет токена или он плохой — 401. Нет права — 403 с текстом `недостаточно прав: требуется <КОД>`.
- Доменные ошибки — объект `{"detail": {"code": "...", "message": "..."}}`: 404 `NOT_FOUND` (в том числе для **чужой**
записи — так задумано, чужое неотличимо от несуществующего), 409 конфликты (`INN_EXISTS`, `LAST_ADMIN`, …),
422 валидация (`INVALID_INN`, `OFFICE_NOT_ALLOWED`, …).
- Ошибки валидации тела (не тот тип поля) — 422 со списком от pydantic.
- Списки — `{"items": [...], "total": N, "limit": 50, "offset": 0}`, `limit` ≤ 200.
Полный каталог кодов — `arm-core/README.md`, раздел «Ошибки API».
## 6. Чек-лист сценариев
Отмечайте: ✅ ожидание совпало / ❌ баг (с номером issue) / ⚠️ вопрос. Пустые поля ожиданий — проверьте сами и
зафиксируйте результат.
### 6.1 Администрирование (UI `/admin`, вход `admin@armka.local`)
| № | Шаги | Ожидание |
|---|---|---|
| A1 | Открыть «Система → Пользователи и роли» | таблица из 7 пользователей, вкладки «Пользователи» и «Матрица прав» |
| A2 | «Создать пользователя»: email `qa1@example.com`, ФИО, филиал, роль KA_RF | пользователь появился; войти им в новой вкладке — получится, роли KA_RF |
| A3 | Создать пользователя с email `qa1@EXAMPLE.com` | ошибка 409 `EMAIL_EXISTS` уведомлением |
| A4 | Создать пользователя с email `x@corp.local` | ошибка 422 (домен `.local` отклоняется — известное ограничение) |
| A5 | У `qa1@example.com` сменить роли на KA_GO + EXPERT | сохранилось; `/me` этого пользователя (новая вкладка) показывает обе роли |
| A6 | Снять роль ADMIN у `admin@armka.local` (у себя), пока он единственный админ | 409 `LAST_ADMIN`, роли не изменились |
| A7 | Выдать роль ADMIN пользователю `qa1@example.com`, затем снять ADMIN у себя | теперь разрешено |
| A8 | Деактивировать единственного активного админа | 409 `LAST_ADMIN` |
| A9 | Матрица прав: роли EXPERT добавить `EXPORT`, сохранить | `/me` эксперта содержит `EXPORT` |
| A10 | Матрица прав: снять `USERS_ADMIN` у ADMIN, когда других админов нет | 409 `LAST_ADMIN` |
| A11 | Войти `expert@armka.local`, открыть `/admin` напрямую | «Нет доступа», пункт меню скрыт, запросов к `/admin/*` нет (вкладка Network) |
| A12 | После правки своих ролей обновить страницу | профиль в шапке обновился только после перезагрузки (известное ограничение) |
### 6.2 Справочники (Swagger `/docs`)
| № | Пользователь | Шаги | Ожидание |
|---|---|---|---|
| D1 | ka_go | `POST /directories/company-groups` `{"name":"ГК Тест ГО"}` | 201, `code` вида `GK-000001`, `base_regional_office_id` пустой |
| D2 | ka_rf | `POST /directories/company-groups` `{"name":"ГК Тест РФ"}` без филиала | 201, филиал подставлен Ростовский |
| D3 | ka_rf | то же с `base_regional_office_id` Краснодарского РФ (id из `GET /directories/regional-offices`) | 422 `OFFICE_NOT_ALLOWED` |
| D4 | ka_rf | `GET /directories/company-groups` | только «ГК Тест РФ», `total` = 1 (ГК ГО не видна) |
| D5 | ka_rf | `GET /directories/company-groups/{id ГК Тест ГО}` | 404, тело такое же, как для случайного uuid |
| D6 | ka_go | `GET /directories/company-groups` | обе ГК |
| D7 | ka_go | `PATCH` ГК: сменить имя | 200; `GET .../history` — одна запись с прежним именем и `valid_to` |
| D8 | ka_go | `POST /directories/legal-entities` ИП с ИНН из 10 цифр | 422 `INVALID_INN` |
| D9 | ka_go | ЮЛ `{"inn":"7700000001","entity_type":"ЮЛ","name":"ООО Альфа","company_group_id":<ГК ГО>}` | 201 |
| D10 | ka_go | повторно тот же ИНН | 409 `INN_EXISTS` |
| D11 | ka_go | ЮЛ без `company_group_id` | 201, в списке `outside_group = true` |
| D12 | ka_go | `POST .../legal-entities/{id}/aliases` `{"alias":"ООО «Альфа»"}` | 201, `normalized_alias` = `альфа` |
| D13 | ka_go | тот же алиас другому ЮЛ | 409 `ALIAS_TAKEN` с `legal_entity_id` владельца |
| D14 | ka_go | `POST /directories/company-groups/{ГК}/related-groups` kind `gsk`, затем `gsz` | 201 оба, коды `GSK-…`, `GSZ-…` |
| D15 | ka_go | добавить ЮЛ «Альфа» в ГСК и в ГСЗ той же ГК | оба 201 |
| D16 | ka_go | создать вторую ГСК в той же ГК и добавить туда «Альфа» | 409 `MEMBER_OF_OTHER_GROUP` |
| D17 | ka_go | добавить в ГСК ЮЛ из другой ГК | 422 `DIFFERENT_COMPANY_GROUP` |
| D18 | ka_go | `POST .../legal-entities/{Альфа}/move` в другую ГК, `effective_date` сегодня | 200; членства закрыты (`GET .../legal-entities/{id}` — `memberships` пуст); история `MOVE_TO_GROUP` |
| D19 | ka_go | `move` с `effective_date` вчера | 422 |
| D20 | ka_go | `POST .../withdraw` `{"event":"CLOSE","effective_date":сегодня}` | `close_date` заполнен, `company_group_id` пуст |
| D21 | admin | `DELETE /directories/related-groups/{id непустой}` | 409 `GROUP_NOT_EMPTY`; пустой — 204 |
| D22 | ka_go | `DELETE /directories/related-groups/{id пустой}` | 403 (право `DIRECTORY_DELETE` только у ADMIN — так задумано) |
| D23 | expert | любой `POST` в справочники | 403 |
| D24 | любой | `GET /directories/company-groups?limit=201` | 422 |
### 6.3 Импорт из ЕЦП CRM (пользователь `admin`, curl или Swagger)
Файл: `fixtures/directories/ecp-export-russkiy-biskvit.xlsx` (21 строка). Выполняйте на свежем стенде или после
сброса (раздел 8), иначе счётчики будут другими.
| № | Шаги | Ожидание |
|---|---|---|
| I1 | `POST /directories/import` с `dry_run=true` | 200; `rows` 21, `groups_created` 1, `offices_created` 1, `entities_created` 20, `memberships_created` 2, `errors` 1 (строка типа ФЛ → `UNSUPPORTED_TYPE`); в БД и справочниках ничего не появилось |
| I2 | тот же файл `dry_run=false` | 200, тот же отчёт; появились ГК «Русский Бисквит», филиал «Санкт-Петербургский РФ», 20 ЮЛ/ИП, одна ГСК с двумя членами |
| I3 | ещё раз `dry_run=false`, `on_existing=skip` | все строки `LE_EXISTS`/`GK_EXISTS`, число записей не изменилось |
| I4 | csv без колонки «Тип» | 422 `MISSING_COLUMNS`, в ответе список недостающих колонок |
| I5 | файл в кодировке cp1251 (сохранить csv из Excel «CSV (разделители — запятые)») | 422 `UNSUPPORTED_FILE` |
| I6 | пользователем `ka_go` | 403 |
| I7 | полная выгрузка `docs/source/biq/ecp-export-lagal-entity.xlsx`, `dry_run=true` | 200 за несколько секунд; `rows` 8400, `offices_created` 53; есть коды `UNSUPPORTED_TYPE`, `INVALID_INN`, `DUPLICATE_IN_FILE` |
```bash
ADMIN=$(curl -s -X POST localhost:8000/api/v1/dev/token -H 'Content-Type: application/json' \
-d '{"email":"admin@armka.local"}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["access_token"])')
curl -s -X POST localhost:8000/api/v1/directories/import -H "Authorization: Bearer $ADMIN" \
-F file=@../fixtures/directories/ecp-export-russkiy-biskvit.xlsx -F dry_run=true | python3 -m json.tool | head -40
```
### 6.4 Аудит (БД)
```bash
docker compose -f ../docs/dev/docker-compose.yml exec postgres \
psql -U armka -d armka -c "select occurred_at, action, section, object_type, changed_field from dkb.audit_event order by occurred_at desc limit 20"
```
| № | После чего | Ожидание |
|---|---|---|
| L1 | A2, A5, A9 | записи `USER_CHANGE`, `USER_ROLE_CHANGE`, `ROLE_PERMISSION_CHANGE` с `before_value`/`after_value` |
| L2 | D1, D7 | `GROUP_CHANGE`; смена филиала ГК — `ASSIGNMENT_CHANGE` |
| L3 | D9, D12, D18 | `LEGAL_ENTITY_CHANGE`, членства — `MEMBERSHIP_CHANGE` |
| L4 | I2 | одна запись `DIRECTORY_IMPORT` с отчётом в `after_value`; после I1 (`dry_run`) записи нет |
| L5 | A6, D10 (отказы) | записей аудита нет |
### 6.5 Негатив и границы
| № | Шаги | Ожидание |
|---|---|---|
| N1 | запрос без заголовка `Authorization` | 401 |
| N2 | токен для несуществующего email (`nobody@armka.local`) через `POST /dev/token` | 404 `пользователь не найден или отключён`, токен не выдан |
| N3 | тело `PATCH` ГК с неизвестным полем | 422 (поля вне схемы запрещены) |
| N4 | импорт файла больше 20 МБ | 413 |
## 7. Известные ограничения (не заводить как баги)
- UI только для администрирования; справочники — через API.
- Вход без пароля, открытый `/docs`, CORS `*` — dev-режим.
- Email `*.local` через API/UI отклоняется, при этом dev-пользователи именно такие (созданы seed напрямую).
- Удалять алиасы и пустые ГСК/ГСЗ может только ADMIN (право `DIRECTORY_DELETE`).
- Граница «дата перевода не раньше сегодня» считается по UTC: до 03:00 МСК «сегодня» ещё вчера. Будет исправлено.
- Повторный `make seed-dev` возвращает права, снятые администратором в матрице.
- Профиль в шапке не перечитывается после правки собственных ролей.
- В консоли браузера сообщение `Store does not have a valid reducer` — каркас, не влияет.
- Таблица пользователей показывает первые 200.
- При импорте ИНН, записанный в Excel числом с ведущим нулём, теряет ноль и даёт `INVALID_INN`.
Полный список отложенных находок — Gitea `RSHB/arm-core`, issue «Follow-ups from #5».
## 8. Сброс стенда
```bash
docker compose -f ../docs/dev/docker-compose.yml down -v # БД и бакет с нуля
make up && docker compose -f ../docs/dev/docker-compose.yml up -d --wait postgres nats minio
make migrate && make seed-dev
```
Бэкенд и фронт перезапускать не нужно, но сессии в браузере лучше начать заново.
## 9. Как заводить баги
Gitea, репозиторий `RSHB/arm-core` (для документации — `RSHB/arm-ka-docs`). Один дефект — одна issue.
Шаблон:
```
Срез: 1 / Сценарий: D16 (номер из гайда, если есть)
Пользователь: ka_go@armka.local (роль KA_GO)
Шаги: 1. … 2. …
Ожидание: 409 MEMBER_OF_OTHER_GROUP
Факт: 500 (тело ответа ниже)
Запрос: метод, путь, тело (без токена)
Ответ: статус и тело
Лог бэкенда: строки с трейсбэком, если есть
Окружение: ОС, версии docker/node, коммит arm-core (`git rev-parse --short HEAD`)
```
Вопросы по ожидаемому поведению (не ясно, баг ли) — тоже issue, с пометкой «вопрос» в заголовке.
## 10. Типичные проблемы при запуске
| Симптом | Причина и решение |
|---|---|
| `make up` → порт занят | локальный PostgreSQL/MinIO; остановить или сменить `*_PORT` в `docs/dev/.env` и те же порты в `arm-core/api/.env` |
| `docker compose up --wait` без списка сервисов возвращает 1 | нормально из-за `minio-init`; используйте команду со списком из 4.1 |
| `make migrate` → connection refused | PostgreSQL ещё не `healthy`; подождать и повторить |
| `make venv` не находит Python 3.11 | `uv` скачает его сам при наличии интернета; проверьте `uv python list` |
| Фронт открылся, но после входа пусто или ошибки сети | забыты `REACT_APP_API_URL`/`REACT_APP_ROOT_PATH` при `npm run start` |
| Страница «Вход выполняется через РАИСА» | это production-сборка; запускайте `npm run start`, а не `build` |
| Импорт падает на записи файла в S3 | MinIO не поднят или не создан бакет: `docker compose logs minio-init` |
| `/healthcheck` показывает `nats: down` | NATS не поднят; на тестирование Среза 1 не влияет |
Подробнее об инфраструктуре — `docs/dev/README.md`, о бэкенде — `arm-core/README.md`.