Skip to content
Source
Sign inSign up

resourceTelemetryState.browser.test.tsx

268 lines9.8 KB

1import { RegistryContext } from "@effect/atom-react";
2import {
3 EnvironmentId,
4 type ResourceTelemetryHistory,
5 type ResourceTelemetrySnapshot,
6} from "@t3tools/contracts";
7import * as Cause from "effect/Cause";
8import * as DateTime from "effect/DateTime";
9import * as Schema from "effect/Schema";
10import * as Effect from "effect/Effect";
11import * as Option from "effect/Option";
12import { AsyncResult, Atom, AtomRegistry } from "effect/reactivity";
13import type { ReactNode } from "react";
14import { describe, expect, it, vi } from "vite-plus/test";
15​
16import { act, render } from "../../test/render";
17​
18const monitor = vi.hoisted(() => ({
19 telemetry:
20 vi.fn<
21 (args: {
22 environmentId: EnvironmentId;
23 }) => Atom.Atom<AsyncResult.AsyncResult<ResourceTelemetrySnapshot, MonitorError>>
24 >(),
25 history:
26 vi.fn<
27 (args: {
28 environmentId: EnvironmentId;
29 input: { windowMs: number; bucketMs: number };
30 }) => Atom.Atom<AsyncResult.AsyncResult<ResourceTelemetryHistory, MonitorError>>
31 >(),
32 retry: vi.fn(),
33}));
34​
35vi.mock("../components/settings/settingsLayout", () => ({
36 SettingsSection: ({
37 title,
38 headerAction,
39 children,
40 }: {
41 title: string;
42 headerAction?: ReactNode;
43 children: ReactNode;
44 }) => (
45 <section aria-label={title}>
46 {headerAction}
47 {children}
48 </section>
49 ),
50}));
51​
52vi.mock("../state/environments", () => ({ usePrimaryEnvironment: () => null }));
53vi.mock("../state/session", () => ({
54 readEnvironmentScope: () => true,
55 useEnvironmentScope: () => true,
56}));
57vi.mock("../state/server", () => ({
58 serverEnvironment: {
59 resourceTelemetry: monitor.telemetry,
60 resourceTelemetryHistory: monitor.history,
61 },
62}));
63vi.mock("../state/use-atom-command", () => ({ useAtomCommand: () => monitor.retry }));
64​
65import { ResourceTelemetryDiagnostics } from "../components/settings/ResourceTelemetryDiagnostics";
66​
67class MonitorError extends Schema.TaggedError<MonitorError>()("MonitorError", {
68 message: Schema.String,
69}) {}
70​
71const environmentId = EnvironmentId.make("remote");
72const startError = "The resource monitor is not available.";
73const historyError = "Resource history could not start.";
74​
75function setup({ retryFails = false, holdRetry = false } = {}) {
76 const readAt = DateTime.makeUnsafe(0);
77 const aggregate = {
78 processCount: 0,
79 currentCpuPercent: 0,
80 cpuTimeMs: 0,
81 currentRssBytes: 0,
82 peakRssBytes: 0,
83 ioReadBytes: 0,
84 ioWriteBytes: 0,
85 ioReadBytesPerSecond: 0,
86 ioWriteBytesPerSecond: 0,
87 processStarts: 0,
88 processExits: 0,
89 };
90 const source = {
91 status: "healthy" as const,
92 lastSampleAt: Option.some(readAt),
93 lastError: Option.none<string>(),
94 };
95 const snapshot: ResourceTelemetrySnapshot = {
96 readAt,
97 sampleIntervalMs: 1000,
98 processes: [],
99 groups: { backend: aggregate, electron: aggregate, monitor: aggregate, allT3: aggregate },
100 power: {
101 source: "unknown",
102 idle: "unknown",
103 idleSeconds: null,
104 locked: "unknown",
105 suspended: false,
106 onBattery: "unknown",
107 lowPowerMode: "unknown",
108 thermalState: "unknown",
109 stale: false,
110 updatedAt: readAt,
111 },
112 speedLimitPercent: Option.none(),
113 attribution: { readAt, entries: [] },
114 health: {
115 native: source,
116 desktop: source,
117 sidecarVersion: Option.none(),
118 sidecarPid: Option.none(),
119 restartCount: 1,
120 collectionDurationMicros: 0,
121 scannedProcessCount: 0,
122 retainedProcessCount: 0,
123 inaccessibleProcessCount: 0,
124 },
125 };
126 const history: ResourceTelemetryHistory = {
127 readAt,
128 windowMs: 900000,
129 bucketMs: 30000,
130 sampleIntervalMs: 1000,
131 retainedSampleCount: 0,
132 buckets: [],
133 topProcesses: [],
134 health: snapshot.health,
135 };
136 let running = false;
137 const telemetryQueries = Atom.family((_environmentId: EnvironmentId) =>
138 Atom.make(
139 Effect.suspend(() =>
140 running ? Effect.succeed(snapshot) : Effect.fail(new MonitorError({ message: startError })),
141 ),
142 ).pipe(Atom.keepAlive),
143 );
144 monitor.telemetry.mockReset();
145 monitor.telemetry.mockImplementation(({ environmentId }) => telemetryQueries(environmentId));
146 const historyReads = vi.fn<(key: string) => void>();
147 const historyQueries = Atom.family((key: string) =>
148 Atom.make(
149 Effect.suspend(() => {
150 historyReads(key);
151 return running
152 ? Effect.succeed(history)
153 : Effect.fail(new MonitorError({ message: historyError }));
154 }),
155 ).pipe(Atom.keepAlive),
156 );
157 monitor.history.mockReset();
158 monitor.history.mockImplementation(({ environmentId, input }) =>
159 historyQueries(`${environmentId}:${input.windowMs}`),
160 );
161 let releaseRetry = () => {};
162 const retryRelease = new Promise<void>((resolve) => {
163 releaseRetry = resolve;
164 });
165 monitor.retry.mockReset();
166 monitor.retry.mockImplementation(async () => {
167 if (holdRetry) await retryRelease;
168 if (retryFails)
169 return AsyncResult.failure(Cause.fail(new MonitorError({ message: "Restart failed." })));
170 running = true;
171 return AsyncResult.success({ accepted: true, snapshot });
172 });
173 const registry = AtomRegistry.make();
174 return {
175 registry,
176 historyQueries,
177 historyReads,
178 releaseRetry,
179 [Symbol.dispose]: () => registry.dispose(),
180 };
182​
183describe("resource monitor retry", () => {
184 it("clears the settings and history errors after a successful retry without remounting", async () => {
185 using fixture = setup();
186 await using screen = await render(
187 <RegistryContext.Provider value={fixture.registry}>
188 <ResourceTelemetryDiagnostics environmentId={environmentId} />
189 </RegistryContext.Provider>,
190 );
191 await expect.element(screen.getByText(startError, { exact: true })).toBeVisible();
192 await expect.element(screen.getByText(historyError, { exact: true })).toBeVisible();
193​
194 await screen.getByRole("button", { name: "Retry monitor" }).click();
195​
196 await expect.element(screen.getByText(startError, { exact: true })).not.toBeInTheDocument();
197 await expect.element(screen.getByText(historyError, { exact: true })).not.toBeInTheDocument();
198 await expect.element(screen.getByText("Native healthy", { exact: true })).toBeVisible();
199 await expect
200 .element(screen.getByRole("button", { name: "Retry monitor" }))
201 .not.toBeInTheDocument();
202 });
203​
204 it.each([false, true])(
205 "refreshes the changed history window only in the retry environment (switch environment: %s)",
206 async (switchEnvironment) => {
207 using fixture = setup({ holdRetry: true });
208 const view = (selectedEnvironment: EnvironmentId) => (
209 <RegistryContext.Provider value={fixture.registry}>
210 <ResourceTelemetryDiagnostics environmentId={selectedEnvironment} />
211 </RegistryContext.Provider>
212 );
213 await using screen = await render(view(environmentId));
214 await expect.element(screen.getByText(historyError, { exact: true })).toBeVisible();
215 await screen.getByRole("button", { name: "Retry monitor" }).click();
216 await expect.element(screen.getByRole("button", { name: "Retry monitor" })).toBeDisabled();
217 const selectedEnvironment = switchEnvironment ? EnvironmentId.make("other") : environmentId;
218 if (switchEnvironment) await screen.rerender(view(selectedEnvironment));
219 await screen.getByRole("button", { name: "5m", exact: true }).click();
220 await expect.element(screen.getByText(historyError, { exact: true })).toBeVisible();
221 const selectedKey = `${selectedEnvironment}:300000`;
222 expect(fixture.registry.get(fixture.historyQueries(selectedKey))._tag).toBe("Failure");
223 fixture.historyReads.mockClear();
224​
225 await act(() => fixture.releaseRetry());
226​
227 if (switchEnvironment) {
228 await expect.element(screen.getByRole("button", { name: "Retry monitor" })).toBeEnabled();
229 await expect.element(screen.getByText(historyError, { exact: true })).toBeVisible();
230 expect(fixture.registry.get(fixture.historyQueries(selectedKey))._tag).toBe("Failure");
231 expect(fixture.historyReads).not.toHaveBeenCalled();
232 } else {
233 await expect.element(screen.getByText(startError, { exact: true })).not.toBeInTheDocument();
234 await expect
235 .element(screen.getByText(historyError, { exact: true }))
236 .not.toBeInTheDocument();
237 expect(fixture.registry.get(fixture.historyQueries(selectedKey))._tag).toBe("Success");
238 expect(fixture.historyReads.mock.calls.map(([key]) => key)).toEqual([selectedKey]);
239 await screen.getByRole("button", { name: "15m", exact: true }).click();
240 await expect
241 .element(screen.getByText(historyError, { exact: true }))
242 .not.toBeInTheDocument();
243 }
244 expect(fixture.registry.get(fixture.historyQueries(`${environmentId}:900000`))._tag).toBe(
245 "Success",
246 );
247 expect(monitor.retry).toHaveBeenCalledExactlyOnceWith({ environmentId, input: {} });
248 },
249 );
250​
251 it("keeps the failed monitor and history errors visible when retry fails", async () => {
252 using fixture = setup({ retryFails: true });
253 await using screen = await render(
254 <RegistryContext.Provider value={fixture.registry}>
255 <ResourceTelemetryDiagnostics environmentId={environmentId} />
256 </RegistryContext.Provider>,
257 );
258 await expect.element(screen.getByText(startError, { exact: true })).toBeVisible();
259 await expect.element(screen.getByText(historyError, { exact: true })).toBeVisible();
260​
261 await screen.getByRole("button", { name: "Retry monitor" }).click();
262​
263 await expect.element(screen.getByRole("button", { name: "Retry monitor" })).toBeEnabled();
264 expect(monitor.retry).toHaveBeenCalledExactlyOnceWith({ environmentId, input: {} });
265 await expect.element(screen.getByText(startError, { exact: true })).toBeVisible();
266 await expect.element(screen.getByText(historyError, { exact: true })).toBeVisible();
267 });
268});