mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 22:25:24 +00:00
131 lines
4.1 KiB
TypeScript
131 lines
4.1 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
advanceMaintenance,
|
|
beginMaintenance,
|
|
nextOccurrence,
|
|
parsePlan,
|
|
plansOverlap,
|
|
} from "../../src/backend/maintenance-time.js";
|
|
import {
|
|
emptyMaintenance,
|
|
type MaintenancePlan,
|
|
} from "../../src/maintenance.js";
|
|
|
|
const at = (value: string) => Date.parse(value);
|
|
const plan = (overrides: Partial<MaintenancePlan> = {}): MaintenancePlan => ({
|
|
id: "one",
|
|
reason: "Upgrade",
|
|
start: "2026-01-31T21:00:00.000Z",
|
|
nextStart: "2026-01-31T21:00:00.000Z",
|
|
durationMinutes: 60,
|
|
recurrence: "once",
|
|
graceMinutes: 20,
|
|
notifyOverdue: true,
|
|
...overrides,
|
|
});
|
|
|
|
describe("maintenance calendar", () => {
|
|
it("keeps weekly UTC time across DST and skips missing monthly days", () => {
|
|
expect(
|
|
new Date(
|
|
nextOccurrence(
|
|
plan({ recurrence: "weekly" }),
|
|
at("2026-03-28T21:00Z"),
|
|
)!,
|
|
).toISOString(),
|
|
).toBe("2026-04-04T21:00:00.000Z");
|
|
expect(
|
|
new Date(
|
|
nextOccurrence(
|
|
plan({ recurrence: "monthly" }),
|
|
at("2026-01-31T21:00Z"),
|
|
)!,
|
|
).toISOString(),
|
|
).toBe("2026-03-31T21:00:00.000Z");
|
|
expect(nextOccurrence(plan(), at("2026-01-31T21:00Z"))).toBeNull();
|
|
});
|
|
it("rejects actual overlaps and accepts touching windows", () => {
|
|
expect(plansOverlap(plan(), plan({ start: "2026-01-31T21:59:00Z" }))).toBe(
|
|
true,
|
|
);
|
|
expect(plansOverlap(plan(), plan({ start: "2026-01-31T22:00:00Z" }))).toBe(
|
|
false,
|
|
);
|
|
expect(
|
|
plansOverlap(
|
|
plan({ recurrence: "monthly" }),
|
|
plan({ start: "2030-03-31T21:30:00Z" }),
|
|
),
|
|
).toBe(true);
|
|
expect(
|
|
plansOverlap(
|
|
plan({ recurrence: "weekly" }),
|
|
plan({ recurrence: "monthly", start: "2026-03-31T21:30:00Z" }),
|
|
),
|
|
).toBe(true);
|
|
expect(
|
|
plansOverlap(
|
|
plan({ recurrence: "weekly" }),
|
|
plan({ recurrence: "monthly", start: "2026-03-31T23:00:00Z" }),
|
|
),
|
|
).toBe(false);
|
|
});
|
|
it("does not end at ETM and merges a later nonoverlapping schedule", () => {
|
|
const state = emptyMaintenance();
|
|
state.plans = [
|
|
plan(),
|
|
plan({
|
|
id: "two",
|
|
reason: "Firmware",
|
|
start: "2026-01-31T22:30:00Z",
|
|
nextStart: "2026-01-31T22:30:00Z",
|
|
}),
|
|
];
|
|
advanceMaintenance(state, at("2026-01-31T22:15Z"));
|
|
expect(state.active?.estimatedEnd).toBe("2026-01-31T22:00:00.000Z");
|
|
advanceMaintenance(state, at("2026-01-31T22:45Z"));
|
|
expect(state.active?.startedAt).toBe("2026-01-31T21:00:00.000Z");
|
|
expect(state.active?.estimatedEnd).toBe("2026-01-31T23:30:00.000Z");
|
|
expect(state.active?.reasons).toEqual(["Upgrade", "Firmware"]);
|
|
advanceMaintenance(state, at("2026-02-02T00:00Z"));
|
|
expect(state.active).not.toBeNull();
|
|
});
|
|
it("catches up after restart and never re-enters a consumed one-time plan", () => {
|
|
const state = emptyMaintenance();
|
|
state.plans = [plan({ recurrence: "monthly" })];
|
|
advanceMaintenance(state, at("2026-05-01T00:00Z"));
|
|
expect(state.active?.startedAt).toBe("2026-03-31T21:00:00.000Z");
|
|
expect(state.plans[0].nextStart).toBe("2026-05-31T21:00:00.000Z");
|
|
state.active = null;
|
|
advanceMaintenance(state, at("2026-05-01T00:00Z"));
|
|
expect(state.active).toBeNull();
|
|
});
|
|
it("preserves a delivered reminder unless a later estimate extends maintenance", () => {
|
|
const state = emptyMaintenance();
|
|
beginMaintenance(state, plan(), at("2026-01-31T21:00Z"));
|
|
state.active!.notified = true;
|
|
beginMaintenance(
|
|
state,
|
|
plan({ durationMinutes: 10 }),
|
|
at("2026-01-31T21:05Z"),
|
|
);
|
|
expect(state.active!.notified).toBe(true);
|
|
beginMaintenance(state, plan(), at("2026-01-31T22:30Z"));
|
|
expect(state.active!.notified).toBe(false);
|
|
});
|
|
it.each([
|
|
{ reason: " " },
|
|
{ durationMinutes: 0 },
|
|
{ durationMinutes: 10081 },
|
|
{ graceMinutes: -1 },
|
|
{ recurrence: "daily" },
|
|
{ notifyOverdue: "true" },
|
|
{ start: "2020-01-01T00:00Z" },
|
|
{ start: "2026-01-31T21:00:01Z" },
|
|
])("rejects invalid input %j", (override) => {
|
|
expect(() =>
|
|
parsePlan(plan(override as never), at("2026-01-01T00:00Z")),
|
|
).toThrow();
|
|
});
|
|
});
|