Files
Learn_System/backend/tests/roles-api.test.js
T
Maxim Dolgolyov bdc8bef857 feat(permissions): C-4a — API конструктора ролей (/api/roles, admin)
rolesController + routes/roles (admin, inline guards): GET список (с числом
пользователей), POST создать кастомную роль (имя-идентификатор + метка + base_roles;
засев прав из функциональной базы), PUT изменить, DELETE удалить (пользователей
возвращает на базу), GET /:name/permissions (эффективная карта база+оверлей + defs).
setPermission теперь принимает кастомные роли (ключ валидируется по базе, хранится
под именем роли). Смонтировано в server.js + тест-харнесс. Тест roles-api 5/5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 15:21:44 +03:00

71 lines
4.3 KiB
JavaScript

'use strict';
/**
* Phase C, C-4a — API конструктора ролей (/api/roles, admin).
* Имя роли — латинский идентификатор (sanitize), метка — любая. Создание засевает
* права из функциональной базы; setPermission принимает кастомные роли (ключ по базе);
* удаление возвращает пользователей на базу; встроенные роли защищены; не-админу 403.
*/
const { describe, it, before, after } = require('node:test');
const assert = require('node:assert/strict');
const { db, getToken, inject, cleanup } = require('./setup');
after(() => cleanup());
describe('roles API (C-4)', () => {
let adminToken, studentUid;
before(async () => {
adminToken = (await getToken('admin')).token;
studentUid = (await getToken('student')).userId;
});
it('создание кастомной роли (имя санитизируется) + засев прав из базы', async () => {
const r = await inject('POST', '/api/roles', { name: 'Curator-1', label: 'Куратор', baseRoles: ['teacher'] }, adminToken);
assert.equal(r.status, 200, JSON.stringify(r.body));
assert.equal(r.body.name, 'curator1', 'имя приведено к латинскому идентификатору');
const list = await inject('GET', '/api/roles', null, adminToken);
assert.ok(list.body.some(x => x.name === 'curator1' && !x.isBuiltin && x.baseRoles[0] === 'teacher'));
const rp = await inject('GET', '/api/roles/curator1/permissions', null, adminToken);
assert.equal(rp.status, 200);
assert.equal(rp.body.base, 'teacher');
assert.ok(rp.body.definitions.length > 0, 'есть определения ключей базы');
assert.equal(rp.body.permissions['classes.manage'], true, 'classes.manage засеян из teacher (default 1)');
});
it('конфиг права кастомной роли через setPermission (ключ валиден по базе)', async () => {
const r = await inject('POST', '/api/permissions', { role: 'curator1', permission: 'questions.manage', enabled: true }, adminToken);
assert.equal(r.status, 200, JSON.stringify(r.body));
const row = db.prepare("SELECT enabled FROM role_permissions WHERE role='curator1' AND permission='questions.manage'").get();
assert.ok(row && row.enabled === 1, 'право сохранено под именем кастомной роли');
const bad = await inject('POST', '/api/permissions', { role: 'curator1', permission: 'tests.free', enabled: true }, adminToken);
assert.equal(bad.status, 400, 'tests.free — студенческий ключ, не для teacher-базы');
});
it('удаление роли возвращает пользователей на функциональную базу', async () => {
await inject('PATCH', `/api/admin/users/${studentUid}/role`, { role: 'curator1' }, adminToken);
let row = db.prepare('SELECT role, custom_role FROM users WHERE id=?').get(studentUid);
assert.equal(row.custom_role, 'curator1'); assert.equal(row.role, 'teacher');
const del = await inject('DELETE', '/api/roles/curator1', null, adminToken);
assert.equal(del.status, 200);
assert.ok(del.body.reassigned >= 1);
row = db.prepare('SELECT role, custom_role FROM users WHERE id=?').get(studentUid);
assert.equal(row.custom_role, null, 'custom_role снят');
assert.equal(row.role, 'teacher', 'остался на функциональной базе');
assert.ok(!db.prepare("SELECT 1 FROM roles WHERE name='curator1'").get(), 'роль удалена');
});
it('встроенную роль нельзя удалить/изменить', async () => {
assert.equal((await inject('DELETE', '/api/roles/teacher', null, adminToken)).status, 400);
assert.equal((await inject('PUT', '/api/roles/teacher', { label: 'x' }, adminToken)).status, 400);
});
it('не-админу — 403', async () => {
const fresh = await getToken('student'); // свежий токен (studentTok мог быть инвалидирован сменой роли)
assert.equal((await inject('GET', '/api/roles', null, fresh.token)).status, 403);
});
});