5aa2dd1a4b
Phase C, Stage C-1 (ветка feature/custom-roles): таблица roles (name, label, base_roles JSON, is_builtin) + засев встроенных. auth.effectiveRoles(role) — кастомная роль наследует base_roles (какие встроенные гейты проходит); встроенные — быстрый путь без БД. requireRole() теперь проверяет пересечение allowed с effectiveRoles → 111 существующих гейтов не задеты (встроенные ведут себя как прежде). Дизайн: PHASE_C_DESIGN.md. Тест effectiveRoles 5/5; полный backend pass. ВАЖНО (обнаружено): users.role в канон-схеме имеет CHECK (admin/teacher/student/ free_student), безопасно пересобрать users (FK от многих таблиц, миграции в txn) нельзя → присвоение кастомной роли пользователю пойдёт через users.custom_role (C-2). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
55 lines
2.6 KiB
JavaScript
55 lines
2.6 KiB
JavaScript
'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, 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);
|
|
});
|
|
});
|