metallkart-erp/tests/backend/e2e-workflow.ts
louis b27edcf2d5 feat(e2e): set up isolated Playwright infra + 10 smoke tests (PLAYWRIGHT-P0)
- DB erp_test isolée (ports 3002/5174, erp_local intouché)
- seed-test.ts déterministe: 3 users, 2 clients, 2 suppliers, 2 orders, 59 help articles
- globalSetup truncate+reseed, webServer dual backend+frontend
- 10 smoke tests: login, RBAC, orders, documents, help drawer, Ctrl+/
- Réorganisation tests/: e2e/ (Playwright) vs backend/ (vitest)
- cross-env + dotenv pour portabilité Windows/Linux
- vite.config.ts proxy dynamique via BACKEND_PORT env var

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-11 18:26:33 +03:00

218 lines
7.0 KiB
TypeScript

/**
* E2E Workflow Test — MetallKart ERP
* Tests the complete flow: Auth → Orders → Estimates → PRs → Tenders → POs
*
* Usage: npx tsx tests/e2e-workflow.ts
* Requires: backend running on localhost:3001
*/
const BASE = 'http://localhost:3001/api/v1';
interface TestContext {
token: string;
orderId?: number;
smetaId?: number;
prIds?: number[];
tenderId?: number;
}
const ctx: TestContext = { token: '' };
let passed = 0;
let failed = 0;
async function api(method: string, path: string, body?: any): Promise<any> {
const headers: Record<string, string> = {};
if (ctx.token) headers['Authorization'] = `Bearer ${ctx.token}`;
if (body) headers['Content-Type'] = 'application/json';
const res = await fetch(`${BASE}${path}`, {
method,
headers,
body: body ? JSON.stringify(body) : undefined,
});
const data = await res.json().catch(() => null);
if (!res.ok) {
throw new Error(`${method} ${path}${res.status}: ${JSON.stringify(data)}`);
}
return data;
}
async function test(name: string, fn: () => Promise<void>) {
try {
await fn();
console.log(`${name}`);
passed++;
} catch (err: any) {
console.log(`${name}: ${err.message}`);
failed++;
}
}
async function run() {
console.log('\n🔧 MetallKart ERP — E2E Workflow Test\n');
// === AUTH ===
console.log('📋 Authentication');
await test('Login as ADMIN', async () => {
const data = await api('POST', '/auth/login', {
email: 'louis@metallcart.ru',
password: 'MetallKart2026!',
});
if (!data.accessToken) throw new Error('No token');
ctx.token = data.accessToken;
});
// === HEALTH ===
console.log('\n📋 Health');
await test('Backend health', async () => {
const res = await fetch('http://localhost:3001/health');
if (!res.ok) throw new Error(`Status ${res.status}`);
const data = await res.json();
if (!data.dbConnected) throw new Error('DB not connected');
});
// === ORDERS ===
console.log('\n📋 Orders');
await test('List orders', async () => {
const data = await api('GET', '/orders');
const orders = Array.isArray(data) ? data : data.data || [];
if (!Array.isArray(orders)) throw new Error('Expected array');
console.log(` Found ${orders.length} orders`);
});
await test('List orders forSelect (exclude ARCHIVED/CANCELLED)', async () => {
const data = await api('GET', '/orders?forSelect=true');
const orders = Array.isArray(data) ? data : data.data || [];
const bad = orders.filter((o: any) => ['ARCHIVED', 'CANCELLED'].includes(o.status));
if (bad.length > 0) throw new Error(`Found ${bad.length} ARCHIVED/CANCELLED orders`);
});
// === ESTIMATES ===
console.log('\n📋 Estimates');
await test('List estimates', async () => {
const data = await api('GET', '/estimates');
const items = Array.isArray(data) ? data : data.data || [];
console.log(` Found ${items.length} estimates`);
if (items.length > 0) {
ctx.smetaId = items[0].id;
ctx.orderId = items[0].orderId;
}
});
// === PURCHASE REQUIREMENTS ===
console.log('\n📋 Purchase Requirements');
await test('List PRs', async () => {
const data = await api('GET', '/purchase-requirements');
const items = Array.isArray(data) ? data : data.data || [];
console.log(` Found ${items.length} PRs`);
if (items.length > 0) {
ctx.prIds = items.slice(0, 3).map((pr: any) => pr.id);
}
});
await test('Consolidated PRs', async () => {
const data = await api('GET', '/purchase-requirements/consolidated');
const groups = Array.isArray(data) ? data : data.groups || data.data || [];
console.log(` Found ${groups.length} groups`);
});
// === TENDERS ===
console.log('\n📋 Tenders');
await test('List tenders', async () => {
const data = await api('GET', '/tenders');
const items = Array.isArray(data) ? data : data.data || [];
console.log(` Found ${items.length} tenders`);
if (items.length > 0) {
ctx.tenderId = items[0].id;
}
});
// === PURCHASE ORDERS ===
console.log('\n📋 Purchase Orders');
await test('List POs', async () => {
const data = await api('GET', '/purchase-orders');
const items = Array.isArray(data) ? data : data.data || [];
console.log(` Found ${items.length} POs`);
});
// === SUPPLIERS ===
console.log('\n📋 Suppliers');
await test('List suppliers', async () => {
const data = await api('GET', '/suppliers');
const items = Array.isArray(data) ? data : data.data || [];
console.log(` Found ${items.length} suppliers`);
});
// === DOCUMENTS ===
console.log('\n📋 Documents');
await test('List documents (contract-tracking)', async () => {
const data = await api('GET', '/contract-tracking');
const items = Array.isArray(data) ? data : data.data || [];
console.log(` Found ${items.length} documents`);
});
// === PAYMENT REQUESTS ===
console.log('\n📋 Payment Requests');
await test('List payment requests', async () => {
const data = await api('GET', '/payment-requests');
const items = Array.isArray(data) ? data : data.data || [];
console.log(` Found ${items.length} payment requests`);
});
// === CLIENTS ===
console.log('\n📋 Clients');
await test('List clients', async () => {
const data = await api('GET', '/clients');
const items = Array.isArray(data) ? data : data.data || [];
console.log(` Found ${items.length} clients`);
});
// === FINANCIAL ===
console.log('\n📋 Financial');
await test('Financial dashboard', async () => {
const data = await api('GET', '/financial/dashboard');
if (!data) throw new Error('No dashboard data');
});
// === CASHFLOW ===
console.log('\n📋 Cashflow');
await test('Cashflow list', async () => {
const data = await api('GET', '/cashflow');
const items = Array.isArray(data) ? data : data.data || data.items || [];
console.log(` Found ${items.length} cashflow entries`);
});
// === NOTIFICATIONS ===
console.log('\n📋 Notifications');
await test('Notifications count', async () => {
const data = await api('GET', '/notifications/count');
console.log(` Unread: ${data.count ?? data.unread ?? 0}`);
});
// === ADMIN ===
console.log('\n📋 Admin');
await test('List users', async () => {
const data = await api('GET', '/admin/users');
const items = Array.isArray(data) ? data : data.data || data.items || [];
console.log(` Found ${items.length} users`);
});
await test('System config', async () => {
const data = await api('GET', '/admin/config');
const items = Array.isArray(data) ? data : data.data || data.items || [];
console.log(` Found ${items.length} config entries`);
});
// === SUMMARY ===
console.log(`\n${'='.repeat(50)}`);
console.log(`Results: ${passed} passed, ${failed} failed, ${passed + failed} total`);
console.log(`${'='.repeat(50)}\n`);
process.exit(failed > 0 ? 1 : 0);
}
run().catch(err => {
console.error('Fatal error:', err);
process.exit(1);
});