Files
psu-schedule/test/sources.test.js
2026-10-16 22:08:45 +03:00

184 lines
8.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
);
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-БА"));
});