'use strict'; const { test, after } = require('node:test'); const assert = require('node:assert/strict'); const { db, inject, getToken, cleanup } = require('./setup'); after(() => cleanup()); /* 1. Student cannot delete a teacher's class ─────────────────────────────── */ test('student cannot delete another teacher\'s class', async () => { const teacher = await getToken('teacher'); const student = await getToken('student'); const create = await inject('POST', '/api/classes', { name: 'Security test class 1' }, teacher.token); assert.ok(create.status < 300, `create failed: ${JSON.stringify(create.body)}`); const classId = create.body.id; const del = await inject('DELETE', `/api/classes/${classId}`, null, student.token); assert.equal(del.status, 403, `expected 403, got ${del.status}: ${JSON.stringify(del.body)}`); // Class must still exist const row = db.prepare('SELECT id FROM classes WHERE id = ?').get(classId); assert.ok(row, 'class was deleted — IDOR confirmed'); }); /* 2. Teacher A cannot delete Teacher B's class ───────────────────────────── */ test('teacher cannot delete another teacher\'s class', async () => { const tA = await getToken('teacher'); const tB = await getToken('teacher'); const create = await inject('POST', '/api/classes', { name: 'Teacher B class' }, tB.token); assert.ok(create.status < 300, `create failed: ${JSON.stringify(create.body)}`); const classId = create.body.id; const del = await inject('DELETE', `/api/classes/${classId}`, null, tA.token); assert.equal(del.status, 403, `expected 403, got ${del.status}: ${JSON.stringify(del.body)}`); const row = db.prepare('SELECT id FROM classes WHERE id = ?').get(classId); assert.ok(row, 'class was deleted by wrong teacher — IDOR confirmed'); }); /* 3. Banned user is blocked on protected routes ─────────────────────────── */ test('banned user is blocked on protected routes', async () => { const u = await getToken('student'); // Token works before ban const before = await inject('GET', '/api/auth/me', null, u.token); assert.equal(before.status, 200); db.prepare('UPDATE users SET is_banned = 1 WHERE id = ?').run(u.userId); const after = await inject('GET', '/api/auth/me', null, u.token); assert.equal(after.status, 403, `expected 403 for banned user, got ${after.status}`); assert.ok( /заблокирован|banned/i.test(after.body.error || ''), `expected ban message, got: ${after.body.error}` ); // Cleanup db.prepare('UPDATE users SET is_banned = 0 WHERE id = ?').run(u.userId); }); /* 4. Token is revoked when token_version is bumped ──────────────────────── */ test('token revoked when token_version changes', async () => { const u = await getToken('student'); // Token works const ok = await inject('GET', '/api/auth/me', null, u.token); assert.equal(ok.status, 200); // Simulate password change (bumps token_version) db.prepare('UPDATE users SET token_version = COALESCE(token_version, 0) + 1 WHERE id = ?').run(u.userId); const revoked = await inject('GET', '/api/auth/me', null, u.token); assert.equal(revoked.status, 401, `expected 401 after token_version bump, got ${revoked.status}`); }); /* 5. Student cannot join class with wrong invite code ───────────────────── */ test('join with wrong invite code returns 404', async () => { const student = await getToken('student'); const join = await inject('POST', '/api/classes/join', { invite_code: 'WRONG-XXXX-0000' }, student.token); assert.ok(join.status === 404 || join.status === 400, `expected 404 or 400, got ${join.status}: ${JSON.stringify(join.body)}`); // Verify no class membership was created const membership = db.prepare('SELECT 1 FROM class_members WHERE user_id = ?').get(student.userId); assert.equal(membership, undefined, 'student joined a class with wrong code'); }); /* 6. Admin-only route blocks teacher ────────────────────────────────────── */ test('admin-only GET /admin/users blocks teacher', async () => { const teacher = await getToken('teacher'); const res = await inject('GET', '/api/admin/users', null, teacher.token); assert.equal(res.status, 403, `expected 403 for teacher on admin route, got ${res.status}`); }); /* 7. Protected route without token returns 401 ──────────────────────────── */ test('protected route without token returns 401', async () => { const res = await inject('GET', '/api/auth/me', null, null); assert.equal(res.status, 401); });