'use strict'; /** * Phase C, Stage C-1 (фундамент): таблица roles + effectiveRoles() — кастомная роль * наследует «базовые роли» (какие встроенные гейты requireRole проходит). * Здесь тестируем ядро (effectiveRoles) и поведение requireRole-мидлвари на нём. * Присвоение кастомной роли пользователю (users.custom_role) — Stage C-2 * (в канонической схеме у users.role есть CHECK, прямое присвоение невозможно). */ const { describe, it, before, after } = require('node:test'); const assert = require('node:assert/strict'); const { db, getToken, inject, cleanup } = require('./setup'); const auth = require('../src/middleware/auth'); after(() => cleanup()); describe('custom roles — effectiveRoles (C-1)', () => { before(() => { const ins = db.prepare("INSERT OR IGNORE INTO roles (name,label,base_roles,is_builtin) VALUES (?,?,?,0)"); ins.run('methodist', 'Методист', JSON.stringify(['teacher'])); ins.run('guestx', 'Гость', JSON.stringify([])); }); it('встроенная роль раскрывается в саму себя (без БД)', () => { assert.deepEqual(auth.effectiveRoles('teacher'), ['teacher']); assert.deepEqual(auth.effectiveRoles('admin'), ['admin']); }); it('кастомная роль наследует base_roles + себя', () => { assert.deepEqual(auth.effectiveRoles('methodist').sort(), ['methodist', 'teacher']); }); it('кастомная роль без base = только она сама', () => { assert.deepEqual(auth.effectiveRoles('guestx'), ['guestx']); }); it('неизвестная роль = только она сама', () => { assert.deepEqual(auth.effectiveRoles('nope'), ['nope']); }); // requireRole-мидлварь на основе effectiveRoles: allow-путь function passes(role, allowed) { let ok = false; const res = { status: () => ({ json: () => {} }) }; auth.requireRole(...allowed)({ user: { role, id: 1 }, ip: '127.0.0.1', method: 'GET', originalUrl: '/t', headers: {} }, res, () => { ok = true; }); return ok; } it('requireRole пропускает кастомную роль по наследованию', () => { assert.equal(passes('methodist', ['teacher', 'admin']), true); assert.equal(passes('guestx', ['teacher', 'admin']), false); assert.equal(passes('teacher', ['teacher', 'admin']), true); assert.equal(passes('student', ['teacher', 'admin']), false); }); }); describe('custom roles — назначение пользователю (C-2)', () => { let adminToken; before(async () => { adminToken = (await getToken('admin')).token; db.prepare("INSERT OR IGNORE INTO roles (name,label,base_roles,is_builtin) VALUES ('methodist','Методист',?,0)") .run(JSON.stringify(['teacher'])); }); it('PATCH role=кастомная → users.role=база, custom_role=имя', async () => { const u = await getToken('student'); const r = await inject('PATCH', `/api/admin/users/${u.userId}/role`, { role: 'methodist' }, adminToken); assert.equal(r.status, 200, JSON.stringify(r.body)); assert.equal(r.body.base, 'teacher'); const row = db.prepare('SELECT role, custom_role FROM users WHERE id=?').get(u.userId); assert.equal(row.role, 'teacher', 'функциональная база = teacher'); assert.equal(row.custom_role, 'methodist'); // обратно на встроенную → custom_role очищается const r2 = await inject('PATCH', `/api/admin/users/${u.userId}/role`, { role: 'student' }, adminToken); assert.equal(r2.status, 200); const row2 = db.prepare('SELECT role, custom_role FROM users WHERE id=?').get(u.userId); assert.equal(row2.role, 'student'); assert.equal(row2.custom_role, null); }); it('PATCH с несуществующей ролью → 400', async () => { const u = await getToken('student'); const bad = await inject('PATCH', `/api/admin/users/${u.userId}/role`, { role: 'ghostrole' }, adminToken); assert.equal(bad.status, 400); }); });