1import type { DesktopHostTelemetrySnapshot } from "@t3tools/contracts";
2import { describe, expect, it } from "@effect/vitest";
3import * as DateTime from "effect/DateTime";
4import * as Effect from "effect/Effect";
5import * as Fiber from "effect/Fiber";
6import * as Layer from "effect/Layer";
7import * as Option from "effect/Option";
8import * as PubSub from "effect/PubSub";
9import * as Ref from "effect/Ref";
10import * as Stream from "effect/Stream";
11
12import * as DesktopTelemetryReceiver from "../resourceTelemetry/DesktopTelemetryReceiver.ts";
13import * as HostPowerMonitor from "./HostPowerMonitor.ts";
14
15describe("HostPowerMonitor", () => {
16 it.effect("publishes semantic power changes without idle-time heartbeat churn", () =>
17 Effect.gen(function* () {
18 const monitor = yield* HostPowerMonitor.make();
19 const initial = {
20 source: "electron-main",
21 idle: "false",
22 idleSeconds: 0,
23 locked: "false",
24 suspended: false,
25 onBattery: "false",
26 lowPowerMode: "unknown",
27 thermalState: "nominal",
28 stale: false,
29 updatedAt: DateTime.makeUnsafe("2026-06-17T12:00:00.000Z"),
30 } as const;
31 yield* monitor.report(initial);
32
33 const nextChange = yield* Stream.runHead(monitor.streamChanges).pipe(Effect.forkChild);
34 yield* Effect.yieldNow;
35 yield* monitor.report({
36 ...initial,
37 idleSeconds: 1,
38 updatedAt: DateTime.makeUnsafe("2026-06-17T12:00:01.000Z"),
39 });
40 yield* monitor.report({
41 ...initial,
42 locked: "true",
43 updatedAt: DateTime.makeUnsafe("2026-06-17T12:00:02.000Z"),
44 });
45
46 expect(Option.getOrThrow(yield* Fiber.join(nextChange)).locked).toBe("true");
47 }),
48 );
49
50 it.effect("ignores host power reports older than the latest accepted snapshot", () =>
51 Effect.gen(function* () {
52 const initial = {
53 source: "electron-main",
54 idle: "false",
55 idleSeconds: 0,
56 locked: "false",
57 suspended: false,
58 onBattery: "false",
59 lowPowerMode: "false",
60 thermalState: "nominal",
61 stale: false,
62 updatedAt: DateTime.makeUnsafe("2026-06-17T12:00:00.000Z"),
63 } as const;
64 const monitor = yield* HostPowerMonitor.make(initial);
65 const latestAt = DateTime.makeUnsafe("2026-06-17T12:00:02.000Z");
66 yield* monitor.report({
67 ...initial,
68 locked: "true",
69 updatedAt: latestAt,
70 });
71 yield* monitor.report({
72 ...initial,
73 updatedAt: DateTime.makeUnsafe("2026-06-17T12:00:01.000Z"),
74 });
75
76 const snapshot = yield* monitor.snapshot;
77 expect(snapshot.locked).toBe("true");
78 expect(DateTime.toEpochMillis(snapshot.updatedAt)).toBe(DateTime.toEpochMillis(latestAt));
79 }),
80 );
81
82 it.effect("consumes desktop power directly without retaining diagnostics telemetry", () =>
83 Effect.gen(function* () {
84 const sampledAt = DateTime.makeUnsafe("2026-06-17T12:00:00.000Z");
85 const desktopChanges = yield* PubSub.sliding<DesktopHostTelemetrySnapshot>(1);
86 const diagnosticsDemandWrites = yield* Ref.make(0);
87 const layerReceiver = DesktopTelemetryReceiver.layerTest({
88 changes: Stream.fromPubSub(desktopChanges),
89 setDiagnosticsDemand: () => Ref.update(diagnosticsDemandWrites, (count) => count + 1),
90 });
91 const layer = HostPowerMonitor.layer.pipe(Layer.provide(layerReceiver));
92
93 yield* Effect.gen(function* () {
94 const monitor = yield* HostPowerMonitor.HostPowerMonitor;
95 const nextPower = yield* Stream.runHead(monitor.streamChanges).pipe(Effect.forkChild);
96 yield* Effect.yieldNow;
97 yield* PubSub.publish(desktopChanges, {
98 version: 1,
99 type: "desktopTelemetry",
100 sequence: 1,
101 sampledAtUnixMs: DateTime.toEpochMillis(sampledAt),
102 electronPid: 100,
103 power: {
104 source: "electron-main",
105 idle: "false",
106 idleSeconds: 0,
107 locked: "false",
108 suspended: false,
109 onBattery: "true",
110 lowPowerMode: "unknown",
111 thermalState: "nominal",
112 stale: false,
113 updatedAt: sampledAt,
114 },
115 speedLimitPercent: Option.none(),
116 electronProcesses: [],
117 });
118
119 expect(Option.getOrThrow(yield* Fiber.join(nextPower)).onBattery).toBe("true");
120 expect(yield* Ref.get(diagnosticsDemandWrites)).toBe(0);
121 }).pipe(Effect.provide(layer));
122 }),
123 );
124
125 it.effect(
126 "subscribes before reading the desktop snapshot so concurrent power updates survive",
127 () =>
128 Effect.gen(function* () {
129 const sampledAt = DateTime.makeUnsafe("2026-06-17T12:00:00.000Z");
130 const initial: DesktopHostTelemetrySnapshot = {
131 version: 1,
132 type: "desktopTelemetry",
133 sequence: 1,
134 sampledAtUnixMs: DateTime.toEpochMillis(sampledAt),
135 electronPid: 100,
136 power: {
137 source: "electron-main",
138 idle: "false",
139 idleSeconds: 0,
140 locked: "false",
141 suspended: false,
142 onBattery: "false",
143 lowPowerMode: "unknown",
144 thermalState: "nominal",
145 stale: false,
146 updatedAt: sampledAt,
147 },
148 speedLimitPercent: Option.none(),
149 electronProcesses: [],
150 };
151 const updated: DesktopHostTelemetrySnapshot = {
152 ...initial,
153 sequence: 2,
154 power: {
155 ...initial.power,
156 onBattery: "true",
157 updatedAt: DateTime.makeUnsafe("2026-06-17T12:00:01.000Z"),
158 },
159 };
160 const desktopChanges = yield* PubSub.sliding<DesktopHostTelemetrySnapshot>(1);
161 const layerReceiver = DesktopTelemetryReceiver.layerTest({
162 latest: Effect.succeedSome(initial),
163 changes: Stream.empty,
164 subscribe: Effect.gen(function* () {
165 const subscription = yield* PubSub.subscribe(desktopChanges);
166 yield* PubSub.publish(desktopChanges, updated);
167 return {
168 latest: Option.some(initial),
169 changes: Stream.fromSubscription(subscription),
170 };
171 }),
172 });
173 const layer = HostPowerMonitor.layer.pipe(Layer.provide(layerReceiver));
174
175 yield* Effect.gen(function* () {
176 const monitor = yield* HostPowerMonitor.HostPowerMonitor;
177 for (
178 let attempt = 0;
179 attempt < 100 && (yield* monitor.snapshot).onBattery !== "true";
180 attempt += 1
181 ) {
182 yield* Effect.yieldNow;
183 }
184 expect((yield* monitor.snapshot).onBattery).toBe("true");
185 }).pipe(Effect.provide(layer));
186 }),
187 );
188});