import test from "node:test"; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { parseDocx, parseWord } from "../server/parser.js"; import { decodeListing, fileMetadata } from "../server/source.js"; const fixture = (name) => readFileSync(new URL(`./fixtures/${name}.docx`, import.meta.url)); const fef = () => parseDocx(fixture("fef-23-ba"), { name: "23-ЛГ_23-БА.docx" }); test("ФЭФ: 23-БА не получает дисциплины 23-ЛГ", () => { const s = fef(), ba = s.lessons.filter((l) => l.groups.includes("23-БА")); assert.deepEqual(s.groups, ["23-ЛГ", "23-БА"]); assert.equal(ba.length, 33); assert.ok(ba.some((l) => l.subject === "Бизнес-анализ")); assert.ok( !ba.some((l) => /логистик|Экспедирование|Управление запасами/i.test(l.subject), ), ); assert.ok( s.lessons .filter((l) => l.groups.includes("23-ЛГ")) .some((l) => /Финансовая логистика/.test(l.subject)), ); }); test("ФЭФ: общая ячейка принадлежит обеим группам", () => { const shared = fef().lessons.filter((l) => l.subject.startsWith("Единый день информирования"), ); assert.equal(shared.length, 1); assert.deepEqual(shared[0].groups, ["23-ЛГ", "23-БА"]); assert.match(shared[0].raw, /15 октября/); assert.equal(shared[0].start, "13:40"); }); test("ФЭФ: варианты недель, цвет, преподаватель и аудитория сохранены", () => { const s = fef(); const lessons = s.lessons.filter( (l) => l.day === 4 && l.groups.includes("23-БА") && l.subject === "Бизнес-анализ", ); assert.deepEqual( lessons.map((l) => [l.condition, l.variant, l.room, l.teacher]), [ ["7,9,11н.", "upper", "430", "Войтенок Ж.Е."], ["4-12н", "lower", "430", "Войтенок Ж.Е."], ], ); assert.equal(lessons[1].sourceColor, "CCFFCC"); assert.equal(lessons[0].start, "10:05"); assert.equal(lessons[0].end, "11:30"); const plan = s.lessons.find( (l) => l.subject === "Бизнес-планирование" && l.start === "11:45" && l.day === 4, ); assert.equal(plan.condition, "1-8,10-17"); assert.ok(s.warnings.length); }); test("ФЭФ: кураторский час в 16.50 не становится невозможной датой", () => { const l = fef().lessons.find( (l) => l.groups.includes("23-БА") && l.day === 1 && l.subject.includes("кураторский"), ); assert.equal(l.start, "16:50"); assert.equal(l.end, null); assert.equal(l.date, null); assert.equal(l.condition, ""); for (const lesson of fef().lessons) if (lesson.date) assert.ok(Number.isFinite(new Date(lesson.date).getTime())); }); test("ФЭФ: объединённое занятие не дублируется на зелёной строке", () => { const s = fef(); const l = s.lessons.filter( (l) => l.day === 2 && l.groups.includes("23-БА") && l.subject === "Управление проектами" && l.start === "15:20", ); assert.equal(l.length, 1); assert.equal(l[0].variant, "all"); }); test("Дневные таблицы четырёх факультетов распознаются со своими группами", () => { for (const [name, groups] of [ ["fit-daily", ["24-ИТ-1", "24-ИТ-2"]], ["fkne-daily", ["24-КБ", "24-КФ"]], ["isf-daily", ["26-ПГС-1", "26-ПГС-2"]], ["gf-daily", ["25-РГФ-1", "25-РГФ-2"]], ]) { const s = parseDocx(fixture(name), { name }); assert.deepEqual(s.groups, groups); assert.ok(s.lessons.length > 20); assert.ok( s.lessons.every( (l) => l.day >= 1 && l.day <= 7 && /^\d{2}:\d{2}$/.test(l.start), ), ); assert.ok( s.lessons.every((l) => l.groups.every((g) => groups.includes(g))), ); } }); test("Сессии: отдельные колонки времени, аудитории и преподавателя", () => { const s = parseDocx(fixture("fit"), { name: "26-ИТзс.docx" }), l = s.lessons[0]; assert.deepEqual(s.groups, ["26-ИТзс"]); assert.equal(l.date, "2026-09-17"); assert.equal(l.start, "11:00"); assert.equal(l.end, "11:30"); assert.equal(l.teacher, "Петрович О.Н."); assert.equal(l.room, "220Д"); assert.deepEqual(l.subgroups, []); assert.match(l.raw, /Петрович/); assert.equal(s.period, "Установочная сессия"); const f = parseDocx(fixture("fkne"), {}); assert.deepEqual(f.groups, ["26-ЭЭТзс"]); assert.ok(f.lessons.every((l) => l.date)); }); test("Импорт отклоняет повреждённые и посторонние файлы", async () => { assert.throws(() => parseDocx(Buffer.from("broken")), /повреждён/); await assert.rejects( () => parseWord(Buffer.from("not a word file")), /настоящий файл Word/, ); }); test("DAV: метаданные вложенных документов и только доверенный каталог", () => { const xml = `/public.php/dav/files/no5zKbMrns6j5jQ/2026-2027/HTTP/1.1 200 OKhttps://evil.example/file.docx`; const entries = decodeListing(xml); assert.equal(entries.length, 1); assert.equal(entries[0].folder, true); const metadata = JSON.parse( readFileSync(new URL("./fixtures/fef-23-ba.docx.json", import.meta.url)), ); const file = fileMetadata(metadata); assert.equal(file.faculty, "Финансово-экономический факультет"); assert.equal(file.course, "4 курс"); assert.equal(file.name, "23-ЛГ_23-БА.docx"); });