metallkart-erp/tests/backend/email.test.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

209 lines
6.3 KiB
TypeScript

/**
* Tests ACHATS-B — Email service SMTP/IMAP
* Run: tsx tests/email.test.ts
* Requires server running on localhost:3001 + seeded DB
*/
const BASE = 'http://localhost:3001/api/v1';
const AUTH = `${BASE}/auth`;
let passed = 0;
let failed = 0;
const failures: string[] = [];
function assert(condition: boolean, testName: string, detail?: string) {
if (condition) {
passed++;
console.log(`${testName}`);
} else {
failed++;
const msg = detail ? `${testName}${detail}` : testName;
failures.push(msg);
console.log(`${msg}`);
}
}
async function login(email: string, password: string): Promise<string> {
const res = await fetch(`${AUTH}/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
const data = await res.json() as any;
return data.accessToken;
}
function authHeaders(token: string): Record<string, string> {
return {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
};
}
function authOnly(token: string): Record<string, string> {
return { Authorization: `Bearer ${token}` };
}
let adminToken: string;
let acheteurToken: string;
let commercialToken: string;
// --- Test: POST /email/test-send — success ---
async function testSendEmail() {
console.log('\n--- POST /email/test-send ---');
// Happy path: admin sends test email
const res = await fetch(`${BASE}/email/test-send`, {
method: 'POST',
headers: authHeaders(adminToken),
body: JSON.stringify({
to: 'purchase@metdesigntver.ru',
subject: 'ERP Test Email ' + new Date().toISOString(),
message: 'This is a test email from MetallKart ERP email module.',
}),
});
const data = await res.json() as any;
assert(
res.status === 200 && data.success === true && typeof data.messageId === 'string',
'test-send success with messageId',
`status=${res.status} success=${data.success} messageId=${data.messageId}`,
);
}
// --- Test: POST /email/test-send — validation error ---
async function testSendEmailValidation() {
console.log('\n--- POST /email/test-send validation ---');
const res = await fetch(`${BASE}/email/test-send`, {
method: 'POST',
headers: authHeaders(adminToken),
body: JSON.stringify({ to: 'not-an-email', subject: '', message: '' }),
});
assert(res.status === 400, 'test-send validation rejects invalid email', `status=${res.status}`);
}
// --- Test: POST /email/test-send — RBAC non-admin 403 ---
async function testSendEmailRBAC() {
console.log('\n--- POST /email/test-send RBAC ---');
// ACHETEUR should be forbidden
const res1 = await fetch(`${BASE}/email/test-send`, {
method: 'POST',
headers: authHeaders(acheteurToken),
body: JSON.stringify({
to: 'test@example.com',
subject: 'test',
message: 'test',
}),
});
assert(res1.status === 403, 'test-send rejected for ACHETEUR (403)', `status=${res1.status}`);
// COMMERCIAL should be forbidden
const res2 = await fetch(`${BASE}/email/test-send`, {
method: 'POST',
headers: authHeaders(commercialToken),
body: JSON.stringify({
to: 'test@example.com',
subject: 'test',
message: 'test',
}),
});
assert(res2.status === 403, 'test-send rejected for COMMERCIAL (403)', `status=${res2.status}`);
}
// --- Test: POST /email/test-send — unauthenticated 401 ---
async function testSendEmailUnauth() {
console.log('\n--- POST /email/test-send unauthenticated ---');
const res = await fetch(`${BASE}/email/test-send`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
to: 'test@example.com',
subject: 'test',
message: 'test',
}),
});
assert(res.status === 401, 'test-send rejected without auth (401)', `status=${res.status}`);
}
// --- Test: GET /email/inbox — success ---
async function testInbox() {
console.log('\n--- GET /email/inbox ---');
const res = await fetch(`${BASE}/email/inbox`, {
headers: authOnly(adminToken),
});
const data = await res.json() as any;
assert(
res.status === 200 && Array.isArray(data.items) && typeof data.total === 'number',
'inbox returns items array and total',
`status=${res.status} total=${data.total}`,
);
}
// --- Test: GET /email/inbox with since param ---
async function testInboxWithSince() {
console.log('\n--- GET /email/inbox with since ---');
const since = new Date(Date.now() - 3600_000).toISOString();
const res = await fetch(`${BASE}/email/inbox?since=${encodeURIComponent(since)}`, {
headers: authOnly(adminToken),
});
const data = await res.json() as any;
assert(
res.status === 200 && Array.isArray(data.items),
'inbox with since filter returns items',
`status=${res.status}`,
);
}
// --- Test: GET /email/inbox — RBAC non-admin 403 ---
async function testInboxRBAC() {
console.log('\n--- GET /email/inbox RBAC ---');
const res = await fetch(`${BASE}/email/inbox`, {
headers: authOnly(acheteurToken),
});
assert(res.status === 403, 'inbox rejected for ACHETEUR (403)', `status=${res.status}`);
}
// --- Test: GET /email/inbox — unauthenticated 401 ---
async function testInboxUnauth() {
console.log('\n--- GET /email/inbox unauthenticated ---');
const res = await fetch(`${BASE}/email/inbox`);
assert(res.status === 401, 'inbox rejected without auth (401)', `status=${res.status}`);
}
async function main() {
console.log('=== EMAIL MODULE TESTS (ACHATS-B) ===\n');
console.log('Logging in...');
adminToken = await login('louis@metallcart.ru', 'MetallKart2026!');
acheteurToken = await login('evgeny@metallcart.ru', 'MetallKart2026!');
commercialToken = await login('anna.v@metallcart.ru', 'MetallKart2026!');
assert(!!adminToken, 'Admin login OK');
assert(!!acheteurToken, 'Acheteur login OK');
assert(!!commercialToken, 'Commercial login OK');
await testSendEmail();
await testSendEmailValidation();
await testSendEmailRBAC();
await testSendEmailUnauth();
await testInbox();
await testInboxWithSince();
await testInboxRBAC();
await testInboxUnauth();
console.log(`\n=== RESULTS: ${passed} passed, ${failed} failed ===`);
if (failures.length > 0) {
console.log('Failures:');
failures.forEach(f => console.log(` - ${f}`));
}
process.exit(failed > 0 ? 1 : 0);
}
main();