1import type { HostPowerSnapshot } from "@t3tools/contracts";
2import { describe, expect, it } from "@effect/vitest";
3import * as DateTime from "effect/DateTime";
4import * as Deferred from "effect/Deferred";
5import * as Effect from "effect/Effect";
6import * as Fiber from "effect/Fiber";
7import * as Ref from "effect/Ref";
8import * as Semaphore from "effect/Semaphore";
9
10import {
11 commitCollectionControlUpdate,
12 synchronizeCollectionControlOnStart,
13} from "./NativeTelemetryClient.ts";
14
15const basePower: HostPowerSnapshot = {
16 source: "electron-main",
17 idle: "false",
18 idleSeconds: 0,
19 locked: "false",
20 suspended: false,
21 onBattery: "false",
22 lowPowerMode: "false",
23 thermalState: "nominal",
24 stale: false,
25 updatedAt: DateTime.makeUnsafe("2026-06-17T12:00:00.000Z"),
26};
27
28describe("commitCollectionControlUpdate", () => {
29 it.effect("retains desired demand and retries unapplied sidecar state", () =>
30 Effect.gen(function* () {
31 const initial = {
32 hostPower: basePower,
33 liveSubscriberCount: 0,
34 sampleIntervalMs: 5_000,
35 };
36 const desired = yield* Ref.make(initial);
37 const applied = yield* Ref.make(initial);
38 const failure = new Error("sidecar write failed");
39 const receivedStates: Array<readonly [number, number]> = [];
40
41 const received = yield* commitCollectionControlUpdate(
42 desired,
43 applied,
44 (current) => ({
45 ...current,
46 liveSubscriberCount: 1,
47 sampleIntervalMs: 1_000,
48 }),
49 (previous, next) => {
50 receivedStates.push([previous.sampleIntervalMs, next.sampleIntervalMs]);
51 return Effect.fail(failure);
52 },
53 ).pipe(Effect.flip);
54
55 expect(received).toBe(failure);
56 expect(yield* Ref.get(desired)).toEqual({
57 ...initial,
58 liveSubscriberCount: 1,
59 sampleIntervalMs: 1_000,
60 });
61 expect(yield* Ref.get(applied)).toEqual(initial);
62
63 yield* commitCollectionControlUpdate(
64 desired,
65 applied,
66 (current) => current,
67 (previous, next) => {
68 receivedStates.push([previous.sampleIntervalMs, next.sampleIntervalMs]);
69 return Effect.void;
70 },
71 );
72 expect(receivedStates).toEqual([
73 [5_000, 1_000],
74 [5_000, 1_000],
75 ]);
76 expect(yield* Ref.get(applied)).toEqual(yield* Ref.get(desired));
77 }),
78 );
79
80 it.effect("serializes startup synchronization with runtime control updates", () =>
81 Effect.gen(function* () {
82 const initial = {
83 hostPower: basePower,
84 liveSubscriberCount: 0,
85 sampleIntervalMs: 5_000,
86 };
87 const desired = yield* Ref.make(initial);
88 const applied = yield* Ref.make(initial);
89 const ready = yield* Ref.make(false);
90 const mutex = yield* Semaphore.make(1);
91 const startupApplying = yield* Deferred.make<void>();
92 const releaseStartup = yield* Deferred.make<void>();
93 const appliedIntervals: Array<number> = [];
94
95 const startupFiber = yield* synchronizeCollectionControlOnStart(
96 mutex,
97 desired,
98 applied,
99 (control) =>
100 Effect.sync(() => {
101 appliedIntervals.push(control.sampleIntervalMs);
102 }).pipe(
103 Effect.andThen(Deferred.succeed(startupApplying, undefined)),
104 Effect.andThen(Deferred.await(releaseStartup)),
105 Effect.asVoid,
106 ),
107 Ref.set(ready, true),
108 ).pipe(Effect.forkChild);
109 yield* Deferred.await(startupApplying);
110
111 const updateFiber = yield* mutex
112 .withPermits(1)(
113 commitCollectionControlUpdate(
114 desired,
115 applied,
116 (current) => ({
117 ...current,
118 liveSubscriberCount: 1,
119 sampleIntervalMs: 1_000,
120 }),
121 (_previous, next) =>
122 Effect.gen(function* () {
123 expect(yield* Ref.get(ready)).toBe(true);
124 appliedIntervals.push(next.sampleIntervalMs);
125 }),
126 ),
127 )
128 .pipe(Effect.forkChild);
129 yield* Effect.yieldNow;
130 expect(yield* Ref.get(desired)).toEqual(initial);
131
132 yield* Deferred.succeed(releaseStartup, undefined);
133 yield* Fiber.join(startupFiber);
134 yield* Fiber.join(updateFiber);
135
136 expect(appliedIntervals).toEqual([5_000, 1_000]);
137 expect(yield* Ref.get(applied)).toEqual(yield* Ref.get(desired));
138 }),
139 );
140});