import { readFileSync } from "node:fs"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; import type { TimeSyncSummary } from "../src/inspection.js"; import { compareDeviceClock } from "../src/sync.js"; import { capabilitiesFromTimeSync, planTimeSync } from "../src/timesync.js"; /** * Tests driven by the captured `inspect --json` output of the real device * (IKEA ALPSTUGA air quality monitor, firmware 1.0.26), per the plan: the * sync behavior is derived from inspected capabilities, not assumptions. */ const fixture = JSON.parse(readFileSync(join(__dirname, "fixtures", "alpstuga-inspect.json"), "utf8")) as { basicInformation: { vendorName: string; productName: string }; timeSynchronization: TimeSyncSummary; }; describe("alpstuga fixture", () => { it("is the expected device", () => { expect(fixture.basicInformation.vendorName).toBe("IKEA of Sweden"); expect(fixture.timeSynchronization).not.toBeNull(); }); it("derives capabilities from the inspection", () => { const capabilities = capabilitiesFromTimeSync(fixture.timeSynchronization); expect(capabilities).toEqual({ timeZoneFeature: true, supportsSetUtcTime: true, supportsSetTimeZone: true, supportsSetDstOffset: true, timeZoneListMaxSize: 2, dstOffsetListMaxSize: 2, }); }); it("plans UTC + time zone + a 2-entry DST list for this device", () => { const capabilities = capabilitiesFromTimeSync(fixture.timeSynchronization); const plan = planTimeSync(capabilities, "America/Chicago", new Date("2026-07-26T00:00:00Z")); expect(plan.setUtcTime).toBe(true); expect(plan.timeZone).toEqual([ { offset: -21600, validAt: 946_684_800_000_000n, name: "America/Chicago" }, ]); expect(plan.dstOffsets).toHaveLength(2); expect(plan.dstOffsets![0]!.offset).toBe(3600); expect(plan.dstOffsets![1]!.offset).toBe(0); }); it("reads the fixture's utcTime as healthy Unix-epoch microseconds", () => { // matter.js's TlvEpochUs decodes wire values to Unix-epoch microseconds, // so the fixture's utcTime (read at commissioning, 2026-07-27T00:18:15Z // wall clock) compares cleanly against that instant with no epoch shift. const attribute = fixture.timeSynchronization.attributes.find(a => a.name === "utcTime"); const deviceMicros = BigInt(attribute!.value as string); const captureInstant = BigInt(Date.parse("2026-07-27T00:18:20Z")) * 1_000n; const comparison = compareDeviceClock(deviceMicros, captureInstant); expect(comparison.epochShifted).toBe(false); expect(comparison.deviceTime).toBe("2026-07-27T00:18:15.000Z"); const effective = comparison.effectiveDeltaMicroseconds!; const magnitude = effective < 0n ? -effective : effective; expect(magnitude < 60n * 1_000_000n).toBe(true); }); it("degrades to UTC-only when the TimeZone feature is absent", () => { const utcOnly: TimeSyncSummary = { ...fixture.timeSynchronization, supportedFeatures: { ...fixture.timeSynchronization.supportedFeatures, timeZone: false }, supportedCommands: { ...fixture.timeSynchronization.supportedCommands, setTimeZone: false, setDstOffset: false, }, }; const plan = planTimeSync(capabilitiesFromTimeSync(utcOnly), "America/Chicago"); expect(plan.setUtcTime).toBe(true); expect(plan.timeZone).toBeNull(); expect(plan.dstOffsets).toBeNull(); }); });