authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-04 13:33:20-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-04 20:33:20+00:00
logc251e41b5e5287b786cda44ddc263b8e6a74859c
tree85d55508f2b295ffff54bb950ed57df42fe3768f
parent5c6c1d67d82bb81bddaee6d83e54e8aecd96ec7d
signature Signed by PGP key B5690EEEBB952194

test(desktop): make path fixtures and timeouts host-portable (#9568)

Co-authored-by: Claude Code <noreply@anthropic.com>

6 files changed, 134 insertions(+), 88 deletions(-)

apps/desktop/scripts/electron-launcher.mjs+8-4
......@@ -165,10 +165,14 @@ function registerMacLauncherBundle(appBundlePath) {
165165 }
166166}
167167
168// Bundle-internal paths are macOS paths whatever host builds them.
168169export function resolveMacLauncherIconPaths(runtimeDir, development = isDevelopment) {
169170 return {
170171 sourceIconPath: development ? developmentMacIconPngPath : productionMacIconPngPath,
171 generatedIconPath: NodePath.join(runtimeDir, development ? "icon-dev.icns" : "icon-prod.icns"),
172 generatedIconPath: NodePath.posix.join(
173 runtimeDir,
174 development ? "icon-dev.icns" : "icon-prod.icns",
175 ),
172176 };
173177}
174178
......@@ -280,12 +284,12 @@ function readJson(path) {
280284}
281285
282286export function resolveMacLauncherPaths(appBundlePath, displayName = APP_DISPLAY_NAME) {
283 const executableDir = NodePath.join(appBundlePath, "Contents", "MacOS");
287 const executableDir = NodePath.posix.join(appBundlePath, "Contents", "MacOS");
284288 const launcherExecutableName = `${displayName} Launcher`;
285289 return {
286290 launcherExecutableName,
287 launcherBinaryPath: NodePath.join(executableDir, launcherExecutableName),
288 runtimeElectronBinaryPath: NodePath.join(executableDir, "Electron"),
291 launcherBinaryPath: NodePath.posix.join(executableDir, launcherExecutableName),
292 runtimeElectronBinaryPath: NodePath.posix.join(executableDir, "Electron"),
289293 };
290294}
291295
apps/desktop/scripts/electron-launcher.test.mjs+3-2
......@@ -84,9 +84,10 @@ describe("electron development launcher", () => {
8484 const development = resolveMacLauncherIconPaths("/runtime", true);
8585 const production = resolveMacLauncherIconPaths("/runtime", false);
8686
87 assert.match(development.sourceIconPath, /assets\/dev\/blueprint-macos-1024\.png$/);
87 // The source icons are real repo paths, joined for the host.
88 assert.match(development.sourceIconPath, /assets[\\/]dev[\\/]blueprint-macos-1024\.png$/);
8889 assert.equal(development.generatedIconPath, "/runtime/icon-dev.icns");
89 assert.match(production.sourceIconPath, /assets\/prod\/black-macos-1024\.png$/);
90 assert.match(production.sourceIconPath, /assets[\\/]prod[\\/]black-macos-1024\.png$/);
9091 assert.equal(production.generatedIconPath, "/runtime/icon-prod.icns");
9192 });
9293});
apps/desktop/src/app/DesktopConnectionCatalogStore.test.ts+19-12
......@@ -4,6 +4,7 @@ import { ConnectionCatalogDocument } from "@t3tools/client-runtime/platform";
44import { EnvironmentId, type PersistedSavedEnvironmentRecord } from "@t3tools/contracts";
55import * as Effect from "effect/Effect";
66import * as FileSystem from "effect/FileSystem";
7import * as Path from "effect/Path";
78import * as Layer from "effect/Layer";
89import * as Option from "effect/Option";
910import * as PlatformError from "effect/PlatformError";
......@@ -226,10 +227,11 @@ describe("DesktopConnectionCatalogStore", () => {
226227 it.effect("surfaces malformed catalog documents without deleting them", () =>
227228 withStore(
228229 Effect.gen(function* () {
230 const path = yield* Path.Path;
229231 const environment = yield* DesktopEnvironment.DesktopEnvironment;
230232 const fileSystem = yield* FileSystem.FileSystem;
231233 const store = yield* DesktopConnectionCatalogStore.DesktopConnectionCatalogStore;
232 const catalogPath = `${environment.stateDir}/connection-catalog.json`;
234 const catalogPath = path.join(environment.stateDir, "connection-catalog.json");
233235 yield* fileSystem.makeDirectory(environment.stateDir, { recursive: true });
234236 yield* fileSystem.writeFileString(catalogPath, "{not-json");
235237
......@@ -247,6 +249,7 @@ describe("DesktopConnectionCatalogStore", () => {
247249
248250 it.effect("surfaces catalog filesystem failures instead of treating them as missing", () =>
249251 Effect.gen(function* () {
252 const path = yield* Path.Path;
250253 const baseFileSystem = yield* FileSystem.FileSystem;
251254 const baseDir = yield* baseFileSystem.makeTempDirectoryScoped({
252255 prefix: "t3-desktop-connection-catalog-test-",
......@@ -255,7 +258,7 @@ describe("DesktopConnectionCatalogStore", () => {
255258 _tag: "PermissionDenied",
256259 module: "FileSystem",
257260 method: "readFileString",
258 pathOrDescriptor: `${baseDir}/userdata/connection-catalog.json`,
261 pathOrDescriptor: path.join(baseDir, "userdata", "connection-catalog.json"),
259262 });
260263 const fileSystemLayer = Layer.succeed(
261264 FileSystem.FileSystem,
......@@ -272,11 +275,11 @@ describe("DesktopConnectionCatalogStore", () => {
272275 error,
273276 DesktopConnectionCatalogStore.DesktopConnectionCatalogStoreReadError,
274277 );
275 assert.equal(error.catalogPath, `${baseDir}/userdata/connection-catalog.json`);
278 assert.equal(error.catalogPath, path.join(baseDir, "userdata", "connection-catalog.json"));
276279 assert.strictEqual(error.cause, permissionError);
277280 assert.equal(
278281 error.message,
279 `Failed to read the desktop connection catalog at ${baseDir}/userdata/connection-catalog.json.`,
282 `Failed to read the desktop connection catalog at ${path.join(baseDir, "userdata", "connection-catalog.json")}.`,
280283 );
281284 assert.notEqual(error.message, permissionError.message);
282285 }).pipe(Effect.provide(NodeServices.layer), Effect.scoped),
......@@ -285,6 +288,7 @@ describe("DesktopConnectionCatalogStore", () => {
285288 it.effect("reports the failed catalog write operation and path", () =>
286289 Effect.gen(function* () {
287290 const baseFileSystem = yield* FileSystem.FileSystem;
291 const path = yield* Path.Path;
288292 const baseDir = yield* baseFileSystem.makeTempDirectoryScoped({
289293 prefix: "t3-desktop-connection-catalog-test-",
290294 });
......@@ -292,7 +296,7 @@ describe("DesktopConnectionCatalogStore", () => {
292296 _tag: "PermissionDenied",
293297 module: "FileSystem",
294298 method: "makeDirectory",
295 pathOrDescriptor: `${baseDir}/userdata`,
299 pathOrDescriptor: path.join(baseDir, "userdata"),
296300 });
297301 const fileSystemLayer = Layer.succeed(
298302 FileSystem.FileSystem,
......@@ -310,11 +314,11 @@ describe("DesktopConnectionCatalogStore", () => {
310314 DesktopConnectionCatalogStore.DesktopConnectionCatalogStoreWriteError,
311315 );
312316 assert.equal(error.operation, "create-directory");
313 assert.equal(error.path, `${baseDir}/userdata`);
317 assert.equal(error.path, path.join(baseDir, "userdata"));
314318 assert.strictEqual(error.cause, permissionError);
315319 assert.equal(
316320 error.message,
317 `Desktop connection catalog write failed during create-directory at ${baseDir}/userdata.`,
321 `Desktop connection catalog write failed during create-directory at ${path.join(baseDir, "userdata")}.`,
318322 );
319323 assert.notEqual(error.message, permissionError.message);
320324 }).pipe(Effect.provide(NodeServices.layer), Effect.scoped),
......@@ -323,6 +327,7 @@ describe("DesktopConnectionCatalogStore", () => {
323327 it.effect("reports the legacy migration stage", () =>
324328 withStore(
325329 Effect.gen(function* () {
330 const path = yield* Path.Path;
326331 const environment = yield* DesktopEnvironment.DesktopEnvironment;
327332 const fileSystem = yield* FileSystem.FileSystem;
328333 const store = yield* DesktopConnectionCatalogStore.DesktopConnectionCatalogStore;
......@@ -335,7 +340,7 @@ describe("DesktopConnectionCatalogStore", () => {
335340 DesktopConnectionCatalogStore.DesktopConnectionCatalogStoreMigrationError,
336341 );
337342 assert.equal(error.operation, "read-legacy-registry");
338 assert.equal(error.catalogPath, `${environment.stateDir}/connection-catalog.json`);
343 assert.equal(error.catalogPath, path.join(environment.stateDir, "connection-catalog.json"));
339344 assert.instanceOf(
340345 error.cause,
341346 DesktopSavedEnvironments.DesktopSavedEnvironmentsDocumentDecodeError,
......@@ -345,7 +350,7 @@ describe("DesktopConnectionCatalogStore", () => {
345350 assert.exists(registryError.cause);
346351 assert.equal(
347352 error.message,
348 `Legacy desktop saved-environment migration failed during read-legacy-registry into ${environment.stateDir}/connection-catalog.json.`,
353 `Legacy desktop saved-environment migration failed during read-legacy-registry into ${path.join(environment.stateDir, "connection-catalog.json")}.`,
349354 );
350355 assert.notEqual(error.message, registryError.message);
351356 }),
......@@ -355,10 +360,11 @@ describe("DesktopConnectionCatalogStore", () => {
355360 it.effect("reports invalid encrypted catalog data without exposing it", () =>
356361 withStore(
357362 Effect.gen(function* () {
363 const path = yield* Path.Path;
358364 const environment = yield* DesktopEnvironment.DesktopEnvironment;
359365 const fileSystem = yield* FileSystem.FileSystem;
360366 const store = yield* DesktopConnectionCatalogStore.DesktopConnectionCatalogStore;
361 const catalogPath = `${environment.stateDir}/connection-catalog.json`;
367 const catalogPath = path.join(environment.stateDir, "connection-catalog.json");
362368 yield* fileSystem.makeDirectory(environment.stateDir, { recursive: true });
363369 yield* fileSystem.writeFileString(catalogPath, '{"version":1,"encryptedCatalog":"%%%"}\n');
364370
......@@ -381,6 +387,7 @@ describe("DesktopConnectionCatalogStore", () => {
381387
382388 it.effect("surfaces a catalog that can no longer be decrypted without deleting it", () =>
383389 Effect.gen(function* () {
390 const path = yield* Path.Path;
384391 const fileSystem = yield* FileSystem.FileSystem;
385392 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
386393 prefix: "t3-desktop-connection-catalog-test-",
......@@ -399,14 +406,14 @@ describe("DesktopConnectionCatalogStore", () => {
399406 DesktopConnectionCatalogStore.DesktopConnectionCatalogStoreProtectionError,
400407 );
401408 assert.equal(error.operation, "decrypt-catalog");
402 assert.equal(error.catalogPath, `${baseDir}/userdata/connection-catalog.json`);
409 assert.equal(error.catalogPath, path.join(baseDir, "userdata", "connection-catalog.json"));
403410 assert.instanceOf(error.cause, ElectronSafeStorage.ElectronSafeStorageDecryptError);
404411 const decryptError = error.cause as ElectronSafeStorage.ElectronSafeStorageDecryptError;
405412 assert.instanceOf(decryptError.cause, Error);
406413 assert.equal(decryptError.cause.message, "invalid encrypted catalog");
407414 assert.equal(
408415 error.message,
409 `Desktop connection catalog protection failed during decrypt-catalog at ${baseDir}/userdata/connection-catalog.json.`,
416 `Desktop connection catalog protection failed during decrypt-catalog at ${path.join(baseDir, "userdata", "connection-catalog.json")}.`,
410417 );
411418 assert.notEqual(error.message, decryptError.message);
412419 yield* Ref.set(failDecrypt, false);
apps/desktop/src/preview/BrowserImport/Sources.test.ts+92-66
......@@ -1,5 +1,6 @@
11// @effect-diagnostics nodeBuiltinImport:off - Builds a Chromium-shaped cookie
22// table with the same native bindings the source reads.
3import * as NodePath from "node:path";
34import * as NodeServices from "@effect/platform-node/NodeServices";
45import { assert, describe, it } from "@effect/vitest";
56import {
......@@ -531,7 +532,7 @@ Path=Profiles/wxyz.empty
531532 yield* fileSystem.makeDirectory(`${root}/Profiles/wxyz.empty`, { recursive: true });
532533
533534 assert.deepEqual(yield* listSourceProfiles(firefox, context), [
534 { directory: "Profiles/abcd.default-release", name: "original" },
535 { directory: context.path.join("Profiles", "abcd.default-release"), name: "original" },
535536 ]);
536537 }),
537538 ),
......@@ -612,10 +613,16 @@ describe("cookieDatabaseCandidatePaths", () => {
612613 run(
613614 Effect.gen(function* () {
614615 const context = yield* withSourceHome();
615 const profile = `${context.home}/Library/Application Support/net.imput.helium/Profile 1`;
616 const profile = context.path.join(
617 context.home,
618 "Library",
619 "Application Support",
620 "net.imput.helium",
621 "Profile 1",
622 );
616623 assert.deepEqual(cookieDatabaseCandidatePaths(helium, context, "Profile 1"), [
617 `${profile}/Network/Cookies`,
618 `${profile}/Cookies`,
624 context.path.join(profile, "Network", "Cookies"),
625 context.path.join(profile, "Cookies"),
619626 ]);
620627 }),
621628 ),
......@@ -626,7 +633,7 @@ describe("cookieDatabaseCandidatePaths", () => {
626633 Effect.gen(function* () {
627634 const fileSystem = yield* FileSystem.FileSystem;
628635 const context = yield* withSourceHome();
629 const root = helium.userDataDirectory(context);
636 const root = userDataDirectory(context);
630637 // Chromium 96+ keeps sessions in Network/; a root Cookies left behind
631638 // by the move is stale and must not be the one imported.
632639 yield* fileSystem.makeDirectory(`${root}/Default/Network`, { recursive: true });
......@@ -635,7 +642,7 @@ describe("cookieDatabaseCandidatePaths", () => {
635642
636643 assert.equal(
637644 yield* resolveCookieDatabase(helium, context, "Default"),
638 `${root}/Default/Network/Cookies`,
645 context.path.join(root, "Default", "Network", "Cookies"),
639646 );
640647 // A fresh install with only the Network/ jar is installed, not hidden.
641648 yield* fileSystem.remove(`${root}/Default/Cookies`);
......@@ -680,8 +687,8 @@ describe("Firefox Snap profiles", () => {
680687 Effect.provideService(HostProcessEnvironment, { HOME: home }),
681688 Effect.provideService(HostProcessPlatform, "linux"),
682689 );
683 const root = `${home}/snap/firefox/common/.mozilla/firefox`;
684 const directory = `${root}/abcd.default`;
690 const root = context.path.join(home, "snap", "firefox", "common", ".mozilla", "firefox");
691 const directory = context.path.join(root, "abcd.default");
685692 yield* fileSystem.makeDirectory(directory, { recursive: true });
686693 yield* writeFirefoxCookieDatabase(`${directory}/cookies.sqlite`, 2, 1);
687694 yield* fileSystem.writeFileString(
......@@ -695,7 +702,7 @@ describe("Firefox Snap profiles", () => {
695702 ]);
696703 assert.equal(
697704 yield* resolveCookieDatabase(firefox, context, directory),
698 `${directory}/cookies.sqlite`,
705 context.path.join(directory, "cookies.sqlite"),
699706 );
700707 assert.isFalse(yield* isSourceRunning(firefox, context));
701708 yield* fileSystem.symlink("foreign-host:+4242", `${directory}/lock`);
......@@ -720,8 +727,8 @@ describe("Firefox Snap profiles", () => {
720727 Effect.provideService(HostProcessEnvironment, { HOME: home }),
721728 Effect.provideService(HostProcessPlatform, "linux"),
722729 );
723 const native = `${home}/.mozilla/firefox`;
724 const snap = `${home}/snap/firefox/common/.mozilla/firefox`;
730 const native = context.path.join(home, ".mozilla", "firefox");
731 const snap = context.path.join(home, "snap", "firefox", "common", ".mozilla", "firefox");
725732 for (const root of [native, snap]) {
726733 yield* fileSystem.makeDirectory(`${root}/abcd.default`, { recursive: true });
727734 yield* writeFirefoxCookieDatabase(`${root}/abcd.default/cookies.sqlite`, 1, 0);
......@@ -735,14 +742,14 @@ describe("Firefox Snap profiles", () => {
735742 const profiles = yield* listSourceProfiles(firefox, context);
736743 assert.deepEqual(
737744 profiles.map((profile) => profile.directory),
738 ["abcd.default", `${snap}/abcd.default`],
745 ["abcd.default", context.path.join(snap, "abcd.default")],
739746 );
740747 const databases = yield* Effect.forEach(profiles, (profile) =>
741748 resolveCookieDatabase(firefox, context, profile.directory),
742749 );
743750 assert.deepEqual(databases, [
744 `${native}/abcd.default/cookies.sqlite`,
745 `${snap}/abcd.default/cookies.sqlite`,
751 context.path.join(native, "abcd.default", "cookies.sqlite"),
752 context.path.join(snap, "abcd.default", "cookies.sqlite"),
746753 ]);
747754 }),
748755 ),
......@@ -752,8 +759,8 @@ describe("Firefox Snap profiles", () => {
752759describe("listSourceProfiles Firefox fallback", () => {
753760 const cases = [
754761 { platform: "linux" as const, profileDirectory: "linux.default" },
755 { platform: "darwin" as const, profileDirectory: "Profiles/macos.default" },
756 { platform: "win32" as const, profileDirectory: "Profiles/windows.default" },
762 { platform: "darwin" as const, profileDirectory: NodePath.join("Profiles", "macos.default") },
763 { platform: "win32" as const, profileDirectory: NodePath.join("Profiles", "windows.default") },
757764 ];
758765
759766 for (const { platform, profileDirectory } of cases) {
......@@ -824,7 +831,11 @@ describe("listSourceProfiles Firefox fallback", () => {
824831
825832 // Returning the empty declared list would hide the browser entirely.
826833 assert.deepEqual(yield* listSourceProfiles(firefox, context), [
827 { directory: "Profiles/real.default", name: "real.default", cookieCount: 3 },
834 {
835 directory: path.join("Profiles", "real.default"),
836 name: "real.default",
837 cookieCount: 3,
838 },
828839 ]);
829840 assert.isTrue(yield* isSourceInstalled(firefox, context));
830841 }),
......@@ -855,7 +866,11 @@ describe("listSourceProfiles Firefox fallback", () => {
855866 );
856867
857868 assert.deepEqual(yield* listSourceProfiles(firefox, context), [
858 { directory: "Profiles/declared.default", name: "Declared", cookieCount: 2 },
869 {
870 directory: path.join("Profiles", "declared.default"),
871 name: "Declared",
872 cookieCount: 2,
873 },
859874 ]);
860875
861876 yield* fileSystem.remove(path.join(root, "profiles.ini"));
......@@ -864,8 +879,16 @@ describe("listSourceProfiles Firefox fallback", () => {
864879 yield* writeFirefoxCookieDatabase(path.join(fallbackDirectory, "cookies.sqlite"), 1, 4);
865880
866881 assert.deepEqual(yield* listSourceProfiles(firefox, context), [
867 { directory: "Profiles/declared.default", name: "declared.default", cookieCount: 2 },
868 { directory: "Profiles/fallback.default", name: "fallback.default", cookieCount: 1 },
882 {
883 directory: path.join("Profiles", "declared.default"),
884 name: "declared.default",
885 cookieCount: 2,
886 },
887 {
888 directory: path.join("Profiles", "fallback.default"),
889 name: "fallback.default",
890 cookieCount: 1,
891 },
869892 ]);
870893 }),
871894 ),
......@@ -928,53 +951,56 @@ describe("isSourceRunning for Firefox", () => {
928951 ),
929952 );
930953
931 it.effect("detects a live fcntl lock on .parentlock, as macOS Firefox leaves it", () =>
932 run(
933 Effect.gen(function* () {
934 const fileSystem = yield* FileSystem.FileSystem;
935 const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
936 const home = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3code-firefox-" });
937 const context = yield* sourcePathContext.pipe(
938 Effect.provideService(HostProcessEnvironment, { HOME: home }),
939 Effect.provideService(HostProcessPlatform, "darwin"),
940 );
941 const root = firefox.userDataDirectory(context)!;
942 const profile = `${root}/Profiles/abcd.default-release`;
943 yield* fileSystem.makeDirectory(profile, { recursive: true });
944 yield* fileSystem.writeFileString(`${profile}/cookies.sqlite`, "db");
945 const parentLock = `${profile}/.parentlock`;
946 yield* fileSystem.writeFileString(parentLock, "");
947
948 // Hold the lock from a child the way Firefox does (F_SETLK, write),
949 // and keep it until the scope closes.
950 const holder = yield* spawner.spawn(
951 ChildProcess.make(
952 "python3",
953 [
954 "-c",
955 "import fcntl,os,sys,time\n" +
956 "fd=os.open(sys.argv[1],os.O_WRONLY)\n" +
957 "fcntl.lockf(fd,fcntl.LOCK_EX|fcntl.LOCK_NB)\n" +
958 "print('locked',flush=True)\n" +
959 "time.sleep(30)",
960 parentLock,
961 ],
962 { stdin: "ignore" },
963 ),
964 );
965 // Wait for the child to confirm it holds the lock before probing.
966 yield* holder.stdout.pipe(
967 Stream.decodeText(),
968 Stream.splitLines,
969 Stream.filter((line) => line.trim() === "locked"),
970 Stream.take(1),
971 Stream.runDrain,
972 );
954 // Holds the lock with python3's fcntl, which does not exist on Windows.
955 it.effect.skipIf(HostProcessPlatform.defaultValue() === "win32")(
956 "detects a live fcntl lock on .parentlock, as macOS Firefox leaves it",
957 () =>
958 run(
959 Effect.gen(function* () {
960 const fileSystem = yield* FileSystem.FileSystem;
961 const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
962 const home = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3code-firefox-" });
963 const context = yield* sourcePathContext.pipe(
964 Effect.provideService(HostProcessEnvironment, { HOME: home }),
965 Effect.provideService(HostProcessPlatform, "darwin"),
966 );
967 const root = firefox.userDataDirectory(context)!;
968 const profile = `${root}/Profiles/abcd.default-release`;
969 yield* fileSystem.makeDirectory(profile, { recursive: true });
970 yield* fileSystem.writeFileString(`${profile}/cookies.sqlite`, "db");
971 const parentLock = `${profile}/.parentlock`;
972 yield* fileSystem.writeFileString(parentLock, "");
973
974 // Hold the lock from a child the way Firefox does (F_SETLK, write),
975 // and keep it until the scope closes.
976 const holder = yield* spawner.spawn(
977 ChildProcess.make(
978 "python3",
979 [
980 "-c",
981 "import fcntl,os,sys,time\n" +
982 "fd=os.open(sys.argv[1],os.O_WRONLY)\n" +
983 "fcntl.lockf(fd,fcntl.LOCK_EX|fcntl.LOCK_NB)\n" +
984 "print('locked',flush=True)\n" +
985 "time.sleep(30)",
986 parentLock,
987 ],
988 { stdin: "ignore" },
989 ),
990 );
991 // Wait for the child to confirm it holds the lock before probing.
992 yield* holder.stdout.pipe(
993 Stream.decodeText(),
994 Stream.splitLines,
995 Stream.filter((line) => line.trim() === "locked"),
996 Stream.take(1),
997 Stream.runDrain,
998 );
973999
974 assert.isTrue(yield* isSourceRunning(firefox, context));
975 yield* holder.kill();
976 }),
977 ),
1000 assert.isTrue(yield* isSourceRunning(firefox, context));
1001 yield* holder.kill();
1002 }),
1003 ),
9781004 );
9791005
9801006 it.effect("reads a Firefox lock symlink's pid to tell live from crashed", () =>
apps/desktop/src/settings/DesktopSavedEnvironments.test.ts+7-4
......@@ -3,6 +3,7 @@ import { assert, describe, it } from "@effect/vitest";
33import { EnvironmentId, type PersistedSavedEnvironmentRecord } from "@t3tools/contracts";
44import * as Effect from "effect/Effect";
55import * as FileSystem from "effect/FileSystem";
6import * as Path from "effect/Path";
67import * as Layer from "effect/Layer";
78import * as Option from "effect/Option";
89import * as PlatformError from "effect/PlatformError";
......@@ -400,11 +401,12 @@ describe("DesktopSavedEnvironments", () => {
400401
401402 it.effect("reports saved environment filesystem reads separately from document decoding", () =>
402403 Effect.gen(function* () {
404 const path = yield* Path.Path;
403405 const baseFileSystem = yield* FileSystem.FileSystem;
404406 const baseDir = yield* baseFileSystem.makeTempDirectoryScoped({
405407 prefix: "t3-desktop-saved-environments-test-",
406408 });
407 const registryPath = `${baseDir}/userdata/saved-environments.json`;
409 const registryPath = path.join(baseDir, "userdata", "saved-environments.json");
408410 const permissionError = PlatformError.systemError({
409411 _tag: "PermissionDenied",
410412 module: "FileSystem",
......@@ -433,6 +435,7 @@ describe("DesktopSavedEnvironments", () => {
433435 it.effect("reports the failed saved environment write operation and path", () =>
434436 Effect.gen(function* () {
435437 const baseFileSystem = yield* FileSystem.FileSystem;
438 const path = yield* Path.Path;
436439 const baseDir = yield* baseFileSystem.makeTempDirectoryScoped({
437440 prefix: "t3-desktop-saved-environments-test-",
438441 });
......@@ -440,7 +443,7 @@ describe("DesktopSavedEnvironments", () => {
440443 _tag: "PermissionDenied",
441444 module: "FileSystem",
442445 method: "makeDirectory",
443 pathOrDescriptor: `${baseDir}/userdata`,
446 pathOrDescriptor: path.join(baseDir, "userdata"),
444447 });
445448 const fileSystemLayer = Layer.succeed(
446449 FileSystem.FileSystem,
......@@ -456,11 +459,11 @@ describe("DesktopSavedEnvironments", () => {
456459 const error = yield* savedEnvironments.setRegistry([savedRegistryRecord]).pipe(Effect.flip);
457460 assert.instanceOf(error, DesktopSavedEnvironments.DesktopSavedEnvironmentsWriteError);
458461 assert.equal(error.operation, "create-directory");
459 assert.equal(error.path, `${baseDir}/userdata`);
462 assert.equal(error.path, path.join(baseDir, "userdata"));
460463 assert.strictEqual(error.cause, permissionError);
461464 assert.equal(
462465 error.message,
463 `Desktop saved-environment write failed during create-directory at ${baseDir}/userdata.`,
466 `Desktop saved-environment write failed during create-directory at ${path.join(baseDir, "userdata")}.`,
464467 );
465468 assert.notEqual(error.message, permissionError.message);
466469 }).pipe(Effect.provide(NodeServices.layer), Effect.scoped),
apps/desktop/vite.config.ts+5
......@@ -83,4 +83,9 @@ export default defineConfig({
8383 entry: ["src/preview-pip-preload.ts"],
8484 },
8585 ],
86 test: {
87 // The Windows lane runs workspace suites concurrently; filesystem-heavy
88 // desktop integration tests can exceed Vitest's 5 second default there.
89 testTimeout: 15_000,
90 },
8691});