food-market/tests/e2e/lib/api.ts
nns 7bb941259a
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 1m20s
CI / Web (React + Vite) (push) Successful in 42s
feat(e2e): infrastructure + first full-cycle scenario + baseline report
Декларативные end-to-end сценарии в tests/e2e/. YAML описывает шаги,
TypeScript-handler — конкретные API/UI/DB-проверки. Отчёт в Markdown.

Структура:
- runner.ts        : entry, парсит YAML, прогоняет steps, пишет report
- run.sh           : pnpm install + tsx
- lib/api.ts       : axios + login() (через /connect/token + /api/me)
- lib/db.ts        : docker exec psql, resetTenantData(), countRows()
- lib/report.ts    : Markdown-аккумулятор (steps + bugs + ux + gap + perf)
- scenarios/full-cycle.yml       : 12 шагов
- scenarios/full-cycle.steps.ts  : handlers (один на шаг)
- README.md        : как добавить новый сценарий

reset_db в preconditions:
- TRUNCATE tenant-таблиц CASCADE
- AspNet*/users — оставляем только admin@food-market.local
- OpenIddict tokens — все valid → revoked
- Реестр products + системные справочники + миграции + platform_settings — НЕ трогаем

Запуск: tests/e2e/run.sh full-cycle [--api-only]

Первый прогон (--api-only, baseline в reports/full-cycle-2026-05-07-baseline.md):
- 8 ✓ / 1 ✗ / 3 ◯ из 12.
- Critical bug: Cashier видит /api/organization/employees через API
  (нет [Authorize(Roles="Admin")] на List endpoint).
- High: при CreateOrg через SuperAdmin не сидируются tenant-units —
  пустой каталог измерений у новой org (DevDataSeeder.SeedTenantReferencesAsync
  должен вызываться, но не вызывается).
- Logic gaps: реестр products tenant-scoped и новая org стартует с
  пустым каталогом; SuperAdmin /organizations не валидирует ФЛК
  телефона; Cashier не получает Identity-роль "Cashier" при создании
  через /employees.

UI-шаги (Playwright) в этом коммите не покрыты — runner работает в
--api-only режиме. UI-extension добавим следующим коммитом, не блокирует
получение полезного отчёта.
2026-05-08 00:05:52 +05:00

52 lines
1.6 KiB
TypeScript

import axios, { AxiosInstance } from 'axios'
import { Agent as HttpsAgent } from 'node:https'
export const ADMIN_BASE = process.env.E2E_ADMIN_URL ?? 'https://admin.food-market.kz'
const httpsAgent = new HttpsAgent({ rejectUnauthorized: false })
export interface AuthSession {
accessToken: string
refreshToken?: string
email: string
roles: string[]
orgId: string | null
}
export function makeClient(token?: string): AxiosInstance {
return axios.create({
baseURL: ADMIN_BASE,
httpsAgent,
headers: token ? { Authorization: `Bearer ${token}` } : {},
validateStatus: () => true, // не кидать на 4xx — runner сам решает
})
}
export async function login(email: string, password: string): Promise<AuthSession> {
const body = new URLSearchParams({
grant_type: 'password',
username: email,
password,
client_id: 'food-market-web',
scope: 'openid profile email roles api offline_access',
})
const res = await makeClient().post('/connect/token', body, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
})
if (res.status !== 200) {
throw new Error(`login failed for ${email}: ${res.status} ${JSON.stringify(res.data)}`)
}
const tok = res.data
const me = await makeClient(tok.access_token).get('/api/me')
if (me.status !== 200) {
throw new Error(`/api/me after login failed: ${me.status} ${JSON.stringify(me.data)}`)
}
return {
accessToken: tok.access_token,
refreshToken: tok.refresh_token,
email: me.data.email,
roles: me.data.roles ?? [],
orgId: me.data.orgId ?? null,
}
}