145 lines
7.8 KiB
JavaScript
145 lines
7.8 KiB
JavaScript
/**
|
|
* Smoke test for enrollment workflow + bulk marks CSV upload.
|
|
* Runs against a live server on http://127.0.0.1:3001.
|
|
*
|
|
* Usage: node evidence/smoke.js
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
// Use 'localhost' (resolves to both IPv4 and IPv6); on this Windows host the
|
|
// Node listener binds to :: (IPv6 any-address), so 127.0.0.1 fails.
|
|
const BASE = 'http://localhost:3001';
|
|
const log = (...a) => console.log(...a);
|
|
const fail = (msg) => { console.error('FAIL:', msg); process.exitCode = 1; };
|
|
const pass = (msg) => log(' PASS:', msg);
|
|
|
|
async function login(email, password) {
|
|
const r = await fetch(`${BASE}/api/auth/login`, {
|
|
method: 'POST',
|
|
headers: { 'content-type': 'application/json' },
|
|
body: JSON.stringify({ email, password }),
|
|
});
|
|
const body = await r.json();
|
|
if (!r.ok || !body.token) {
|
|
throw new Error(`login ${email} -> ${r.status} ${JSON.stringify(body)}`);
|
|
}
|
|
return { token: body.token, user: body.user };
|
|
}
|
|
|
|
async function api(method, path, { token, json, formData } = {}) {
|
|
const headers = {};
|
|
if (token) headers['authorization'] = `Bearer ${token}`;
|
|
let body;
|
|
if (json !== undefined) {
|
|
headers['content-type'] = 'application/json';
|
|
body = JSON.stringify(json);
|
|
} else if (formData) {
|
|
body = formData;
|
|
}
|
|
const r = await fetch(`${BASE}${path}`, { method, headers, body });
|
|
let parsed = null;
|
|
const text = await r.text();
|
|
try { parsed = text ? JSON.parse(text) : null; } catch { parsed = text; }
|
|
return { status: r.status, body: parsed };
|
|
}
|
|
|
|
(async () => {
|
|
log('============================================================');
|
|
log(' Smoke test: enrollment workflow + bulk marks CSV upload');
|
|
log(` Time: ${new Date().toISOString()}`);
|
|
log('============================================================\n');
|
|
|
|
// ---- [1] Student self-enrols in class_id=2 (Grade 2) ----
|
|
// student5@school.com (id=10) is seeded in Grade 1 only; safe to enrol in Grade 2.
|
|
log('[1] Student student5@school.com self-enrols in class_id=2 (Grade 2)');
|
|
const student = await login('student5@school.com', 'student123');
|
|
log(` student token len: ${student.token.length}`);
|
|
|
|
const enrol = await api('POST', '/api/enrollments', {
|
|
token: student.token,
|
|
json: { class_id: 2 },
|
|
});
|
|
log(` HTTP ${enrol.status} -> ${JSON.stringify(enrol.body)}`);
|
|
if (enrol.status !== 201 && enrol.status !== 200) fail('expected 201/200 on enrol');
|
|
if (enrol.body.request_status !== 'pending') fail(`expected request_status=pending, got ${enrol.body.request_status}`);
|
|
if (enrol.body.status !== 'inactive') fail(`expected status=inactive, got ${enrol.body.status}`);
|
|
if (!enrol.body.requested_at) fail('expected requested_at to be set');
|
|
if (enrol.body.approved_by !== null) fail(`expected approved_by=null, got ${enrol.body.approved_by}`);
|
|
pass(`request_status=${enrol.body.request_status}, status=${enrol.body.status}`);
|
|
const enrollmentId = enrol.body.id;
|
|
|
|
// ---- [2] Admin GET /api/enrollments/pending ----
|
|
log('\n[2] Admin GET /api/enrollments/pending');
|
|
const admin = await login('admin@school.com', 'admin123');
|
|
const pending = await api('GET', '/api/enrollments/pending', { token: admin.token });
|
|
log(` HTTP ${pending.status}, ${Array.isArray(pending.body) ? pending.body.length : 'n/a'} rows`);
|
|
if (pending.status !== 200) fail('expected 200 on pending list');
|
|
if (!Array.isArray(pending.body)) fail('expected array');
|
|
const found = pending.body.find(r => r.id === enrollmentId);
|
|
if (!found) fail(`enrollment ${enrollmentId} not found in pending list`);
|
|
else pass(`enrollment ${enrollmentId} visible to admin`);
|
|
|
|
// ---- [3] Admin POST /api/enrollments/:id/approve ----
|
|
log('\n[3] Admin POST /api/enrollments/:id/approve');
|
|
const approve = await api('POST', `/api/enrollments/${enrollmentId}/approve`, { token: admin.token });
|
|
log(` HTTP ${approve.status} -> ${JSON.stringify(approve.body)}`);
|
|
if (approve.status !== 200) fail('expected 200 on approve');
|
|
if (approve.body.request_status !== 'approved') fail('expected request_status=approved');
|
|
if (approve.body.status !== 'active') fail('expected status=active');
|
|
if (!approve.body.approved_at) fail('expected approved_at to be set');
|
|
if (approve.body.approved_by !== admin.user.id) fail('expected approved_by=admin.id');
|
|
pass(`request_status=${approve.body.request_status}, approved_by=${approve.body.approved_by}`);
|
|
|
|
// ---- [4] Teacher uploads 3-row valid CSV ----
|
|
log('\n[4] Teacher uploads evidence/valid.csv (3 rows, all valid)');
|
|
const teacher = await login('teacher@school.com', 'teacher123');
|
|
const fd = new FormData();
|
|
fd.append('file', new Blob([fs.readFileSync(path.resolve(__dirname, 'valid.csv'))], { type: 'text/csv' }), 'valid.csv');
|
|
const validRes = await api('POST', '/api/marks/bulk', { token: teacher.token, formData: fd });
|
|
log(` HTTP ${validRes.status} -> ${JSON.stringify(validRes.body)}`);
|
|
if (validRes.status !== 200) fail('expected 200 on bulk valid');
|
|
if (validRes.body.inserted !== 3) fail(`expected inserted=3, got ${validRes.body.inserted}`);
|
|
if (validRes.body.updated !== 0) fail(`expected updated=0, got ${validRes.body.updated}`);
|
|
if (!Array.isArray(validRes.body.errors) || validRes.body.errors.length !== 0) fail('expected errors=[]');
|
|
pass(`inserted=${validRes.body.inserted}, updated=${validRes.body.updated}, errors=[]`);
|
|
|
|
// ---- [5] Teacher uploads 4-row mixed CSV (2 valid + 2 invalid) ----
|
|
log('\n[5] Teacher uploads evidence/mixed.csv (4 rows: 2 valid, 2 invalid)');
|
|
const fd2 = new FormData();
|
|
fd2.append('file', new Blob([fs.readFileSync(path.resolve(__dirname, 'mixed.csv'))], { type: 'text/csv' }), 'mixed.csv');
|
|
const mixedRes = await api('POST', '/api/marks/bulk', { token: teacher.token, formData: fd2 });
|
|
log(` HTTP ${mixedRes.status} -> ${JSON.stringify(mixedRes.body)}`);
|
|
if (mixedRes.status !== 200) fail('expected 200 on bulk mixed');
|
|
// 2 valid inserts/updates + 2 errors
|
|
const insUpd = (mixedRes.body.inserted || 0) + (mixedRes.body.updated || 0);
|
|
if (insUpd !== 2) fail(`expected inserted+updated=2, got inserted=${mixedRes.body.inserted} updated=${mixedRes.body.updated}`);
|
|
if (!Array.isArray(mixedRes.body.errors) || mixedRes.body.errors.length !== 2) {
|
|
fail(`expected errors=[2 items], got ${JSON.stringify(mixedRes.body.errors)}`);
|
|
} else {
|
|
const reasons = mixedRes.body.errors.map(e => e.reason).join(' | ');
|
|
pass(`inserted=${mixedRes.body.inserted}, updated=${mixedRes.body.updated}, errors=2 [${reasons}]`);
|
|
}
|
|
|
|
// ---- [6] Negative: student cannot call /marks/bulk ----
|
|
log('\n[6] Student attempts POST /api/marks/bulk (should be 403)');
|
|
const fd3 = new FormData();
|
|
fd3.append('file', new Blob([fs.readFileSync(path.resolve(__dirname, 'valid.csv'))], { type: 'text/csv' }), 'valid.csv');
|
|
const stuBulk = await api('POST', '/api/marks/bulk', { token: student.token, formData: fd3 });
|
|
log(` HTTP ${stuBulk.status} -> ${JSON.stringify(stuBulk.body)}`);
|
|
if (stuBulk.status !== 403) fail(`expected 403, got ${stuBulk.status}`);
|
|
else pass('student correctly blocked from /marks/bulk');
|
|
|
|
// ---- [7] GET /api/marks?assignment_id=1 (teacher sees all) ----
|
|
log('\n[7] GET /api/marks?assignment_id=1');
|
|
const marksList = await api('GET', '/api/marks?assignment_id=1', { token: teacher.token });
|
|
log(` HTTP ${marksList.status}, ${Array.isArray(marksList.body) ? marksList.body.length : 'n/a'} rows`);
|
|
if (marksList.status !== 200) fail('expected 200 on /marks list');
|
|
if (!Array.isArray(marksList.body)) fail('expected array');
|
|
pass(`returned ${marksList.body.length} submission rows for assignment 1`);
|
|
|
|
log('\n============================================================');
|
|
log(process.exitCode === 1 ? 'SMOKE: FAIL' : 'SMOKE: PASS');
|
|
log('============================================================');
|
|
})().catch(e => { console.error('UNCAUGHT:', e); process.exit(1); }); |