History

[codex] Diagnose desktop client settings read failures (#3432)

Julius Marminge committed 4 months ago

Commit ad2cb1d1 parent 674590e2 files · +146 −2Browse files

Unified · Split

2 files
  1. apps/desktop/src/settings/DesktopClientSettings.diagnostics.test.ts +129 −0
  2. apps/desktop/src/settings/DesktopClientSettings.ts +17 −2
apps/desktop/src/settings/DesktopClientSettings.diagnostics.test.ts+129−0
@@ -0,0 +1,129 @@
1+import * as NodeServices from "@effect/platform-node/NodeServices";
2+import { assert, describe, it } from "@effect/vitest";
3+import * as Effect from "effect/Effect";
4+import * as FileSystem from "effect/FileSystem";
5+import * as Layer from "effect/Layer";
6+import * as Logger from "effect/Logger";
7+import * as Option from "effect/Option";
8+import * as PlatformError from "effect/PlatformError";
9+import * as References from "effect/References";
10+
11+import * as DesktopConfig from "../app/DesktopConfig.ts";
12+import * as DesktopEnvironment from "../app/DesktopEnvironment.ts";
13+import * as DesktopClientSettings from "./DesktopClientSettings.ts";
14+
15+interface LogRecord {
16+ readonly message: unknown;
17+ readonly annotations: Readonly<Record<string, unknown>>;
18+}
19+
20+const baseDir = "/virtual-home";
21+
22+function makeLayer(fileSystemLayer: Layer.Layer<FileSystem.FileSystem>) {
23+ const environmentLayer = DesktopEnvironment.layer({
24+ dirname: "/repo/apps/desktop/src",
25+ homeDirectory: baseDir,
26+ platform: "darwin",
27+ processArch: "x64",
28+ appVersion: "1.2.3",
29+ appPath: "/repo",
30+ isPackaged: true,
31+ resourcesPath: "/missing/resources",
32+ runningUnderArm64Translation: false,
33+ }).pipe(
34+ Layer.provide(
35+ Layer.mergeAll(NodeServices.layer, DesktopConfig.layerTest({ T3CODE_HOME: baseDir })),
36+ ),
37+ );
38+
39+ return DesktopClientSettings.layer.pipe(
40+ Layer.provideMerge(Layer.mergeAll(environmentLayer, NodeServices.layer, fileSystemLayer)),
41+ );
42+}
43+
44+const readWithLogs = (fileSystemLayer: Layer.Layer<FileSystem.FileSystem>) => {
45+ const records: Array<LogRecord> = [];
46+ const logger = Logger.make(({ fiber, message }) => {
47+ records.push({
48+ message,
49+ annotations: { ...fiber.getRef(References.CurrentLogAnnotations) },
50+ });
51+ });
52+
53+ return Effect.gen(function* () {
54+ const environment = yield* DesktopEnvironment.DesktopEnvironment;
55+ const settings = yield* DesktopClientSettings.DesktopClientSettings;
56+ return {
57+ result: yield* settings.get,
58+ settingsPath: environment.clientSettingsPath,
59+ records,
60+ };
61+ }).pipe(
62+ Effect.provide(
63+ Layer.mergeAll(
64+ makeLayer(fileSystemLayer),
65+ Logger.layer([logger], { mergeWithExisting: false }),
66+ ),
67+ ),
68+ );
69+};
70+
71+describe("DesktopClientSettings diagnostics", () => {
72+ it.effect("treats a missing settings file as expected without warning", () =>
73+ Effect.gen(function* () {
74+ const result = yield* readWithLogs(FileSystem.layerNoop({}));
75+
76+ assert.isTrue(Option.isNone(result.result));
77+ assert.deepEqual(result.records, []);
78+ }),
79+ );
80+
81+ it.effect("logs non-missing filesystem failures with the settings path", () => {
82+ const permissionError = PlatformError.systemError({
83+ _tag: "PermissionDenied",
84+ module: "FileSystem",
85+ method: "readFileString",
86+ pathOrDescriptor: `${baseDir}/userdata/client-settings.json`,
87+ });
88+
89+ return Effect.gen(function* () {
90+ const result = yield* readWithLogs(
91+ FileSystem.layerNoop({
92+ readFileString: () => Effect.fail(permissionError),
93+ }),
94+ );
95+
96+ assert.isTrue(Option.isNone(result.result));
97+ assert.equal(result.records.length, 1);
98+ assert.deepEqual(result.records[0]?.message, [
99+ "Could not read desktop client settings.",
100+ permissionError,
101+ ]);
102+ assert.equal(result.records[0]?.annotations.settingsPath, result.settingsPath);
103+ });
104+ });
105+
106+ it.effect("logs malformed settings documents with the settings path", () =>
107+ Effect.gen(function* () {
108+ const result = yield* readWithLogs(
109+ FileSystem.layerNoop({
110+ readFileString: () => Effect.succeed("{not-json"),
111+ }),
112+ );
113+
114+ assert.isTrue(Option.isNone(result.result));
115+ assert.equal(result.records.length, 1);
116+ const message = result.records[0]?.message;
117+ if (!Array.isArray(message)) {
118+ return assert.fail("expected structured warning arguments");
119+ }
120+ assert.equal(message[0], "Could not decode desktop client settings.");
121+ const schemaError = message[1];
122+ if (schemaError === null || typeof schemaError !== "object") {
123+ return assert.fail("expected the schema error in the warning");
124+ }
125+ assert.equal("_tag" in schemaError ? schemaError._tag : undefined, "SchemaError");
126+ assert.equal(result.records[0]?.annotations.settingsPath, result.settingsPath);
127+ }),
128+ );
129+});
apps/desktop/src/settings/DesktopClientSettings.ts+17−2
@@ -67,14 +67,29 @@ const readClientSettings = ( · 66 unmodified lines
6767 settingsPath: string,
6868 ): Effect.Effect<Option.Option<ClientSettings>> =>
6969 fileSystem.readFileString(settingsPath).pipe(
70− Effect.option,
70+ Effect.map(Option.some),
71+ Effect.catchTags({
72+ PlatformError: (cause) =>
73+ cause.reason._tag === "NotFound"
74+ ? Effect.succeed(Option.none<string>())
75+ : Effect.logWarning("Could not read desktop client settings.", cause).pipe(
76+ Effect.annotateLogs({ settingsPath }),
77+ Effect.as(Option.none<string>()),
78+ ),
79+ }),
7180 Effect.flatMap(
7281 Option.match({
7382 onNone: () => Effect.succeed(Option.none<ClientSettings>()),
7483 onSome: (raw) =>
7584 decodeClientSettingsJson(raw).pipe(
7685 Effect.map((settings) => Option.some(settings)),
77− Effect.orElseSucceed(() => Option.none<ClientSettings>()),
86+ Effect.catchTags({
87+ SchemaError: (cause) =>
88+ Effect.logWarning("Could not decode desktop client settings.", cause).pipe(
89+ Effect.annotateLogs({ settingsPath }),
90+ Effect.as(Option.none<ClientSettings>()),
91+ ),
92+ }),
7893 ),
7994 }),
8095 ),

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t