krona-staff/docs/RUN-MANUAL-TESTING.md

156 lines
19 KiB
Markdown
Raw Permalink 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.

# Руководство по развёртыванию и чек-лист боевого тестирования Krona Staff
## 1. Назначение и архитектурные инварианты
Krona Staff — автономно развёртываемый блок Krona Business Platform, отвечающий за учёт рабочего времени, терминалы посещаемости и управленческий расчёт зарплаты.
### Ключевые инварианты (AGENTS.md)
1. **Разделение доменов**: Core владеет Tenant, User, Role, SSO, Platform Settings, Files, Notifications. Staff владеет Employee и HR-доменом (`User != Employee`).
2. **Изоляция арендаторов**: Все таблицы содержат `tenant_id` (ULID 26 символов). В PostgreSQL включен и принудительно активирован Row-Level Security (`FORCE ROW LEVEL SECURITY`). Запросы выполняются через tenant-scoped транзакции с выставлением `app.tenant_id`.
3. **Разделение ролей БД**:
- `krona_migrator` — владелец схемы и таблиц, выполняет миграции DDL, `NOSUPERUSER NOBYPASSRLS`.
- `krona_staff_app` — runtime-пользователь приложения с минимальными DML правами (SELECT, INSERT, UPDATE, DELETE на прикладные таблицы; нет прав на `schema_migrations`, нет прав DDL).
4. **Типы данных**:
- Идентификаторы — ULID (`VARCHAR(26)`).
- Денежные суммы — `BIGINT` в минимальных единицах (копейках/minor units) + ISO 4217 код валюты (например, `'RUB'`).
- Временные метки — UTC (`TIMESTAMPTZ`).
5. **Надёжность**: Транзакционный Outbox, идемпотентность по `Idempotency-Key`, CSRF-защита сессионных cookie (`Origin`/`Sec-Fetch-Site`), Rate-limiting ключей входа.
---
## 2. Развёртывание и запуск
### 2.1. Системные требования
- **ОС**: Linux (Ubuntu 22.04+/Debian 12+) или Windows Server 2022+ / Windows 11 (для сборки терминала).
- **Среда выполнения**: Node.js 22 LTS, npm 10+.
- **База данных**: PostgreSQL 17 (поддерживается 17.0+).
- **Сеть**: Порт 8080 (HTTP) или 8453 (HTTPS пилот).
### 2.2. Запуск через Docker Compose (рекомендуемый пилот)
```bash
# 1. Перейдите в каталог deploy/pilot
cd deploy/pilot
# 2. Задайте ULID организации из Krona Business Platform
export STANDALONE_TENANT_ID="01J0000000000000000000000A"
# 3. Выполните скрипт первичной инициализации:
# - Сгенерирует стойкие пароли БД, токены менеджера и терминала в secrets/
# - Сгенерирует ключи Web Push (VAPID)
# - Создаст самоподписанный TLS-сертификат для терминала
./setup.sh
# 4. Запустите стек (PostgreSQL + мигратор + app)
docker compose up -d --build
# 5. Проверьте запуск контейнеров и миграций
docker compose ps
docker compose logs -f app
```
### 2.3. Запуск без Docker (Bare-Metal / Dev стенд)
1. **Создайте базу данных и роли в PostgreSQL**:
```sql
-- Под пользователем postgres (DBA):
CREATE ROLE krona_migrator LOGIN PASSWORD 'migrator_secure_password' NOSUPERUSER NOCREATEDB NOCREATEROLE NOBYPASSRLS;
CREATE ROLE krona_staff_app LOGIN PASSWORD 'app_secure_password' NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS;
CREATE DATABASE krona_staff OWNER krona_migrator ENCODING 'UTF8';
\c krona_staff
ALTER SCHEMA public OWNER TO krona_migrator;
REVOKE CREATE ON SCHEMA public FROM PUBLIC;
REVOKE ALL ON DATABASE krona_staff FROM PUBLIC;
GRANT CONNECT ON DATABASE krona_staff TO krona_staff_app;
```
2. **Настройте переменные окружения (`.env`)**:
```bash
cp .env.example .env
```
Укажите в `.env`:
```ini
PORT=8080
NODE_ENV=production
STANDALONE_TENANT_ID=01J0000000000000000000000A
STANDALONE_ACTOR_ID=01J00000000000000000000001
STANDALONE_MANAGER_TOKEN=your_secure_manager_token_hex_or_ulid
STANDALONE_TERMINAL_TOKEN=your_secure_terminal_token_hex_or_ulid
DATABASE_URL=postgres://krona_staff_app:app_secure_password@127.0.0.1:5432/krona_staff
DATABASE_MIGRATION_URL=postgres://krona_migrator:migrator_secure_password@127.0.0.1:5432/krona_staff
```
3. **Сгенерируйте VAPID ключи**:
```bash
node scripts/generate-vapid-keys.mjs >> .env
```
4. **Примените миграции**:
```bash
npm run db:migrate
```
5. **Соберите и запустите приложение**:
```bash
npm run build
npm run start
```
---
## 3. Чек-лист ручного боевого тестирования владельцем
Каждый шаг должен быть выполнен последовательно. Ожидаемый результат сверяется с фактическим.
| № | Шаг / Сценарий | Действие / HTTP-запрос | Ожидаемый результат | Статус |
| ------ | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | :----: |
| **1** | **Системные проверки (Health)** | `GET /api/v1/staff/live`<br>`GET /api/v1/staff/ready`<br>`GET /api/v1/staff/version` | HTTP 200 на всех эндпоинтах. `ready` подтверждает соединение с БД под ролью `krona_staff_app`. `version` возвращает `runtime: standalone` и semver. | [ ] |
| **2** | **Контракт OpenAPI** | `GET /api/v1/staff/openapi.json` | HTTP 200, возвращается валидная спецификация OpenAPI 3.1 со всеми роутами модуля. | [ ] |
| **3** | **Защита доступа (401)** | `GET /api/v1/staff/portal` без заголовка Authorization | HTTP 401 Unauthorized. Ошибки с деталями SQL отсутствуют. | [ ] |
| **4** | **Защита от CSRF** | `POST /api/v1/staff/portal/employees` с cookie сессии, но без заголовка `Origin` или с чужим Origin | HTTP 403 Forbidden (CSRF token missing/invalid). | [ ] |
| **5** | **Защита от перебора паролей (429)** | 35 последовательных запросов с неверным токеном/ключом | После исчерпания лимита возвращается HTTP 429 Too Many Requests с заголовком `Retry-After`. | [ ] |
| **6** | **Создание сотрудника** | `POST /api/v1/staff/employees`<br>Headers: `Authorization: Bearer <MANAGER_TOKEN>`<br>Body: `{"personnel_number": "EMP-001", "display_name": "Иванов Иван", "profession_code": "cook"}` | HTTP 201 Created. Создан сотрудник, присвоен ULID, запись зафиксирована в `staff_employees`. | [ ] |
| **7** | **Проверка справочника профессий** | `GET /api/v1/staff/professions` | HTTP 200, список активных профессий (`cook`, `cashier`, `barista`, `waiter` и др.). | [ ] |
| **8** | **Назначение тарифной ставки сотруднику** | `POST /api/v1/staff/payroll/assignments`<br>Body: привязка правила (почасовая ставка 350 руб/час = 35000 minor units). | HTTP 201 Created. Версионированное правило сохранено в `staff_pay_assignments`. | [ ] |
| **9** | **Отметка прихода через терминал** | `POST /api/v1/staff/attendance/events`<br>Headers: `Authorization: Bearer <TERMINAL_TOKEN>`<br>Body: `{"event_id": "<ULID>", "employee_id": "<EMP_ID>", "event_type": "check_in", "occurred_at": "<NOW_ISO>"}` | HTTP 201 Created. Событие записано в `staff_attendance_events`. Отправлено событие в outbox. | [ ] |
| **10** | **Идемпотентность отметки** | Повтор запроса шага 9 с тем же `event_id` и `source_event_id` | HTTP 200 OK (или 201), дубликат не создаётся, количество записей не удваивается. | [ ] |
| **11** | **Отметка ухода через терминал** | `POST /api/v1/staff/attendance/events` с `event_type: "check_out"` спустя 8 часов | HTTP 201 Created. Формируется завершённая рабочая смена. | [ ] |
| **12** | **Подтверждение смены менеджером** | `POST /api/v1/staff/work-sessions/:id/confirm` | HTTP 200 OK. Смена переходит в статус `confirmed`, часы готовы к начислению. | [ ] |
| **13** | **Авторасчёт ведомости зарплаты** | `POST /api/v1/staff/payroll/runs/auto-calc`<br>Body: период с 1 по 15 число месяца. | HTTP 201 Created. Начислено: 8 ч × 350 руб = 2800 руб (280000 minor units). Сохранена расшифровка формулы расчёта. | [ ] |
| **14** | **Согласование ведомости бухгалтером** | `POST /api/v1/staff/payroll/batches/:id/approve` | HTTP 200 OK. Статус пакета переходит в `approved`. | [ ] |
| **15** | **Подписание ведомости директором** | `POST /api/v1/staff/payroll/batches/:id/sign`<br>Body: `{"signature": "director_sign_key", "note": "Выплата за первую половину месяца"}` | HTTP 200 OK. Пакет подписан. Созданы строки начислений в `staff_payroll_ledger` с полем `note` (без ошибки 500!). | [ ] |
| **16** | **Проверка реестра выплат (Ledger)** | `GET /api/v1/staff/payroll/ledger` | HTTP 200 OK. Отображаются начисления и выплаты, сальдо корректно сходится. | [ ] |
| **17** | **Web Push уведомления сотруднику** | `POST /api/v1/staff/portal/push-subscriptions` с валидным FCM/W3C endpoint. | HTTP 201 Created. Подписка сохранена. При наступлении события формируется delivery без ложных блокировок хоста. | [ ] |
| **18** | **Уведомления директору без дублей** | `GET /api/v1/staff/notifications/unread` под учётной записью директора | HTTP 200 OK. Каждому директору отдаются ровно его уведомления без дублирования на каждого получателя. | [ ] |
| **19** | **Офлайн-синхронизация: регистрация терминала** | `POST /api/v1/staff/sync/devices/register` с токеном менеджера | HTTP 201 Created. Возвращается токен устройства `kstaff_dev_...`. Токен сохраняется в терминале. | [ ] |
| **20** | **Офлайн-синхронизация: начальный Pull (C0)** | `GET /api/v1/staff/sync/pull` с токеном устройства (без курсора) | HTTP 200 OK. Возвращаются справочники (сотрудники, оргструктура, тарифы) и курсор `cursor_ulid`. | [ ] |
| **21** | **Офлайн-синхронизация: пакет команд Push** | `POST /api/v1/staff/sync/push` с массивом офлайн-отметок посещаемости | HTTP 200 OK. Все корректные команды применены, битые команды получают понятную ошибку, статус транзакции не ломает весь пакет. | [ ] |
| **22** | **Отзыв прав уволенного сотрудника** | Перевод сотрудника в статус `fired` / деактивация доступа в `staff_access_grants` | Попытка входа уволенного сотрудника немедленно возвращает HTTP 401 Unauthorized. | [ ] |
| **23** | **Проверка утилиты миграции арендатора (Rehome)** | `node scripts/rehome-tenant.mjs --dry-run --source <OLD_ULID> --target <NEW_ULID>` | Утилита производит корректное сравнение количества строк по всем таблицам (`Number`) без сбоев типов и ошибок внешних ключей. | [ ] |
---
## 4. Диагностика и устранение неполадок
1. **Ошибка `401 Unauthorized` при запросе от терминала**:
- Проверьте, передан ли заголовок `Authorization: Bearer <STANDALONE_TERMINAL_TOKEN>` или токен зарегистрированного устройства.
2. **Ошибка `403 Forbidden` при запросах из веб-интерфейса**:
- Убедитесь, что запрос отправляется с доверенного Origin (`localhost`, `127.0.0.1` или IP пилота). Сессионные мутирующие запросы требуют валидный Origin.
3. **Проверка логов транзакций и RLS**:
```bash
# Просмотр логов контейнера
docker compose logs -f app
```
4. **Проверка состояния миграций БД**:
```bash
node scripts/check-migrations.mjs
```