- admin-routes.js: create-with-credentials, /convert, /reset-password, /revoke-sessions, /sessions, /import (transactional CSV import — whole file validated, one Postgres transaction, never accepts a password/hash column from input). Fixed GET /api/admin/students to select an explicit safe column list instead of SELECT * — the new password_hash/credential_token_hash columns must never be exposed through this route. - server.js: /api/login now also blocks on credential_token_hash (not just password_hash) — closes a real window where a student mid-conversion stayed reachable via the old bare-student_id path. studentSession middleware now re-verifies status='active' on every request, not just at session creation, matching instructorSession's existing behavior. - sessions.js: revokeAllStudentSessions, listStudentSessions. - rbac.js: extracted checkCohortAccess/checkCohortWriteAccess as shared core functions so the new classroom/help-request RBAC (next commit) reuses the exact same authorization rule instead of a parallel copy. - student-csv.js + test: hand-rolled parser (no new dependency), unit tested. - prometheus.yml.j2: activated the platform-api scrape job.
47 lines
1.8 KiB
JavaScript
47 lines
1.8 KiB
JavaScript
#!/usr/bin/env node
|
|
// Fast, isolated unit test for student-csv.js — no Postgres, no HTTP. No framework, matching
|
|
// project convention (see platform/lab-router/test/run.js).
|
|
|
|
const assert = require('assert');
|
|
const { parseStudentCsv } = require('../src/student-csv');
|
|
|
|
function main() {
|
|
{
|
|
const r = parseStudentCsv('display_name,email,login_identifier,cohort_id\nAlice,[email protected],,root-fall\nBob,,bob01,root-fall');
|
|
assert.equal(r.errors.length, 0);
|
|
assert.equal(r.rows.length, 2);
|
|
assert.equal(r.rows[0].email, '[email protected]');
|
|
assert.equal(r.rows[1].login_identifier, 'bob01');
|
|
console.log('ok — valid file parses');
|
|
}
|
|
{
|
|
const r = parseStudentCsv('email\[email protected]');
|
|
assert.ok(r.errors.length > 0);
|
|
console.log('ok — missing display_name column rejected');
|
|
}
|
|
{
|
|
const r = parseStudentCsv('display_name,password\nAlice,hunter2');
|
|
assert.ok(r.errors.some((e) => e.message.includes('password')));
|
|
console.log('ok — password column never accepted from a file');
|
|
}
|
|
{
|
|
const r = parseStudentCsv('display_name,email\nAlice,[email protected],extra');
|
|
assert.ok(r.errors.length > 0);
|
|
assert.equal(r.rows.length, 0);
|
|
console.log('ok — a malformed row rejects the whole file (transactional, no partial import)');
|
|
}
|
|
{
|
|
const r = parseStudentCsv('display_name,email\nAlice,[email protected]\nBob,[email protected]');
|
|
assert.ok(r.errors.some((e) => e.message.includes('duplicate email')));
|
|
console.log('ok — duplicate email within one file rejected');
|
|
}
|
|
{
|
|
const r = parseStudentCsv('display_name,email\nAlice,not-an-email');
|
|
assert.ok(r.errors.some((e) => e.message.includes('invalid email')));
|
|
console.log('ok — invalid email format rejected');
|
|
}
|
|
console.log('\nAll student-csv unit tests passed.');
|
|
}
|
|
|
|
main();
|