From 0600c7c5a1551abe61ace2879d21c936137b97ec Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Mon, 5 Oct 2026 12:17:38 +0400 Subject: [PATCH] =?UTF-8?q?dev/qa-guide.md:=20=D0=B3=D0=B0=D0=B9=D0=B4=20?= =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D1=89=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20=E2=80=94=20=D0=9F=D0=9E,=20=D0=B7=D0=B0=D0=BF?= =?UTF-8?q?=D1=83=D1=81=D0=BA=20=D1=81=D1=82=D0=B5=D0=BD=D0=B4=D0=B0=20?= =?UTF-8?q?=D0=A1=D1=80=D0=B5=D0=B7=D0=BE=D0=B2=200=E2=80=931,=20=D1=87?= =?UTF-8?q?=D0=B5=D0=BA-=D0=BB=D0=B8=D1=81=D1=82=20=D1=81=D1=86=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=D1=80=D0=B8=D0=B5=D0=B2,=20=D0=BE=D0=B3=D1=80?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=87=D0=B5=D0=BD=D0=B8=D1=8F,=20=D0=B1?= =?UTF-8?q?=D0=B0=D0=B3=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Путь проверен 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 Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2 --- dev/README.md | 2 + dev/qa-guide.md | 295 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 297 insertions(+) create mode 100644 dev/qa-guide.md diff --git a/dev/README.md b/dev/README.md index e46fffb..5211e3f 100644 --- a/dev/README.md +++ b/dev/README.md @@ -5,6 +5,8 @@ `tabular-parser`, `scan-gateway` и эмулятор ИС Сканирование `scan-emulator` (см. «Профиль `services`»). Без профиля поднимается только инфраструктура, а сервисы можно запускать из venv (см. «Запуск сервисов из venv»). +Для ручного тестирования стенда (установка ПО, запуск, чек-лист сценариев по ролям) — [`qa-guide.md`](qa-guide.md). + ## Предварительные требования - Docker и compose v2 (проверено на docker 28 / compose 2.34). diff --git a/dev/qa-guide.md b/dev/qa-guide.md new file mode 100644 index 0000000..69adf97 --- /dev/null +++ b/dev/qa-guide.md @@ -0,0 +1,295 @@ +# Гайд тестировщика: локальный стенд АРМ КА (Срезы 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: . Сервер слушает только `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 +``` + +Откроется с формой «Вход для разработки». Две переменные окружения обязательны: без них +фронт ищет 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`.