ci / verify (push) Canceled after 0s
Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
71 lines
4.9 KiB
JavaScript
71 lines
4.9 KiB
JavaScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { usageDateRange, usageMinutes, usageConversationContext, usageConversationURL } from "../src/components/usage-model.ts";
|
|
|
|
test("usage range begins at Beijing midnight independent of browser timezone", () => {
|
|
const previous = process.env.TZ;
|
|
try {
|
|
for (const zone of ["Etc/UTC", "America/Los_Angeles", "Asia/Tokyo"]) {
|
|
process.env.TZ = zone;
|
|
assert.deepEqual(usageDateRange(30, new Date("2026-10-05T16:30:00Z")), {
|
|
from: "2026-09-06T16:00:00.000Z", to: "2026-10-05T16:30:00.000Z", timezone: "Asia/Shanghai",
|
|
});
|
|
}
|
|
} finally {
|
|
if (previous === undefined) delete process.env.TZ; else process.env.TZ = previous;
|
|
}
|
|
});
|
|
|
|
test("usage range includes the current local day and handles year rollover", () => {
|
|
assert.equal(usageDateRange(1, new Date("2026-01-01T00:05:00+08:00")).from, "2025-12-31T16:00:00.000Z");
|
|
assert.equal(usageDateRange(7, new Date("2026-01-01T00:05:00+08:00")).from, "2025-12-25T16:00:00.000Z");
|
|
for (const days of [0, -1, 367, NaN, 1.5]) assert.throws(() => usageDateRange(days), RangeError);
|
|
});
|
|
|
|
test('30/90/365 days are inclusive Beijing calendar days across midnight, with an exact request-time end', () => {
|
|
const cases = [
|
|
['2026-10-05T15:59:59.999Z', {30:'2026-09-05T16:00:00.000Z',90:'2026-07-07T16:00:00.000Z',365:'2025-10-05T16:00:00.000Z'}],
|
|
['2026-10-05T16:00:00.000Z', {30:'2026-09-06T16:00:00.000Z',90:'2026-07-08T16:00:00.000Z',365:'2025-10-06T16:00:00.000Z'}],
|
|
['2026-10-05T16:00:00.001Z', {30:'2026-09-06T16:00:00.000Z',90:'2026-07-08T16:00:00.000Z',365:'2025-10-06T16:00:00.000Z'}],
|
|
];
|
|
for(const [at,starts] of cases) for(const days of [30,90,365]) {
|
|
assert.deepEqual(usageDateRange(days,new Date(at)), {from:starts[days],to:at,timezone:'Asia/Shanghai'});
|
|
}
|
|
assert.equal(usageDateRange(365,new Date('2028-03-01T00:00:00+08:00')).from,'2027-03-02T16:00:00.000Z',
|
|
'the year option means 365 inclusive days, rather than the same date twelve months earlier');
|
|
});
|
|
|
|
test("short completed chats do not display zero minutes", () => {
|
|
assert.equal(usageMinutes(0), "0");
|
|
assert.equal(usageMinutes(1), "<1");
|
|
assert.equal(usageMinutes(29000), "<1");
|
|
assert.equal(usageMinutes(60000), "1");
|
|
assert.equal(usageMinutes(98000), "1.6");
|
|
});
|
|
|
|
const roster = [{ id: 'a-first', household_id: 'a' }, { id: 'a-second', household_id: 'a' }, { id: 'b-first', household_id: 'b' }];
|
|
const fleet = [{ id: 'a-device', household_id: 'a', subject_id: 'a-second' }, { id: 'b-device', household_id: 'b', subject_id: 'b-first' }, { id: 'unassigned', household_id: 'a' }, { id: 'stale-owner', household_id: 'a', subject_id: 'b-first' }];
|
|
test('a selected device selects its verified elder even when another elder is already current', () => {
|
|
for (const current of [undefined, 'a-first', 'a-second']) assert.deepEqual(usageConversationContext('a', fleet, roster, current, 'a-device'), { householdID: 'a', deviceID: 'a-device', subjectID: 'a-second' });
|
|
});
|
|
test('all devices use the current valid elder or the existing family default', () => {
|
|
assert.equal(usageConversationContext('a', fleet, roster, 'a-second').subjectID, 'a-second');
|
|
assert.deepEqual(usageConversationContext('a', fleet, roster, 'b-first'), { householdID: 'a', deviceID: undefined, subjectID: 'a-first' });
|
|
});
|
|
test('all devices with a single verified owner select that elder instead of an unrelated default', () => {
|
|
const singleOwnerFleet = [{ id: 'one', household_id: 'a', subject_id: 'a-second' }, { id: 'two', household_id: 'a', subject_id: 'a-second' }, fleet[1]];
|
|
assert.equal(usageConversationContext('a', singleOwnerFleet, roster, 'a-first').subjectID, 'a-second');
|
|
assert.equal(usageConversationContext('a', [...singleOwnerFleet, fleet[2]], roster, 'a-first').subjectID, 'a-first', 'incomplete ownership uses the existing default');
|
|
});
|
|
test('foreign or removed devices and invalid owners never cross the household boundary', () => {
|
|
for (const device of ['b-device', 'removed']) assert.deepEqual(usageConversationContext('a', fleet, roster, 'b-first', device), { householdID: 'a', deviceID: undefined, subjectID: 'a-first' });
|
|
for (const device of ['unassigned', 'stale-owner']) assert.equal(usageConversationContext('a', fleet, roster, 'b-first', device).subjectID, 'a-first');
|
|
assert.equal(usageConversationContext('empty', fleet, roster, 'a-first', 'a-device').subjectID, undefined);
|
|
});
|
|
test('the all-records URL carries verified context without promising date or device filtering', () => {
|
|
const context = usageConversationContext('a', fleet, roster, 'a-first', 'a-device');
|
|
const url = new URL(usageConversationURL(context), 'https://synthetic.invalid');
|
|
assert.deepEqual(Object.fromEntries(url.searchParams), { section: 'conversations', household_id: 'a', usage_device_id: 'a-device', subject_id: 'a-second' });
|
|
assert(!url.searchParams.has('device_id') && !url.searchParams.has('from') && !url.searchParams.has('to'));
|
|
});
|