src.nth.io/

summaryrefslogtreecommitdiff
path: root/test/sync.test.ts
blob: d48f286991c7d6a44f6a95850a9f9ad0a284f0ea (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import { describe, expect, it } from "vitest";
import {
  MATTER_EPOCH_UNIX_SECONDS,
  compareDeviceClock,
  currentMatterUtcMicroseconds,
  formatDurationMicros,
  formatMatterMicros,
  matterMicrosToUnixMillis,
  unixMillisToMatterMicros,
} from "../src/sync.js";

describe("Matter epoch conversion", () => {
  it("maps 2000-01-01T00:00:00Z to 0 microseconds", () => {
    const unixMs = BigInt(Date.parse("2000-01-01T00:00:00Z"));
    expect(unixMillisToMatterMicros(unixMs)).toBe(0n);
  });

  it("maps 2000-01-01T00:00:01Z to 1,000,000 microseconds", () => {
    const unixMs = BigInt(Date.parse("2000-01-01T00:00:01Z"));
    expect(unixMillisToMatterMicros(unixMs)).toBe(1_000_000n);
  });

  it("keeps the Unix epoch conversion exact", () => {
    expect(unixMillisToMatterMicros(0n)).toBe(-MATTER_EPOCH_UNIX_SECONDS * 1_000_000n);
    expect(unixMillisToMatterMicros(0n)).toBe(-946_684_800_000_000n);
  });

  it("keeps current dates exact through bigint round-trips", () => {
    const unixMs = BigInt(Date.parse("2026-07-26T20:00:00.123Z"));
    const micros = unixMillisToMatterMicros(unixMs);
    expect(micros).toBe((unixMs - 946_684_800_000n) * 1_000n);
    expect(matterMicrosToUnixMillis(micros)).toBe(unixMs);
  });

  it("stays exact beyond JavaScript number precision", () => {
    // Microsecond timestamps pass Number.MAX_SAFE_INTEGER around year 2285;
    // conversion must not degrade there, proving no number arithmetic occurs.
    const unixMs = BigInt(Date.parse("2300-01-01T00:00:00.001Z"));
    const micros = unixMillisToMatterMicros(unixMs);
    expect(micros > BigInt(Number.MAX_SAFE_INTEGER)).toBe(true);
    expect(matterMicrosToUnixMillis(micros)).toBe(unixMs);
  });

  it("computes the current time via the injected clock", () => {
    const nowMs = Date.parse("2026-07-26T20:00:00Z");
    const micros = currentMatterUtcMicroseconds(() => nowMs);
    expect(micros).toBe(unixMillisToMatterMicros(BigInt(nowMs)));
  });

  it("formats Matter microseconds as an ISO instant", () => {
    expect(formatMatterMicros(0n)).toBe("2000-01-01T00:00:00.000Z");
    expect(formatMatterMicros(1_000_000n)).toBe("2000-01-01T00:00:01.000Z");
  });
});

describe("formatDurationMicros", () => {
  it("scales units with magnitude", () => {
    expect(formatDurationMicros(0n)).toBe("0us");
    expect(formatDurationMicros(999n)).toBe("999us");
    expect(formatDurationMicros(412_000n)).toBe("412ms");
    expect(formatDurationMicros(3_200_000n)).toBe("3.2s");
    expect(formatDurationMicros(59_000_000n)).toBe("59s");
    expect(formatDurationMicros(125_000_000n)).toBe("2m 5s");
    expect(formatDurationMicros(3_840_000_000n)).toBe("1h 4m");
    // 90,061 seconds = 1d 1h 1m 1s; seconds are dropped once days appear.
    expect(formatDurationMicros(90_061_000_000n)).toBe("1d 1h 1m");
  });

  it("rejects negative durations", () => {
    expect(() => formatDurationMicros(-1n)).toThrow();
  });
});

describe("compareDeviceClock", () => {
  // compareDeviceClock operates on Unix-epoch microseconds (matter.js convention).
  const host = BigInt(Date.parse("2026-07-26T20:00:00.000Z")) * 1_000n;

  it("reports an unset device clock", () => {
    const comparison = compareDeviceClock(null, host);
    expect(comparison.deviceTime).toBeNull();
    expect(comparison.deltaMicroseconds).toBeNull();
    expect(comparison.hostTime).toBe("2026-07-26T20:00:00.000Z");
    expect(comparison.description).toBe("device clock was unset");
  });

  it("reports a device clock behind the host", () => {
    const comparison = compareDeviceClock(host - 83_000_000n, host);
    expect(comparison.deviceTime).toBe("2026-07-26T19:58:37.000Z");
    expect(comparison.deltaMicroseconds).toBe(-83_000_000n);
    expect(comparison.description).toBe("device clock was 1m 23s behind");
  });

  it("reports a device clock ahead of the host", () => {
    const comparison = compareDeviceClock(host + 5_500_000n, host);
    expect(comparison.deltaMicroseconds).toBe(5_500_000n);
    expect(comparison.description).toBe("device clock was 5.5s ahead");
  });

  it("treats sub-second deltas as in sync but still reports them", () => {
    const comparison = compareDeviceClock(host - 412_000n, host);
    expect(comparison.description).toBe("device clock was within 1s of host time (412ms behind)");
  });

  it("detects a device that encodes Unix-epoch time on the wire (wrong epoch bug)", () => {
    // Such firmware decodes to exactly 946,684,800s ahead at the matter.js
    // API boundary. The raw delta stays exact; the effective delta folds
    // the shift out.
    const wrong = host + 946_684_800n * 1_000_000n + 1_400_000n;
    const comparison = compareDeviceClock(wrong, host);
    expect(comparison.epochShifted).toBe(true);
    expect(comparison.deltaMicroseconds).toBe(946_684_800_000_000n + 1_400_000n);
    expect(comparison.effectiveDeltaMicroseconds).toBe(1_400_000n);
    expect(comparison.deviceTime).toBe("2026-07-26T20:00:01.400Z");
    expect(comparison.description).toBe(
      "device encodes Unix-epoch time on the wire (off-spec); corrected, its clock was 1.4s ahead",
    );
  });

  it("does not fold out large errors that are not the epoch shift", () => {
    // 30 days past the shift window: a genuinely broken clock, not epoch bug.
    const wrong = host + 946_684_800n * 1_000_000n + 30n * 86_400n * 1_000_000n;
    const comparison = compareDeviceClock(wrong, host);
    expect(comparison.epochShifted).toBe(false);
    expect(comparison.effectiveDeltaMicroseconds).toBe(comparison.deltaMicroseconds);
    expect(comparison.description).toContain("ahead");
  });

  it("keeps spec-compliant comparisons unshifted", () => {
    const comparison = compareDeviceClock(host - 83_000_000n, host);
    expect(comparison.epochShifted).toBe(false);
    expect(comparison.effectiveDeltaMicroseconds).toBe(-83_000_000n);
  });
});