190 lines
9.3 KiB
JavaScript
190 lines
9.3 KiB
JavaScript
import test from "node:test";
|
||
import assert from "node:assert/strict";
|
||
import { readFileSync } from "node:fs";
|
||
import {
|
||
pickYearFolder,
|
||
pickReferences,
|
||
isWeeksTable,
|
||
isBellSchedule,
|
||
mergeWeekTables,
|
||
fileMetadata,
|
||
decodeListing,
|
||
} from "../server/source.js";
|
||
import { parseWeeksDocx } from "../server/calendar.js";
|
||
import { parseDocx } from "../server/parser.js";
|
||
import {
|
||
addDays,
|
||
semesterFor,
|
||
semesterView,
|
||
lessonDates,
|
||
academicStartYear,
|
||
} from "../shared/weeks.js";
|
||
import { scheduleUpdate, namesGroup } from "../shared/updates.js";
|
||
|
||
const fixture = (name) =>
|
||
readFileSync(new URL(`./fixtures/${name}.docx`, import.meta.url));
|
||
const autumn = parseWeeksDocx(fixture("weeks"));
|
||
// A spring table in the same format: numbering restarts at 1 on Monday 8 February.
|
||
const spring = Array.from({ length: 17 }, (_, i) => ({
|
||
n: i + 1,
|
||
start: addDays("2027-02-08", i * 7),
|
||
end: addDays("2027-02-08", i * 7 + 6),
|
||
color: i % 2 ? "green" : "white",
|
||
}));
|
||
const bothSemesters = { ...mergeWeekTables([spring, autumn]), bells: [], source: {} };
|
||
const SHARE = "/public.php/dav/files/no5zKbMrns6j5jQ/";
|
||
|
||
test("Учебный год: папка меняется 1 сентября, без новой папки остаётся прошлая", () => {
|
||
const folders = ["2025-2026", "2026-2027", "Майноры", "2026-2028"];
|
||
assert.equal(academicStartYear("2026-08-31"), 2025);
|
||
assert.equal(pickYearFolder(folders, "2026-08-31").name, "2025-2026");
|
||
assert.deepEqual(pickYearFolder(folders, "2026-09-01"), {
|
||
name: "2026-2027",
|
||
label: "2026–2027",
|
||
current: true,
|
||
expected: "2026-2027",
|
||
});
|
||
const late = pickYearFolder(folders, "2027-09-01");
|
||
assert.equal(late.name, "2026-2027");
|
||
assert.equal(late.current, false);
|
||
assert.equal(late.expected, "2027-2028");
|
||
// Only next year's folder published early: still better than nothing.
|
||
assert.equal(pickYearFolder(["2027-2028"], "2027-08-20").name, "2027-2028");
|
||
assert.equal(pickYearFolder(["Майноры"], "2026-10-16"), null);
|
||
});
|
||
|
||
test("Справочные файлы находятся по смыслу имени, папка года важнее корня", () => {
|
||
assert.ok(isWeeksTable("Расписание белых и зеленых недель.docx"));
|
||
assert.ok(isWeeksTable("Расписание белых и зелёных недель 2027.docx"));
|
||
assert.ok(!isWeeksTable("Расписание белых и зеленых недель.doc"));
|
||
assert.ok(!isWeeksTable("23-ЛГ_23-БА.docx"));
|
||
assert.ok(isBellSchedule("Расписание звонков (новое).docx"));
|
||
const entry = (path, modified, folder = false) => ({ path, modified, folder, size: 1 });
|
||
const refs = pickReferences(
|
||
[
|
||
entry(SHARE + encodeURIComponent("Расписание белых и зеленых недель.docx"), "Wed, 26 Aug 2026 10:00:00 GMT"),
|
||
entry(SHARE + encodeURIComponent("Расписание звонков.docx"), "Wed, 12 Feb 2025 07:55:59 GMT"),
|
||
entry(SHARE + "2026-2027/", null, true),
|
||
],
|
||
[
|
||
entry(SHARE + "2026-2027/" + encodeURIComponent("Белые и зелёные недели, весна.docx"), "Mon, 25 Jan 2027 09:00:00 GMT"),
|
||
],
|
||
);
|
||
assert.deepEqual(
|
||
refs.weeks.map((f) => [f.name, f.scope]),
|
||
[
|
||
["Белые и зелёные недели, весна.docx", "year"],
|
||
["Расписание белых и зеленых недель.docx", "root"],
|
||
],
|
||
);
|
||
assert.equal(refs.bells.name, "Расписание звонков.docx");
|
||
});
|
||
|
||
test("DAV: год берётся из пути документа, чужие ссылки отбрасываются", () => {
|
||
const xml = `<d:multistatus xmlns:d="DAV:"><d:response><d:href>${SHARE}2027-2028/</d:href><d:propstat><d:prop><d:resourcetype><d:collection/></d:resourcetype></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response><d:response><d:href>https://evil.example/file.docx</d:href></d:response></d:multistatus>`;
|
||
assert.equal(decodeListing(xml).length, 1);
|
||
const metadata = JSON.parse(
|
||
readFileSync(new URL("./fixtures/fef-23-ba.docx.json", import.meta.url)),
|
||
);
|
||
const file = fileMetadata(metadata);
|
||
assert.equal(file.year, "2026–2027");
|
||
assert.equal(file.faculty, "Финансово-экономический факультет");
|
||
});
|
||
|
||
test("Таблица с двумя семестрами делится по сбросу нумерации", () => {
|
||
const { semesters, weeks } = bothSemesters;
|
||
assert.deepEqual(
|
||
semesters.map((s) => [s.id, s.label, s.start, s.end]),
|
||
[
|
||
["2026-autumn", "Осенний семестр 2026/27", "2026-09-01", "2027-01-03"],
|
||
["2026-spring", "Весенний семестр 2026/27", "2027-02-08", "2027-06-06"],
|
||
],
|
||
);
|
||
assert.equal(weeks.filter((w) => w.semester === "2026-spring").length, 17);
|
||
// The same table read twice (root and year folder) does not duplicate weeks.
|
||
assert.equal(mergeWeekTables([autumn, autumn]).weeks.length, 18);
|
||
});
|
||
|
||
test("Расписание получает недели своего семестра, а не первого попавшегося", () => {
|
||
const fef = parseDocx(fixture("fef-23-ba"), { name: "23-ЛГ_23-БА.docx" });
|
||
assert.equal(semesterFor(bothSemesters, fef, "2027-03-01").id, "2026-autumn");
|
||
const springSchedule = { ...fef, period: "Весенний семестр" };
|
||
const view = semesterView(bothSemesters, springSchedule, "2026-10-16");
|
||
assert.equal(view.semester.id, "2026-spring");
|
||
const lesson = { day: 1, date: null, condition: "1-15н", variant: "upper" };
|
||
assert.equal(lessonDates(lesson, view)[0], "2027-02-08");
|
||
assert.equal(lessonDates(lesson, semesterView(bothSemesters, fef, "2026-10-16"))[0], "2026-09-14");
|
||
// Spring table not published yet: no guessed dates.
|
||
const onlyAutumn = { ...mergeWeekTables([autumn]), bells: [], source: {} };
|
||
assert.equal(semesterFor(onlyAutumn, springSchedule, "2027-01-20"), null);
|
||
// Next year's autumn schedule against this year's table: no match either.
|
||
assert.equal(semesterFor(onlyAutumn, { ...fef, academicYear: "2027" }, "2027-09-10"), null);
|
||
// Unknown period: the semester that contains today, none between semesters.
|
||
const plain = { lessons: [{ date: null }], period: "Расписание из Word" };
|
||
assert.equal(semesterFor(bothSemesters, plain, "2026-11-02").id, "2026-autumn");
|
||
assert.equal(semesterFor(bothSemesters, plain, "2027-01-20"), null);
|
||
// A session schedule follows its own dates.
|
||
const session = { lessons: [{ date: "2027-02-10" }, { date: "2027-02-11" }], period: "Установочная сессия" };
|
||
assert.equal(semesterFor(bothSemesters, session, "2026-12-01").id, "2026-spring");
|
||
// A calendar cached before semesters existed still works.
|
||
assert.equal(
|
||
semesterView({ weeks: autumn, bells: [], source: {} }, fef, "2026-10-16").weeks.length,
|
||
18,
|
||
);
|
||
});
|
||
|
||
test("Обновления: тот же файл перечитывается, новый документ группы предлагается", () => {
|
||
const saved = {
|
||
schemaVersion: 5,
|
||
source: {
|
||
fileId: "a",
|
||
modified: "Mon, 12 Oct 2026 11:45:13 GMT",
|
||
importedAt: "2026-10-16T10:00:00Z",
|
||
faculty: "ФЭФ",
|
||
},
|
||
};
|
||
const file = (id, name, modified, faculty = "ФЭФ") => ({ id, name, modified, faculty });
|
||
const base = file("a", "23-ЛГ_23-БА.docx", saved.source.modified);
|
||
const options = { schemaVersion: 5 };
|
||
assert.equal(scheduleUpdate(saved, { files: [base] }, "23-БА", options), null);
|
||
const republished = { ...base, modified: "Tue, 20 Oct 2026 08:00:00 GMT" };
|
||
assert.equal(
|
||
scheduleUpdate(saved, { files: [republished] }, "23-БА", options).changed,
|
||
republished,
|
||
);
|
||
assert.equal(
|
||
scheduleUpdate({ ...saved, schemaVersion: 4 }, { files: [base] }, "23-БА", options).changed,
|
||
base,
|
||
);
|
||
// Without options the server's parser version from the catalog decides; an old server
|
||
// without it never triggers a re-read.
|
||
const parsed11 = { ...saved, schemaVersion: 11 };
|
||
assert.equal(scheduleUpdate(parsed11, { files: [base], parserVersion: 12 }, "23-БА").changed, base);
|
||
assert.equal(scheduleUpdate(parsed11, { files: [base], parserVersion: 11 }, "23-БА"), null);
|
||
assert.equal(scheduleUpdate(parsed11, { files: [base] }, "23-БА"), null);
|
||
const spring = file("b", "23-БА весна.docx", "Mon, 25 Jan 2027 09:00:00 GMT");
|
||
const others = [
|
||
file("c", "23-БАз.docx", "Mon, 25 Jan 2027 09:00:00 GMT"),
|
||
file("d", "23-БА.docx", "Mon, 25 Jan 2027 09:00:00 GMT", "ФИТ"),
|
||
file("e", "23-БА старое.docx", "Mon, 01 Sep 2026 09:00:00 GMT"),
|
||
];
|
||
const found = scheduleUpdate(saved, { files: [base, spring, ...others] }, "23-БА", options);
|
||
assert.equal(found.newer, spring);
|
||
assert.equal(found.changed, null);
|
||
assert.equal(
|
||
scheduleUpdate(saved, { files: [base, spring] }, "23-БА", { ...options, dismissed: ["b"] }),
|
||
null,
|
||
);
|
||
// New academic year: the old path is gone.
|
||
assert.deepEqual(scheduleUpdate(saved, { files: [] }, "23-БА", options), {
|
||
changed: null,
|
||
newer: null,
|
||
missing: true,
|
||
});
|
||
// An offline copy of the catalog proves nothing.
|
||
assert.equal(scheduleUpdate(saved, { files: [], stale: true }, "23-БА", options), null);
|
||
assert.ok(namesGroup("23-ЛГ_23-БА.docx", "23-БА"));
|
||
assert.ok(!namesGroup("123-БА.docx", "23-БА"));
|
||
assert.ok(!namesGroup("23-БАз.docx", "23-БА"));
|
||
});
|