Files
Learn_System/backend/tests/admin-sessions.test.js
Maxim Dolgolyov 1fdbb9a445 test(backend): +31 integration tests for permissions/overview/search/sessions/features
Coverage:
- Permissions: role/user toggle + audit + token_version bump, /me, 403 non-admin (10 tests)
- Admin overview: shape, all fields, types, auth guard, empty DB zeros (4 tests)
- Cmd+K search: shape, min-query empty, SQL injection sanity, user lookup (5 tests)
- Session delete: CASCADE, audit entry, 404 missing, 403 non-admin (4 tests)
- Feature gates: disabled flag returns 404, enabled returns 401/200, admin API toggle (5 tests)
- setup.js: add /api/permissions, /api/pet, /api/biochem routes for test coverage

tests 66 (was 35) · pass 63 (was 32) · fail 3 (baseline auth.test.js, unchanged)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 21:58:37 +03:00

115 lines
5.2 KiB
JavaScript

'use strict';
/**
* Integration tests: DELETE /api/admin/sessions/:id
* Covers: session removal + cascade, audit entry, 404 for missing, 403 for non-admin.
*/
const { describe, it, before, after } = require('node:test');
const assert = require('node:assert/strict');
const { db, inject, getToken, cleanup } = require('./setup');
after(() => cleanup());
describe('Admin Session Delete', () => {
let adminToken, studentUser, subjectId, sessionId;
before(async () => {
const a = await getToken('admin');
adminToken = a.token;
const s = await getToken('student');
studentUser = s;
// Ensure subject exists
let subj = db.prepare("SELECT id FROM subjects WHERE slug = 'admin-sess-test'").get();
if (!subj) {
db.prepare("INSERT INTO subjects (slug, name, icon) VALUES ('admin-sess-test', 'Admin Sess Test', 'test')").run();
subj = db.prepare("SELECT id FROM subjects WHERE slug = 'admin-sess-test'").get();
}
subjectId = subj.id;
// Create a question with options
const qId = db.prepare(
'INSERT INTO questions (subject_id, text, difficulty) VALUES (?, ?, 1)'
).run(subjectId, 'Admin session delete test question').lastInsertRowid;
db.prepare('INSERT INTO options (question_id, text, is_correct) VALUES (?, ?, 0)').run(qId, 'Wrong');
db.prepare('INSERT INTO options (question_id, text, is_correct) VALUES (?, ?, 1)').run(qId, 'Correct');
// Start a session via API
const sessRes = await inject('POST', '/api/sessions', {
subject_slug: 'admin-sess-test', count: 1, mode: 'exam',
}, studentUser.token);
assert.equal(sessRes.status, 201, `session create failed: ${JSON.stringify(sessRes.body)}`);
sessionId = sessRes.body.session_id;
// Answer to create user_answers row
const q = sessRes.body.questions[0];
await inject('POST', `/api/sessions/${sessionId}/answer`, {
question_id: q.id, option_id: q.options[0].id,
}, studentUser.token);
});
// ── 1. Non-admin gets 403 ────────────────────────────────────────────────
it('student gets 403 when trying to delete a session', async () => {
const res = await inject('DELETE', `/api/admin/sessions/${sessionId}`, null, studentUser.token);
assert.equal(res.status, 403, `expected 403, got ${res.status}`);
});
// ── 2. 404 for non-existent session ─────────────────────────────────────
it('returns 404 for non-existent session id', async () => {
const res = await inject('DELETE', '/api/admin/sessions/999999', null, adminToken);
assert.equal(res.status, 404, `expected 404, got ${res.status}: ${JSON.stringify(res.body)}`);
});
// ── 3. Session delete cascades user_answers and session_questions ─────────
it('deletes session and cascades related rows', async () => {
// Verify rows exist before delete
const answersBefore = db.prepare('SELECT COUNT(*) AS n FROM user_answers WHERE session_id = ?')
.get(sessionId).n;
const sqBefore = db.prepare('SELECT COUNT(*) AS n FROM session_questions WHERE session_id = ?')
.get(sessionId).n;
// The session must still exist
const sessBefore = db.prepare('SELECT id FROM test_sessions WHERE id = ?').get(sessionId);
assert.ok(sessBefore, 'session should exist before delete');
const res = await inject('DELETE', `/api/admin/sessions/${sessionId}`, null, adminToken);
assert.equal(res.status, 200, `expected 200, got ${res.status}: ${JSON.stringify(res.body)}`);
assert.equal(res.body.ok, true);
// Session gone
const sessAfter = db.prepare('SELECT id FROM test_sessions WHERE id = ?').get(sessionId);
assert.equal(sessAfter, undefined, 'session should be deleted');
// user_answers gone
const answersAfter = db.prepare('SELECT COUNT(*) AS n FROM user_answers WHERE session_id = ?')
.get(sessionId).n;
assert.equal(answersAfter, 0, 'user_answers should be cleared after session delete');
// session_questions gone
const sqAfter = db.prepare('SELECT COUNT(*) AS n FROM session_questions WHERE session_id = ?')
.get(sessionId).n;
assert.equal(sqAfter, 0, 'session_questions should be cleared after session delete');
});
// ── 4. Audit entry created on delete ─────────────────────────────────────
it('creates audit log entry on session delete', async () => {
// Create and immediately delete another session
const sessRes = await inject('POST', '/api/sessions', {
subject_slug: 'admin-sess-test', count: 1, mode: 'practice',
}, studentUser.token);
assert.equal(sessRes.status, 201);
const newSessId = sessRes.body.session_id;
const countBefore = db.prepare(
"SELECT COUNT(*) AS n FROM admin_audit_log WHERE action = 'session.delete'"
).get().n;
await inject('DELETE', `/api/admin/sessions/${newSessId}`, null, adminToken);
const countAfter = db.prepare(
"SELECT COUNT(*) AS n FROM admin_audit_log WHERE action = 'session.delete'"
).get().n;
assert.ok(countAfter > countBefore, 'Expected session.delete audit entry to be created');
});
});