Source-only publication of reviewed commit 64e96a7f423673dc29ad56485be9fc79bce8af1c.
Passed in isolation: focused frontend/backend tests, race checks, TypeScript typecheck, lint, Go compilation, Flowgram build, and deterministic SDK build/tests.
Not accepted as a production release: Next.js export/native assembly remains blocked by environment memory limits; browser interaction and MySQL staging acceptance remain unverified. No production server, database/schema, runtime configuration, deployment, release tag, or workflow execution was performed.
Expected source tree: 0a4a7528e9.
135 lines
8.7 KiB
JavaScript
135 lines
8.7 KiB
JavaScript
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)
|
|
}
|
|
})
|