'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); }); });