Skip to content

Commit f0f033b

Browse files
test: skip symlink fixtures where the host cannot create symlinks
Creating a symlink on Windows needs Developer Mode or elevation, so every test that plants one to prove containment or escape detection failed at setup there. workflowScriptQuery planted its link at module scope and took the whole file down at collection. Add a once-per-process probe, @t3tools/shared/testing/symlinks, and gate those tests on it across server, desktop, and scripts. Co-Authored-By: Claude Code <[email protected]>
1 parent 8c4c6d4 commit f0f033b

20 files changed

Lines changed: 1035 additions & 902 deletions

‎apps/desktop/src/preview/BrowserImport/BrowserImport.test.ts‎

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import * as Ref from "effect/Ref";
1414
import * as BrowserSession from "../BrowserSession.ts";
1515
import * as BrowserImport from "./BrowserImport.ts";
1616
import { BROWSER_IMPORT_SOURCES, sourcePathContext } from "./Sources.ts";
17+
import { symlinksSupported } from "@t3tools/shared/testing/symlinks";
1718

1819
const helium = BROWSER_IMPORT_SOURCES.find((source) => source.id === "helium")!;
1920

@@ -105,28 +106,30 @@ describe("BrowserImport.importCookies", () => {
105106
}).pipe(Effect.provide(NodeServices.layer), Effect.scoped),
106107
);
107108

108-
it.effect("refuses to import while the source browser holds its profile", () =>
109-
Effect.gen(function* () {
110-
const fileSystem = yield* FileSystem.FileSystem;
111-
const { importer, root } = yield* withImporter();
112-
// The lock Chromium leaves while it is running, dangling target and
113-
// all. This must stop the import before it ever asks the keychain.
114-
yield* fileSystem.symlink("host-that-does-not-exist-1234", `${root}/SingletonLock`);
115-
116-
const error = yield* importer
117-
.importCookies({
118-
input: {
119-
sourceId: "helium",
120-
sourceProfileDirectory: "Default",
121-
targetProfileId: "default",
122-
},
123-
scope: "persist:t3code-preview-test",
124-
persistent: true,
125-
})
126-
.pipe(Effect.flip);
109+
it.effect.skipIf(!symlinksSupported)(
110+
"refuses to import while the source browser holds its profile",
111+
() =>
112+
Effect.gen(function* () {
113+
const fileSystem = yield* FileSystem.FileSystem;
114+
const { importer, root } = yield* withImporter();
115+
// The lock Chromium leaves while it is running, dangling target and
116+
// all. This must stop the import before it ever asks the keychain.
117+
yield* fileSystem.symlink("host-that-does-not-exist-1234", `${root}/SingletonLock`);
118+
119+
const error = yield* importer
120+
.importCookies({
121+
input: {
122+
sourceId: "helium",
123+
sourceProfileDirectory: "Default",
124+
targetProfileId: "default",
125+
},
126+
scope: "persist:t3code-preview-test",
127+
persistent: true,
128+
})
129+
.pipe(Effect.flip);
127130

128-
assert.equal(error.reason, "browserRunning");
129-
}).pipe(Effect.provide(NodeServices.layer), Effect.scoped),
131+
assert.equal(error.reason, "browserRunning");
132+
}).pipe(Effect.provide(NodeServices.layer), Effect.scoped),
130133
);
131134
});
132135

‎apps/desktop/src/preview/BrowserImport/Sources.test.ts‎

Lines changed: 104 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
sourcePathContext,
3333
windowsChromiumCookiesAreHeld,
3434
} from "./Sources.ts";
35+
import { symlinksSupported } from "@t3tools/shared/testing/symlinks";
3536

3637
const helium = BROWSER_IMPORT_SOURCES.find((source) => source.id === "helium")!;
3738

@@ -125,7 +126,7 @@ const writeFirefoxCookieDatabase = (
125126
});
126127

127128
describe("Helium on Linux", () => {
128-
it.effect("discovers its profiles and checks the user-data lock", () =>
129+
it.effect.skipIf(!symlinksSupported)("discovers its profiles and checks the user-data lock", () =>
129130
run(
130131
Effect.gen(function* () {
131132
const fileSystem = yield* FileSystem.FileSystem;
@@ -221,48 +222,52 @@ describe("isSourceRunning", () => {
221222
),
222223
);
223224

224-
it.effect("reads Chromium's dangling SingletonLock symlink as a running browser", () =>
225-
run(
226-
Effect.gen(function* () {
227-
const fileSystem = yield* FileSystem.FileSystem;
228-
const context = yield* withSourceHome();
229-
assert.isFalse(yield* isSourceRunning(helium, context));
230-
231-
// Chromium points the lock at `<host>-<pid>`, a target that never
232-
// exists on disk. A check that follows the link reports a running
233-
// browser as closed, letting an import read a live, mid-write database.
234-
yield* fileSystem.symlink(
235-
"host-that-does-not-exist-1234",
236-
`${userDataDirectory(context)}/SingletonLock`,
237-
);
225+
it.effect.skipIf(!symlinksSupported)(
226+
"reads Chromium's dangling SingletonLock symlink as a running browser",
227+
() =>
228+
run(
229+
Effect.gen(function* () {
230+
const fileSystem = yield* FileSystem.FileSystem;
231+
const context = yield* withSourceHome();
232+
assert.isFalse(yield* isSourceRunning(helium, context));
233+
234+
// Chromium points the lock at `<host>-<pid>`, a target that never
235+
// exists on disk. A check that follows the link reports a running
236+
// browser as closed, letting an import read a live, mid-write database.
237+
yield* fileSystem.symlink(
238+
"host-that-does-not-exist-1234",
239+
`${userDataDirectory(context)}/SingletonLock`,
240+
);
238241

239-
assert.isTrue(yield* isSourceRunning(helium, context));
240-
}),
241-
),
242+
assert.isTrue(yield* isSourceRunning(helium, context));
243+
}),
244+
),
242245
);
243246

244-
it.effect("uses the provided hostname to classify Chromium locks", () =>
245-
run(
246-
Effect.gen(function* () {
247-
const fileSystem = yield* FileSystem.FileSystem;
248-
const paths = yield* withSourceHome();
249-
yield* fileSystem.symlink(
250-
"lock-owner-99999999",
251-
`${helium.userDataDirectory(paths)}/SingletonLock`,
252-
);
247+
it.effect.skipIf(!symlinksSupported)(
248+
"uses the provided hostname to classify Chromium locks",
249+
() =>
250+
run(
251+
Effect.gen(function* () {
252+
const fileSystem = yield* FileSystem.FileSystem;
253+
const paths = yield* withSourceHome();
254+
yield* fileSystem.symlink(
255+
"lock-owner-99999999",
256+
`${helium.userDataDirectory(paths)}/SingletonLock`,
257+
);
253258

254-
assert.isTrue(
255-
yield* isSourceRunning(helium, paths).pipe(
256-
Effect.provideService(HostProcessHostname, "another-host"),
257-
),
258-
);
259-
assert.isFalse(
260-
yield* isSourceRunning(helium, paths).pipe(
261-
Effect.provideService(HostProcessHostname, "lock-owner"),
262-
),
263-
);
264-
}),
265-
),
259+
assert.isTrue(
260+
yield* isSourceRunning(helium, paths).pipe(
261+
Effect.provideService(HostProcessHostname, "another-host"),
262+
),
263+
);
264+
assert.isFalse(
265+
yield* isSourceRunning(helium, paths).pipe(
266+
Effect.provideService(HostProcessHostname, "lock-owner"),
267+
),
268+
);
269+
}),
270+
),
266271
);
267272
});
268273

@@ -385,25 +390,27 @@ describe("isSourceInstalled", () => {
385390
),
386391
);
387392

388-
it.effect("follows cookie database symlinks when detecting profiles", () =>
389-
run(
390-
Effect.gen(function* () {
391-
const fileSystem = yield* FileSystem.FileSystem;
392-
const context = yield* withSourceHome();
393-
const root = userDataDirectory(context);
394-
yield* fileSystem.makeDirectory(`${root}/Default`, { recursive: true });
395-
yield* fileSystem.symlink("missing-cookies", `${root}/Default/Cookies`);
393+
it.effect.skipIf(!symlinksSupported)(
394+
"follows cookie database symlinks when detecting profiles",
395+
() =>
396+
run(
397+
Effect.gen(function* () {
398+
const fileSystem = yield* FileSystem.FileSystem;
399+
const context = yield* withSourceHome();
400+
const root = userDataDirectory(context);
401+
yield* fileSystem.makeDirectory(`${root}/Default`, { recursive: true });
402+
yield* fileSystem.symlink("missing-cookies", `${root}/Default/Cookies`);
396403

397-
assert.deepEqual(yield* listSourceProfiles(helium, context), []);
398-
assert.isFalse(yield* isSourceInstalled(helium, context));
404+
assert.deepEqual(yield* listSourceProfiles(helium, context), []);
405+
assert.isFalse(yield* isSourceInstalled(helium, context));
399406

400-
yield* fileSystem.writeFileString(`${root}/Default/missing-cookies`, "db");
401-
assert.deepEqual(yield* listSourceProfiles(helium, context), [
402-
{ directory: "Default", name: "Default" },
403-
]);
404-
assert.isTrue(yield* isSourceInstalled(helium, context));
405-
}),
406-
),
407+
yield* fileSystem.writeFileString(`${root}/Default/missing-cookies`, "db");
408+
assert.deepEqual(yield* listSourceProfiles(helium, context), [
409+
{ directory: "Default", name: "Default" },
410+
]);
411+
assert.isTrue(yield* isSourceInstalled(helium, context));
412+
}),
413+
),
407414
);
408415
});
409416

@@ -660,44 +667,48 @@ describe("cookieDatabaseCandidatePaths", () => {
660667
const firefox = BROWSER_IMPORT_SOURCES.find((source) => source.id === "firefox")!;
661668

662669
describe("Firefox Snap profiles", () => {
663-
it.effect("finds Snap profiles with or without profiles.ini and checks their locks", () =>
664-
run(
665-
Effect.gen(function* () {
666-
const fileSystem = yield* FileSystem.FileSystem;
667-
const home = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3code-firefox-snap-" });
668-
const context = yield* sourcePathContext.pipe(
669-
Effect.provideService(HostProcessEnvironment, { HOME: home }),
670-
Effect.provideService(HostProcessPlatform, "linux"),
671-
);
672-
const root = `${home}/snap/firefox/common/.mozilla/firefox`;
673-
const directory = `${root}/abcd.default`;
674-
yield* fileSystem.makeDirectory(directory, { recursive: true });
675-
yield* writeFirefoxCookieDatabase(`${directory}/cookies.sqlite`, 2, 1);
676-
yield* fileSystem.writeFileString(
677-
`${root}/profiles.ini`,
678-
"[Profile0]\nName=Personal\nIsRelative=1\nPath=abcd.default\n",
679-
);
670+
it.effect.skipIf(!symlinksSupported)(
671+
"finds Snap profiles with or without profiles.ini and checks their locks",
672+
() =>
673+
run(
674+
Effect.gen(function* () {
675+
const fileSystem = yield* FileSystem.FileSystem;
676+
const home = yield* fileSystem.makeTempDirectoryScoped({
677+
prefix: "t3code-firefox-snap-",
678+
});
679+
const context = yield* sourcePathContext.pipe(
680+
Effect.provideService(HostProcessEnvironment, { HOME: home }),
681+
Effect.provideService(HostProcessPlatform, "linux"),
682+
);
683+
const root = `${home}/snap/firefox/common/.mozilla/firefox`;
684+
const directory = `${root}/abcd.default`;
685+
yield* fileSystem.makeDirectory(directory, { recursive: true });
686+
yield* writeFirefoxCookieDatabase(`${directory}/cookies.sqlite`, 2, 1);
687+
yield* fileSystem.writeFileString(
688+
`${root}/profiles.ini`,
689+
"[Profile0]\nName=Personal\nIsRelative=1\nPath=abcd.default\n",
690+
);
680691

681-
assert.isTrue(yield* isSourceInstalled(firefox, context));
682-
assert.deepEqual(yield* listSourceProfiles(firefox, context), [
683-
{ directory, name: "Personal", cookieCount: 2 },
684-
]);
685-
assert.equal(
686-
yield* resolveCookieDatabase(firefox, context, directory),
687-
`${directory}/cookies.sqlite`,
688-
);
689-
assert.isFalse(yield* isSourceRunning(firefox, context));
690-
yield* fileSystem.symlink("foreign-host:+4242", `${directory}/lock`);
691-
assert.isTrue(yield* isSourceRunning(firefox, context));
692-
yield* fileSystem.remove(`${directory}/lock`);
693-
assert.isFalse(yield* isSourceRunning(firefox, context));
692+
assert.isTrue(yield* isSourceInstalled(firefox, context));
693+
assert.deepEqual(yield* listSourceProfiles(firefox, context), [
694+
{ directory, name: "Personal", cookieCount: 2 },
695+
]);
696+
assert.equal(
697+
yield* resolveCookieDatabase(firefox, context, directory),
698+
`${directory}/cookies.sqlite`,
699+
);
700+
assert.isFalse(yield* isSourceRunning(firefox, context));
701+
yield* fileSystem.symlink("foreign-host:+4242", `${directory}/lock`);
702+
assert.isTrue(yield* isSourceRunning(firefox, context));
703+
yield* fileSystem.remove(`${directory}/lock`);
704+
assert.isFalse(yield* isSourceRunning(firefox, context));
694705

695-
yield* fileSystem.remove(`${root}/profiles.ini`);
696-
assert.deepEqual(yield* listSourceProfiles(firefox, context), [
697-
{ directory, name: "abcd.default", cookieCount: 2 },
698-
]);
699-
}),
700-
),
706+
yield* fileSystem.remove(`${root}/profiles.ini`);
707+
assert.deepEqual(yield* listSourceProfiles(firefox, context), [
708+
{ directory, name: "abcd.default", cookieCount: 2 },
709+
]);
710+
}),
711+
),
701712
);
702713

703714
it.effect("keeps matching profile names in native and Snap installs distinct", () =>
@@ -862,7 +873,7 @@ describe("listSourceProfiles Firefox fallback", () => {
862873
});
863874

864875
describe("isSourceRunning for Firefox", () => {
865-
it.effect("finds the lock inside the profile, not at the root", () =>
876+
it.effect.skipIf(!symlinksSupported)("finds the lock inside the profile, not at the root", () =>
866877
run(
867878
Effect.gen(function* () {
868879
const fileSystem = yield* FileSystem.FileSystem;

0 commit comments

Comments
 (0)