9b7585ac7b
Миграция 052: мост «открыть все опубликованные курсы всем существующим классам» (тип 'course' уже в CHECK из 051). courseController.list/search фильтруют курсы для НЕпривилегированных по allowedRefs(uid,'course') (content_ref = courses.id как TEXT); admin/teacher — все. /api/access/catalog отдаёт курсы; CONTENT_TYPES в админ-UI = textbook,exam,sim,course → курсы управляются во всех режимах «Доступ». Тест course-access 4/4 (allowlist+класс+privileged+каталог). Полный набор: 213 pass. ВАЖНО: новый опубликованный курс по умолчанию закрыт (allowlist) — открыть классам в админке. Мост сохранил видимость текущих опубликованных курсов у существующих классов. class_courses остаётся для назначений с дедлайном (сверх видимости). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
56 lines
2.6 KiB
JavaScript
56 lines
2.6 KiB
JavaScript
'use strict';
|
|
/**
|
|
* Фаза 1c (добавочная модель): видимость курсов по классам через content_access.
|
|
* GET /api/courses фильтрует список для НЕпривилегированных по allowedRefs(uid,'course');
|
|
* admin/teacher видят все. content_ref = courses.id (как TEXT).
|
|
*/
|
|
const { describe, it, before, after } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const { db, getToken, inject, cleanup } = require('./setup');
|
|
|
|
after(() => cleanup());
|
|
|
|
describe('course access (per-class)', () => {
|
|
let teacher, student, classId, courseId;
|
|
|
|
before(async () => {
|
|
teacher = await getToken('teacher');
|
|
student = await getToken('student');
|
|
const r = db.prepare(
|
|
`INSERT INTO courses (subject_slug, title, is_published, created_by) VALUES ('math','Acc Course',1,?)`
|
|
).run(teacher.userId);
|
|
courseId = Number(r.lastInsertRowid);
|
|
|
|
const cr = await inject('POST', '/api/classes', { name: 'CourseAcc Class' }, teacher.token);
|
|
assert.ok(cr.status < 300, JSON.stringify(cr.body));
|
|
classId = db.prepare('SELECT id FROM classes WHERE name = ?').get('CourseAcc Class').id;
|
|
await inject('POST', `/api/classes/${classId}/members`, { user_id: student.userId }, teacher.token);
|
|
});
|
|
|
|
const has = (body, id) => (Array.isArray(body) ? body : []).some(c => c.id === id);
|
|
|
|
it('ученик без правил не видит опубликованный курс (allowlist)', async () => {
|
|
const r = await inject('GET', '/api/courses', null, student.token);
|
|
assert.equal(r.status, 200);
|
|
assert.ok(!has(r.body, courseId), 'курс скрыт без правила');
|
|
});
|
|
|
|
it('teacher видит курс (privileged)', async () => {
|
|
const r = await inject('GET', '/api/courses', null, teacher.token);
|
|
assert.ok(has(r.body, courseId));
|
|
});
|
|
|
|
it('открытый классу курс виден ученику', async () => {
|
|
db.prepare(`INSERT OR IGNORE INTO content_access (content_type,content_ref,scope,target_id,allow)
|
|
VALUES ('course',?, 'class',?,1)`).run(String(courseId), classId);
|
|
const r = await inject('GET', '/api/courses', null, student.token);
|
|
assert.ok(has(r.body, courseId), 'курс виден после открытия классу');
|
|
});
|
|
|
|
it('каталог админки включает курсы', async () => {
|
|
const r = await inject('GET', '/api/access/catalog', null, teacher.token);
|
|
assert.ok(Array.isArray(r.body.courses), 'есть массив courses');
|
|
assert.ok(r.body.courses.some(c => Number(c.id) === courseId), 'наш курс в каталоге');
|
|
});
|
|
});
|