106 lines
5.1 KiB
JavaScript
106 lines
5.1 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* End-to-end smoke test — drives the running server over HTTP (no browser needed).
|
|
* Store case: catalog → cart validation → mock checkout → order, plus the base auth/roles flow.
|
|
* Start the app first (`npm start`), then: QA_BASE_URL=http://127.0.0.1:3000 node tests/e2e/smoke.mjs
|
|
*/
|
|
const BASE = (process.env.QA_BASE_URL || 'http://127.0.0.1:3000').replace(/\/$/, '');
|
|
const jar = new Map();
|
|
const failures = [];
|
|
|
|
function store(res) {
|
|
const list = typeof res.headers.getSetCookie === 'function' ? res.headers.getSetCookie() : [];
|
|
for (const raw of list) {
|
|
const pair = raw.split(';')[0];
|
|
const eq = pair.indexOf('=');
|
|
if (eq > 0) jar.set(pair.slice(0, eq), pair.slice(eq + 1));
|
|
}
|
|
}
|
|
|
|
async function req(path, { method = 'GET', body } = {}) {
|
|
const headers = {};
|
|
if (body !== undefined) headers['Content-Type'] = 'application/json';
|
|
if (jar.size) headers.cookie = [...jar.entries()].map(([k, v]) => `${k}=${v}`).join('; ');
|
|
const res = await fetch(BASE + path, { method, headers, body: body === undefined ? undefined : JSON.stringify(body), redirect: 'manual' });
|
|
store(res);
|
|
const json = await res.clone().json().catch(() => null);
|
|
return { status: res.status, json };
|
|
}
|
|
|
|
function check(name, cond, detail = '') {
|
|
if (cond) console.log(` ok ${name}`);
|
|
else {
|
|
console.log(` FAIL ${name}${detail ? ` — ${detail}` : ''}`);
|
|
failures.push(name);
|
|
}
|
|
}
|
|
|
|
async function run() {
|
|
const email = `smoke_${Date.now().toString(36)}@example.com`;
|
|
|
|
const health = await req('/api/health');
|
|
check('health 200', health.status === 200 && health.json?.status === 'ok', `status=${health.status}`);
|
|
|
|
// --- catalog
|
|
const catalog = await req('/api/products');
|
|
const items = catalog.json?.items ?? [];
|
|
check('catalog returns products', catalog.status === 200 && items.length > 0, `status=${catalog.status} count=${items.length}`);
|
|
const product = items.find((p) => p.name === 'Classic Tee') ?? items[0];
|
|
|
|
const filtered = await req('/api/products?category=home');
|
|
check('category filter works', filtered.status === 200 && (filtered.json?.items ?? []).every((p) => p.category?.slug === 'home'), `status=${filtered.status}`);
|
|
|
|
// --- checkout validation (server-side)
|
|
const empty = await req('/api/checkout', { method: 'POST', body: { email, address: '123 Test St', lines: [] } });
|
|
check('empty cart rejected', empty.status === 400, `status=${empty.status}`);
|
|
|
|
if (product) {
|
|
const limited = items.find((p) => typeof p.stock === 'number' && p.stock >= 1 && p.stock < 99) ?? product;
|
|
const tooMany = await req('/api/checkout', {
|
|
method: 'POST',
|
|
body: { email, address: '123 Test St', lines: [{ productId: limited.id, quantity: Math.min(99, limited.stock + 1) }] },
|
|
});
|
|
check('over-stock rejected', tooMany.status === 409, `status=${tooMany.status} stock=${limited.stock}`);
|
|
|
|
const order = await req('/api/checkout', {
|
|
method: 'POST',
|
|
body: { email, address: '123 Test St, Springfield', lines: [{ productId: product.id, quantity: 1 }] },
|
|
});
|
|
check('mock checkout creates a paid order', order.status === 201 && order.json?.order?.status === 'paid', `status=${order.status} order=${JSON.stringify(order.json?.order)}`);
|
|
}
|
|
|
|
// --- base auth / roles
|
|
const reg = await req('/api/auth/register', { method: 'POST', body: { email, password: 'password123', name: 'Smoke' } });
|
|
check('register 201', reg.status === 201, `status=${reg.status}`);
|
|
|
|
const newUserAdmin = await req('/api/admin/orders');
|
|
check('non-admin blocked from admin orders', newUserAdmin.status === 403, `status=${newUserAdmin.status}`);
|
|
|
|
await req('/api/auth/logout', { method: 'POST' });
|
|
const afterLogout = await req('/api/auth/me');
|
|
check('logout clears session', afterLogout.status === 401, `status=${afterLogout.status}`);
|
|
|
|
const adminLogin = await req('/api/auth/login', { method: 'POST', body: { email: 'admin@example.com', password: 'password' } });
|
|
check('seeded admin can sign in', adminLogin.status === 200 && adminLogin.json?.user?.role === 'admin', `status=${adminLogin.status}`);
|
|
|
|
const adminProducts = await req('/api/admin/products');
|
|
check('admin sees products', adminProducts.status === 200 && (adminProducts.json?.products ?? []).length >= 12, `status=${adminProducts.status}`);
|
|
|
|
const adminOrders = await req('/api/admin/orders');
|
|
check('admin sees orders', adminOrders.status === 200 && Array.isArray(adminOrders.json?.orders), `status=${adminOrders.status}`);
|
|
|
|
await req('/api/auth/logout', { method: 'POST' });
|
|
const customerLogin = await req('/api/auth/login', { method: 'POST', body: { email: 'customer@example.com', password: 'password' } });
|
|
check('seeded customer can sign in', customerLogin.status === 200, `status=${customerLogin.status}`);
|
|
const myOrders = await req('/api/orders');
|
|
check('customer sees own orders', myOrders.status === 200 && (myOrders.json?.orders ?? []).length >= 2, `status=${myOrders.status}`);
|
|
|
|
console.log(failures.length ? `\n${failures.length} check(s) failed` : '\nAll e2e checks passed');
|
|
process.exit(failures.length ? 1 : 0);
|
|
}
|
|
|
|
run().catch((e) => {
|
|
console.error(e);
|
|
process.exit(1);
|
|
});
|