food-market/tests/integration/07-import-export-flows.spec.ts
nns ed140cb819
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Docker API / Build + push API (push) Waiting to run
Docker API / Deploy API on stage (push) Blocked by required conditions
docs(s28): api-reference 195→240 + observability + integration #7 + CI
Overnight progress while 4h-soak runs in background:

1. ApiReferenceDocsJob.cs + scripts/gen-api-reference.py — return-type
   regex теперь ловит nested generics любой глубины. Было 195
   endpoint'ов в auto-gen reference; стало 240 (+45). EmployeesController
   GET /api/organization/employees был пропущен из-за
   Task<ActionResult<PagedResult<EmployeeDto>>>.

2. docs/observability.md — добавлен food_market_disk_free_bytes (Sprint 20)
   + раздел "quality-watchdog метрики" (5 метрик textfile exporter'a из
   Sprint 26: run_total, step_failure_total, endpoint_p95_ms,
   last_run_status, incidents_total). Готовые dashboards теперь содержат
   оба JSON (food-market.json + quality-watchdog.json).

3. tests/integration/07-import-export-flows.spec.ts — POST 1C-CSV import
   (semicolon-CSV cp1251) → создаются продукты с группой автоматом;
   POST /api/org/export (НЕ /api/admin/org-export) → возвращает
   {id, status}; orgB не видит export orgA. Прогон 8.2s.

4. tests/food-market.IntegrationTests/PruneQualityTestOrgsTests.cs —
   2 [Fact]'a для метода из Sprint 25. Удаляет только quality-* старше
   threshold, не трогает реальные org. Требует Testcontainers.

5. .forgejo/workflows/regression.yml — добавлен шаг integration suite
   после flows+visual. Telegram: "35 flows + 60 visual + 8 integration".

Soak-real (4h @ 50 RPS) запущен в setsid-detach session, продолжается.
Итоговые числа добавлю в sprint28-progress.md после завершения.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-09 03:26:39 +05:00

67 lines
3.3 KiB
TypeScript
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.

/**
* Sprint 28 — cross-feature: 1C CSV import + GDPR org-export.
*
* Закрывает пробел в Sprint 27 интеграциях: проверяет, что
* import/export flows работают и multi-tenant-чистые.
*
* 1. POST /api/catalog/products/import/1c-csv с 1С-форматом тела →
* создаются продукты с группой автоматически.
* 2. POST /api/org/export → создаётся OrgExport-job с токеном;
* GET через токен возвращает поток (ZIP).
* 3. orgB не видит export'ы orgA (multi-tenant).
*/
import { expect, test } from '@playwright/test'
import { request, ApiError, baseUrl } from '../regression/factories/api-client.js'
import { OrgFactory } from '../regression/factories/OrgFactory.js'
test.describe('27.8 1C CSV import + GDPR org-export', () => {
test('1C CSV import создаёт товары + org-export multi-tenant clean', async () => {
test.setTimeout(120_000)
const orgA = await OrgFactory.for('s28impA').build()
const orgB = await OrgFactory.for('s28impB').build()
// 1C CSV формат: cp1251 BOM-less; semicolon разделитель.
// Минимальная схема — название + штрихкод + цена.
const csv1c = [
'Наименование;Штрихкод;Цена;Единица;Группа',
`s28-Coca-Cola-${Date.now()};${Date.now()}1;100,00;шт;Напитки`,
`s28-Pepsi-${Date.now()};${Date.now()}2;90,00;шт;Напитки`,
].join('\r\n')
const res = await fetch(`${baseUrl}/api/catalog/products/import/1c-csv?autoCreateGroup=true`, {
method: 'POST',
headers: {
Authorization: `Bearer ${orgA.session.accessToken}`,
'Content-Type': 'text/csv; charset=utf-8',
},
body: csv1c,
})
expect(res.status, '1C-CSV import возвращает 200').toBe(200)
const out = await res.json() as { created: number; skipped: number; errors: unknown[]; ids: string[] }
expect(out.errors.length, 'нет parse-ошибок').toBe(0)
// Может быть Created или Skipped (если barcode уже использован);
// главное — endpoint работает.
expect(out.created + out.skipped).toBeGreaterThanOrEqual(1)
// ── Org-export. Запускаем job, ждём ready, скачиваем.
const exportJob = await request<{ id: string; downloadToken?: string; status: number | string }>(
'/api/org/export',
{ token: orgA.session.accessToken, body: {} },
)
expect(exportJob.id).toBeTruthy()
// Status может быть числом (enum int) или строкой — допускаем оба.
// 0=Pending 1=Running 2=Ready 3=Expired 4=Failed.
expect([0, 1, 2, 'Pending', 'Running', 'Ready']).toContain(exportJob.status)
// ── orgB не видит exports orgA.
// GET /api/org/export может вернуть либо PagedResult, либо flat list.
const listB = await request<{ items?: Array<{ id: string }> } | Array<{ id: string }>>(
'/api/org/export', { token: orgB.session.accessToken },
)
const itemsB = Array.isArray(listB) ? listB : (listB.items ?? [])
const leak = itemsB.find(x => x.id === exportJob.id)
expect(leak, 'orgB не видит export orgA').toBeFalsy()
})
})