feat(permissions): B7 — пресеты-профили прав (применение к классу одним кликом)

PRESETS (student): «Полный доступ», «Режим фокуса» (без магазина/испытаний),
«Ограниченный» (+ без лаборатории), «Сбросить к стандарту роли». GET
/api/permissions/presets + POST /api/permissions/class/:id/preset (admin).
Рефактор: общий applyPermsToClass() (карта key→1/0/inherit) — его используют и
bulk, и preset. В блоке «Массово по классу» — кнопки пресетов (с подтверждением).
Тест: список + применение focus/reset + валидация. Backend pass (3 baseline-Auth).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-03 14:33:25 +03:00
parent b95b639e75
commit 8b495f1508
5 changed files with 110 additions and 21 deletions
+28
View File
@@ -246,4 +246,32 @@ describe('Permissions', () => {
{ permission: 'questions.manage', enabled: true }, adminToken);
assert.equal(bad.status, 400);
});
// ── B7: пресеты-профили ────────────────────────────────────────────────────
it('B7: пресеты — список + применение к классу + валидация', async () => {
const list = await inject('GET', '/api/permissions/presets', null, adminToken);
assert.equal(list.status, 200);
assert.ok(Array.isArray(list.body.student) && list.body.student.some(p => p.id === 'focus'));
const cr = await inject('POST', '/api/classes', { name: 'PresetClass' }, adminToken);
assert.ok(cr.status < 300);
const cid = db.prepare('SELECT id FROM classes WHERE name = ?').get('PresetClass').id;
await inject('POST', `/api/classes/${cid}/members`, { user_id: studentUser.userId }, adminToken);
// focus: shop.purchase=0, gamification.challenges=0
const ap = await inject('POST', `/api/permissions/class/${cid}/preset`, { preset: 'focus' }, adminToken);
assert.equal(ap.status, 200);
assert.ok(ap.body.affected >= 1);
const shop = db.prepare('SELECT enabled FROM user_permissions WHERE user_id=? AND permission=?')
.get(studentUser.userId, 'shop.purchase');
assert.ok(shop && shop.enabled === 0, 'focus выключил магазин');
// reset: снимает все оверрайды
await inject('POST', `/api/permissions/class/${cid}/preset`, { preset: 'reset' }, adminToken);
const left = db.prepare("SELECT COUNT(*) n FROM user_permissions WHERE user_id=?").get(studentUser.userId).n;
assert.equal(left, 0, 'reset снял все личные правила');
const badP = await inject('POST', `/api/permissions/class/${cid}/preset`, { preset: 'nope' }, adminToken);
assert.equal(badP.status, 400);
});
});