import assert from "node:assert/strict" import { readFile } from "node:fs/promises" import test from "node:test" import vm from "node:vm" import ts from "typescript" async function load(path, imports = {}, extras = {}) { const source = await readFile(new URL(path, import.meta.url), "utf8") const compiled = ts.transpileModule(source, { compilerOptions: { target: ts.ScriptTarget.ES2020, module: ts.ModuleKind.CommonJS, jsx: ts.JsxEmit.ReactJSX } }) const loaded = { exports: {} } vm.runInNewContext(compiled.outputText, { exports: loaded.exports, module: loaded, require: name => { assert.ok(name in imports, `unexpected import ${name}`); return imports[name] }, Intl, Date, Set, AbortController, setTimeout, clearTimeout, process, ...extras }) return loaded.exports } const helpers = await load("./support-availability.ts") const base = { teamId: 5, mode: "recurring", timezone: "Europe/Moscow", effectiveTimezone: "Europe/Moscow", deploymentTimezone: "UTC", weekly: [], exceptions: [] } test("calendar accepts split, overnight and full-day midnight intervals", () => { for (const weekly of [ [{ weekday: 1, start: "09:00", end: "12:00" }, { weekday: 1, start: "13:00", end: "18:00" }], [{ weekday: 7, start: "22:00", end: "02:00" }], [{ weekday: 1, start: "00:00", end: "24:00" }], ]) assert.equal(helpers.calendarValidationKey({ ...base, weekly }), null) }) test("invalid IANA zones, offsets, equal intervals and invalid midnight starts fail", () => { for (const timezone of ["Mars/Olympus", "+03:00", "Local"]) assert.equal(helpers.calendarValidationKey({ ...base, timezone }), "supportAvailability.invalidTimezone") assert.equal(helpers.calendarValidationKey({ ...base, teamId: 0, timezone: "" }), "supportAvailability.invalidTimezone") assert.equal(helpers.calendarValidationKey({ ...base, timezone: "" }), null) for (const [start, end] of [["08:00", "08:00"], ["24:00", "08:00"], ["09:00", "24:01"]]) assert.equal(helpers.calendarValidationKey({ ...base, weekly: [{ weekday: 1, start, end }] }), "supportAvailability.invalidHours") }) test("invalid, duplicate and leap exception dates are checked", () => { for (const date of ["2026-02-29", "2026-04-31", "invalid"]) assert.equal(helpers.calendarValidationKey({ ...base, exceptions: [{ date, closed: true, periods: [] }] }), "supportAvailability.invalidExceptionDate") const leap = { date: "2028-02-29", closed: true, periods: [] } assert.equal(helpers.calendarValidationKey({ ...base, exceptions: [leap] }), null) assert.equal(helpers.calendarValidationKey({ ...base, exceptions: [leap, leap] }), "supportAvailability.invalidExceptionDate") }) test("closing a holiday normalizes outgoing periods without discarding the editing draft", () => { const calendar = { ...base, exceptions: [{ date: "2026-12-25", closed: true, periods: [{ start: "00:00", end: "24:00" }] }] } const payload = helpers.supportCalendarPayload(calendar) assert.equal(payload.exceptions[0].periods.length, 0) assert.equal(calendar.exceptions[0].periods.length, 1) calendar.exceptions[0].closed = false assert.equal(helpers.supportCalendarPayload(calendar).exceptions[0].periods[0].end, "24:00") }) test("vacation validates absolute interval and rejects DST gap without shifting", () => { const previous = process.env.TZ process.env.TZ = "America/New_York" try { assert.equal(helpers.localDateTimeToISO("2026-03-08T02:30"), null) assert.equal(helpers.localDateTimeToISO("2026-03-08T03:30"), "2026-03-08T07:30:00.000Z") assert.equal(helpers.vacationValidationKey("2026-10-16T10:00", "2026-10-16T09:00"), "supportAvailability.invalidVacation") assert.equal(helpers.vacationValidationKey("2026-10-16T10:00", "2026-10-16T11:00"), null) } finally { process.env.TZ = previous } }) test("DST overlap exposes both exact instants and preserves existing later occurrence", () => { const previous = process.env.TZ process.env.TZ = "America/New_York" try { const values = Array.from(helpers.localDateTimeCandidates("2026-11-01T01:30")) assert.deepEqual(values, ["2026-11-01T05:30:00.000Z", "2026-11-01T06:30:00.000Z"]) assert.equal(helpers.selectedLocalDateTimeISO("2026-11-01T01:30", values[1]), values[1]) assert.equal(helpers.selectedLocalDateTimeISO("2026-11-01T01:30", "stale"), values[0]) } finally { process.env.TZ = previous } }) test("preview renders calendar zone and explicitly uses UTC for opaque legacy process zones", () => { assert.match(helpers.formatAvailabilityTime("2026-10-16T12:00:00Z", "Europe/Moscow", "en-US"), /03:00|15:00/) assert.match(helpers.formatAvailabilityTime("2026-10-16T12:00:00Z", "Local", "en-US"), /UTC/) }) async function apiWith(request, extras = {}) { return load("./api/support-availability.ts", { "@/lib/api/client": { request }, "@/lib/support-availability": helpers, "@/lib/sdk/runtime-config": { readSupportChatRuntimeConfig: () => ({ channelId: "channel-a", baseUrl: "https://support.example/", userToken: "must-not-send" }) }, }, extras) } test("public status is channel-scoped, anonymous and uses shared request without private credentials", async () => { let call const api = await apiWith(async (url, options) => { call = { url, options }; return { available: false, reason: "temporarily_absent", timezone: "UTC" } }) const response = await api.fetchPublicSupportStatus() assert.equal(call.url, "/api/support/status") assert.equal(call.options.skipAuth, true) assert.equal(call.options.baseUrl, "https://support.example") assert.equal(call.options.headers["X-Channel-Id"], "channel-a") assert.equal("Authorization" in call.options.headers, false) assert.equal(response.available, false) }) test("assignment candidates use captured conversation and trust server eligibility, not idle alone", async () => { let path const api = await apiWith(async url => { path = url; return [{ online: false, serviceStatus: 0, eligible: false, reason: "offline" }] }) const items = await api.fetchAvailabilityAgents(19) assert.equal(path, "/api/dashboard/support_availability/agents?conversationId=19") assert.equal(items[0].eligible, false) }) test("calendar save and preview normalize closed periods identically and retain explicit mode", async () => { const calls = [] const api = await apiWith(async (url, options) => { calls.push({ url, body: JSON.parse(options.body) }); return base }) const draft = { ...base, mode: "legacy", exceptions: [{ date: "2026-12-25", closed: true, periods: [{ start: "09:00", end: "17:00" }] }] } await api.updateSupportCalendar(draft) await api.previewSupportCalendar(draft, "2026-12-25T12:00:00Z") assert.equal(calls[0].body.exceptions[0].periods.length, 0) assert.equal(calls[0].body.mode, "legacy") assert.equal(calls[1].body.calendar.exceptions[0].periods.length, 0) assert.equal(calls[1].body.at, "2026-12-25T12:00:00Z") assert.equal(draft.exceptions[0].periods.length, 1) }) test("a hung availability read has a bounded deadline and cleans up timers", async () => { let timeout, cleared = false, signal const api = await apiWith(async (_url, options) => new Promise((_, reject) => { signal = options.signal signal.addEventListener("abort", () => reject(new Error("aborted"))) }), { setTimeout(fn, delay) { assert.equal(delay, 10000); timeout = fn; return 1 }, clearTimeout(id) { assert.equal(id, 1); cleared = true } }) const pending = api.fetchMyAvailability() timeout() await assert.rejects(pending, /aborted/) assert.equal(signal.aborted, true) assert.equal(cleared, true) }) test("unmount/account-scope cancellation aborts an in-flight availability read", async () => { const controller = new AbortController() const api = await apiWith(async (_url, options) => new Promise((_, reject) => options.signal.addEventListener("abort", () => reject(new Error("aborted"))))) const pending = api.fetchAvailabilityAgents(undefined, controller.signal) controller.abort() await assert.rejects(pending, /aborted/) }) test("all generated manual statuses and blocking reasons have complete translations", async () => { const enums = await load("./generated/enums.ts") const mapping = await load("./support-availability-i18n.ts", { "@/lib/generated/enums": enums }) for (const locale of ["en-US", "ru-RU", "zh-CN"]) { const catalog = JSON.parse(await readFile(new URL(`../messages/${locale}.json`, import.meta.url), "utf8")) const t = key => { const value = key.split(".").reduce((value, part) => value?.[part], catalog); assert.equal(typeof value, "string", `${locale}: ${key}`); return value } assert.equal(mapping.availabilityStatusOptions(t).length, 5) for (const reason of ["eligible", "profile_disabled", "user_disabled", "team_disabled", "outside_hours", "offline", "busy", "break", "unavailable", "vacation", "auto_assign_disabled", "capacity"]) mapping.availabilityReasonLabel(reason, t) } })