History

test: stop path and platform tests depending on the host OS (#9564)

Julius Marminge committed 2 months ago

Commit cc607531 parent bfef97310 files · +65 −33Browse files

Unified · Split

10 files
  1. apps/desktop/src/app/DesktopAppIdentity.test.ts +2 −0
  2. apps/desktop/src/app/DesktopAssets.test.ts +7 −1
  3. apps/desktop/src/app/DesktopEnvironment.test.ts +6 −1
  4. apps/server/src/auth/ServerSecretStore.test.ts +2 −2
  5. apps/server/src/project/ProjectFaviconResolver.test.ts +3 −2
  6. apps/server/src/provider/Drivers/ClaudeSkills.test.ts +13 −7
  7. packages/ssh/src/config.test.ts +2 −1
  8. packages/tailscale/src/tailscale.test.ts +14 −12
  9. scripts/mobile-showcase.test.ts +14 −6
  10. scripts/mobile-showcase.ts +2 −1
apps/desktop/src/app/DesktopAppIdentity.test.ts+2−0
@@ -1,3 +1,4 @@
1import * as NodePath from "@effect/platform-node/NodePath";
1import * as NodeServices from "@effect/platform-node/NodeServices";2import * as NodeServices from "@effect/platform-node/NodeServices";
2import { assert, describe, it } from "@effect/vitest";3import { assert, describe, it } from "@effect/vitest";
3import * as Effect from "effect/Effect";4import * as Effect from "effect/Effect";
@@ -88,6 +89,7 @@ const makeEnvironmentLayer = (overrides: TestEnvironmentInput = {}) => { · 84 unmodified lines
88 Layer.provide(89 Layer.provide(
89 Layer.mergeAll(90 Layer.mergeAll(
90 NodeServices.layer,91 NodeServices.layer,
92 NodePath.layerPosix,
91 DesktopConfig.layerTest({93 DesktopConfig.layerTest({
92 ...env,94 ...env,
93 }),95 }),
apps/desktop/src/app/DesktopAssets.test.ts+7−1
@@ -1,3 +1,4 @@
1import * as NodePath from "@effect/platform-node/NodePath";
1import * as NodeServices from "@effect/platform-node/NodeServices";2import * as NodeServices from "@effect/platform-node/NodeServices";
2import { assert, describe, it } from "@effect/vitest";3import { assert, describe, it } from "@effect/vitest";
3import * as Effect from "effect/Effect";4import * as Effect from "effect/Effect";
@@ -20,7 +21,11 @@ const environmentLayer = DesktopEnvironment.layer({ · 16 unmodified lines
20 isPackaged: true,21 isPackaged: true,
21 resourcesPath: "/Applications/T3 Code.app/Contents/Resources",22 resourcesPath: "/Applications/T3 Code.app/Contents/Resources",
22 runningUnderArm64Translation: false,23 runningUnderArm64Translation: false,
23}).pipe(Layer.provide(Layer.mergeAll(NodeServices.layer, DesktopConfig.layerTest({}))));24}).pipe(
25 Layer.provide(
26 Layer.mergeAll(NodeServices.layer, NodePath.layerPosix, DesktopConfig.layerTest({})),
27 ),
28);
24 29
25describe("DesktopAssets", () => {30describe("DesktopAssets", () => {
26 it.effect("uses canonical source-tree icons for unpackaged development", () =>31 it.effect("uses canonical source-tree icons for unpackaged development", () =>
@@ -39,6 +44,7 @@ describe("DesktopAssets", () => { · 12 unmodified lines
39 Layer.provide(44 Layer.provide(
40 Layer.mergeAll(45 Layer.mergeAll(
41 NodeServices.layer,46 NodeServices.layer,
47 NodePath.layerPosix,
42 DesktopConfig.layerTest({ VITE_DEV_SERVER_URL: "http://localhost:5733" }),48 DesktopConfig.layerTest({ VITE_DEV_SERVER_URL: "http://localhost:5733" }),
43 ),49 ),
44 ),50 ),
apps/desktop/src/app/DesktopEnvironment.test.ts+6−1
@@ -1,3 +1,4 @@
1import * as NodePath from "@effect/platform-node/NodePath";
1import * as NodeServices from "@effect/platform-node/NodeServices";2import * as NodeServices from "@effect/platform-node/NodeServices";
2import { assert, describe, it } from "@effect/vitest";3import { assert, describe, it } from "@effect/vitest";
3import * as Effect from "effect/Effect";4import * as Effect from "effect/Effect";
@@ -26,7 +27,11 @@ const makeEnvironmentLayer = ( · 22 unmodified lines
26 DesktopEnvironment.layer({27 DesktopEnvironment.layer({
27 ...defaultInput,28 ...defaultInput,
28 ...overrides,29 ...overrides,
29 }).pipe(Layer.provide(Layer.mergeAll(NodeServices.layer, DesktopConfig.layerTest(env))));30 }).pipe(
31 Layer.provide(
32 Layer.mergeAll(NodeServices.layer, NodePath.layerPosix, DesktopConfig.layerTest(env)),
33 ),
34 );
30 35
31const makeEnvironment = (36const makeEnvironment = (
32 overrides: Partial<DesktopEnvironment.MakeDesktopEnvironmentInput> = {},37 overrides: Partial<DesktopEnvironment.MakeDesktopEnvironmentInput> = {},
apps/server/src/auth/ServerSecretStore.test.ts+2−2
@@ -109,7 +109,7 @@ const ConcurrentReadMissFileSystemLayer = Layer.effect( · 108 unmodified lines
109 return {109 return {
110 ...fileSystem,110 ...fileSystem,
111 readFile: (path) =>111 readFile: (path) =>
112 String(path).endsWith("/session-signing-key.bin")112 /[\\/]session-signing-key\.bin$/.test(String(path))
113 ? Ref.updateAndGet(readCountRef, (count) => count + 1).pipe(113 ? Ref.updateAndGet(readCountRef, (count) => count + 1).pipe(
114 Effect.flatMap((count) => {114 Effect.flatMap((count) => {
115 if (count > 2) {115 if (count > 2) {
@@ -219,7 +219,7 @@ it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => { · 103 unmodified lines
219 yield* secretStore.set("session-signing-key", Uint8Array.from([1, 2, 3]));219 yield* secretStore.set("session-signing-key", Uint8Array.from([1, 2, 3]));
220 220
221 assert.isTrue(221 assert.isTrue(
222 chmodCalls.some((call) => call.mode === 0o700 && call.path.endsWith("/secrets")),222 chmodCalls.some((call) => call.mode === 0o700 && /[\\/]secrets$/.test(call.path)),
223 );223 );
224 assert.isAtLeast(chmodCalls.filter((call) => call.mode === 0o600).length, 2);224 assert.isAtLeast(chmodCalls.filter((call) => call.mode === 0o600).length, 2);
225 }).pipe(Effect.provide(NodeServices.layer)),225 }).pipe(Effect.provide(NodeServices.layer)),
apps/server/src/project/ProjectFaviconResolver.test.ts+3−2
@@ -54,11 +54,12 @@ it.layer(TestLayer)("ProjectFaviconResolverLive", (it) => { · 53 unmodified lines
54 it.effect("serves repeated resolves from cache instead of re-walking candidates", () =>54 it.effect("serves repeated resolves from cache instead of re-walking candidates", () =>
55 Effect.gen(function* () {55 Effect.gen(function* () {
56 const resolver = yield* ProjectFaviconResolver.ProjectFaviconResolver;56 const resolver = yield* ProjectFaviconResolver.ProjectFaviconResolver;
57 const path = yield* Path.Path;
57 const cwd = yield* makeTempDir;58 const cwd = yield* makeTempDir;
58 yield* writeTextFile(cwd, "public/favicon.svg", "<svg>public</svg>");59 yield* writeTextFile(cwd, "public/favicon.svg", "<svg>public</svg>");
59 60
60 const resolved = yield* resolver.resolvePath(cwd);61 const resolved = yield* resolver.resolvePath(cwd);
61 expect(resolved?.endsWith("public/favicon.svg")).toBe(true);62 expect(resolved?.endsWith(path.join("public", "favicon.svg"))).toBe(true);
62 63
63 // `favicon.svg` outranks `public/favicon.svg`, so a resolver that walked64 // `favicon.svg` outranks `public/favicon.svg`, so a resolver that walked
64 // the candidate list again would switch to it. Staying on the original65 // the candidate list again would switch to it. Staying on the original
@@ -71,7 +72,7 @@ it.layer(TestLayer)("ProjectFaviconResolverLive", (it) => { · 6 unmodified lines
71 72
72 yield* TestClock.adjust(Duration.minutes(11));73 yield* TestClock.adjust(Duration.minutes(11));
73 74
74 expect((yield* resolver.resolvePath(cwd))?.endsWith("/favicon.svg")).toBe(true);75 expect(yield* resolver.resolvePath(cwd)).toBe(path.join(cwd, "favicon.svg"));
75 expect(yield* resolver.resolvePath(cwd)).not.toBe(resolved);76 expect(yield* resolver.resolvePath(cwd)).not.toBe(resolved);
76 }).pipe(Effect.provide(TestClock.layer())),77 }).pipe(Effect.provide(TestClock.layer())),
77 );78 );
apps/server/src/provider/Drivers/ClaudeSkills.test.ts+13−7
@@ -1,3 +1,4 @@
1import * as NodePath from "@effect/platform-node/NodePath";
1import * as NodeServices from "@effect/platform-node/NodeServices";2import * as NodeServices from "@effect/platform-node/NodeServices";
2import { assert, it } from "@effect/vitest";3import { assert, it } from "@effect/vitest";
3import * as Effect from "effect/Effect";4import * as Effect from "effect/Effect";
@@ -492,7 +493,11 @@ it.layer(NodeServices.layer)("discoverClaudeSkills", (it) => { · 488 unmodified lines
492 493
493 it.effect("lets the administrator's managed policy outrank every other settings file", () =>494 it.effect("lets the administrator's managed policy outrank every other settings file", () =>
494 Effect.gen(function* () {495 Effect.gen(function* () {
495 const path = yield* Path.Path;496 // The function is pure on its `path` argument, so hand it the
497 // implementation matching each platform under test rather than the
498 // host's.
499 const path = yield* Path.Path.pipe(Effect.provide(NodePath.layerPosix));
500 const win32Path = yield* Path.Path.pipe(Effect.provide(NodePath.layerWin32));
496 501
497 for (const [platform, expected] of [502 for (const [platform, expected] of [
498 ["darwin", "/Library/Application Support/ClaudeCode/managed-settings.json"],503 ["darwin", "/Library/Application Support/ClaudeCode/managed-settings.json"],
@@ -508,14 +513,15 @@ it.layer(NodeServices.layer)("discoverClaudeSkills", (it) => { · 9 unmodified lines
508 }513 }
509 514
510 assert.deepEqual(515 assert.deepEqual(
511 skillOverrideSettingsPaths(path, "/home/.claude", undefined, "win32", {516 skillOverrideSettingsPaths(win32Path, "C:\\Users\\me\\.claude", undefined, "win32", {
512 PROGRAMDATA: "C:/ProgramData",517 PROGRAMDATA: "C:\\ProgramData",
513 }).at(-1),518 }).at(-1),
514 "C:/ProgramData/ClaudeCode/managed-settings.json",519 "C:\\ProgramData\\ClaudeCode\\managed-settings.json",
520 );
521 assert.deepEqual(
522 skillOverrideSettingsPaths(win32Path, "C:\\Users\\me\\.claude", undefined, "win32", {}),
523 ["C:\\Users\\me\\.claude\\settings.json"],
515 );524 );
516 assert.deepEqual(skillOverrideSettingsPaths(path, "/home/.claude", undefined, "win32", {}), [
517 "/home/.claude/settings.json",
518 ]);
519 525
520 // Only the repository root's local file joins in, after the526 // Only the repository root's local file joins in, after the
521 // workspace's own local file so it wins.527 // workspace's own local file so it wins.
packages/ssh/src/config.test.ts+2−1
@@ -1,3 +1,4 @@
1import * as NodePath from "@effect/platform-node/NodePath";
1import * as NodeServices from "@effect/platform-node/NodeServices";2import * as NodeServices from "@effect/platform-node/NodeServices";
2import { assert, describe, it } from "@effect/vitest";3import { assert, describe, it } from "@effect/vitest";
3import * as Effect from "effect/Effect";4import * as Effect from "effect/Effect";
@@ -133,6 +134,6 @@ describe("ssh config", () => { · 129 unmodified lines
133 "/tmp/home",134 "/tmp/home",
134 );135 );
135 assert.equal(pattern, "/tmp/home/.ssh/config.d/*.conf");136 assert.equal(pattern, "/tmp/home/.ssh/config.d/*.conf");
136 }).pipe(Effect.provide(NodeServices.layer)),137 }).pipe(Effect.provide(NodePath.layerPosix)),
137 );138 );
138});139});
packages/tailscale/src/tailscale.test.ts+14−12
@@ -8,6 +8,7 @@ import * as Sink from "effect/Sink"; · 7 unmodified lines
8import * as Stream from "effect/Stream";8import * as Stream from "effect/Stream";
9import * as TestClock from "effect/testing/TestClock";9import * as TestClock from "effect/testing/TestClock";
10import { ChildProcessSpawner } from "effect/unstable/process";10import { ChildProcessSpawner } from "effect/unstable/process";
11import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
11 12
12import {13import {
13 buildTailscaleHttpsBaseUrl,14 buildTailscaleHttpsBaseUrl,
@@ -98,14 +99,22 @@ function neverFinishingMockHandle() { · 84 unmodified lines
98 });99 });
99}100}
100 101
102// The executable name depends on the host platform (`tailscale.exe` on
103// Windows), so pin it: these tests assert the posix spelling.
104function spawnerLayer(spawner: ChildProcessSpawner.ChildProcessSpawner["Service"]) {
105 return Layer.merge(
106 Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner),
107 Layer.succeed(HostProcessPlatform, "linux"),
108 );
109}
110
101function mockSpawnerLayer(111function mockSpawnerLayer(
102 handler: (112 handler: (
103 command: string,113 command: string,
104 args: ReadonlyArray<string>,114 args: ReadonlyArray<string>,
105 ) => { stdout?: string; stderr?: string; code?: number },115 ) => { stdout?: string; stderr?: string; code?: number },
106) {116) {
107 return Layer.succeed(117 return spawnerLayer(
108 ChildProcessSpawner.ChildProcessSpawner,
109 ChildProcessSpawner.make((command) => {118 ChildProcessSpawner.make((command) => {
110 const childProcess = command as unknown as {119 const childProcess = command as unknown as {
111 readonly command: string;120 readonly command: string;
@@ -194,10 +203,7 @@ describe("tailscale", () => { · 82 unmodified lines
194 method: "spawn",203 method: "spawn",
195 cause: systemCause,204 cause: systemCause,
196 });205 });
197 const layer = Layer.succeed(206 const layer = spawnerLayer(ChildProcessSpawner.make(() => Effect.fail(cause)));
198 ChildProcessSpawner.ChildProcessSpawner,
199 ChildProcessSpawner.make(() => Effect.fail(cause)),
200 );
201 207
202 return Effect.gen(function* () {208 return Effect.gen(function* () {
203 const error = yield* readTailscaleStatus.pipe(Effect.flip, Effect.provide(layer));209 const error = yield* readTailscaleStatus.pipe(Effect.flip, Effect.provide(layer));
@@ -218,8 +224,7 @@ describe("tailscale", () => { · 14 unmodified lines
218 // inside an `Effect.callback` registration, so that throw arrives as a224 // inside an `Effect.callback` registration, so that throw arrives as a
219 // defect rather than a typed error - the shape reproduced here.225 // defect rather than a typed error - the shape reproduced here.
220 const defect = Object.assign(new Error("spawn tailscale ENOTDIR"), { code: "ENOTDIR" });226 const defect = Object.assign(new Error("spawn tailscale ENOTDIR"), { code: "ENOTDIR" });
221 const layer = Layer.succeed(227 const layer = spawnerLayer(
222 ChildProcessSpawner.ChildProcessSpawner,
223 ChildProcessSpawner.make(() =>228 ChildProcessSpawner.make(() =>
224 Effect.callback<never, never>(() => {229 Effect.callback<never, never>(() => {
225 throw defect;230 throw defect;
@@ -297,10 +302,7 @@ describe("tailscale", () => { · 71 unmodified lines
297 it.effect("times out tailscale status through TestClock", () => {302 it.effect("times out tailscale status through TestClock", () => {
298 const layer = Layer.merge(303 const layer = Layer.merge(
299 TestClock.layer(),304 TestClock.layer(),
300 Layer.succeed(305 spawnerLayer(ChildProcessSpawner.make(() => Effect.succeed(neverFinishingMockHandle()))),
301 ChildProcessSpawner.ChildProcessSpawner,
302 ChildProcessSpawner.make(() => Effect.succeed(neverFinishingMockHandle())),
303 ),
304 );306 );
305 307
306 return Effect.gen(function* () {308 return Effect.gen(function* () {
scripts/mobile-showcase.test.ts+14−6
@@ -1,3 +1,5 @@
1// @effect-diagnostics nodeBuiltinImport:off - expectations mirror the host path joins the script under test performs.
2import * as NodePath from "node:path";
1import { assert, it } from "@effect/vitest";3import { assert, it } from "@effect/vitest";
2import { PNG } from "pngjs";4import { PNG } from "pngjs";
3 5
@@ -167,8 +169,14 @@ it("expands both appearances into independent upload-ready directories", () => { · 163 unmodified lines
167 directory: showcaseCaptureDirectory("/captures", capture),169 directory: showcaseCaptureDirectory("/captures", capture),
168 })),170 })),
169 [171 [
170 { appearance: "light", directory: "/captures/apple/iphone-test/light/t3-code" },172 {
171 { appearance: "dark", directory: "/captures/apple/iphone-test/dark/t3-code" },173 appearance: "light",
174 directory: NodePath.join("/captures", "apple", "iphone-test", "light", "t3-code"),
175 },
176 {
177 appearance: "dark",
178 directory: NodePath.join("/captures", "apple", "iphone-test", "dark", "t3-code"),
179 },
172 ],180 ],
173 );181 );
174});182});
@@ -192,10 +200,10 @@ it("expands themes into independent upload-ready directories per appearance", () · 17 unmodified lines
192 showcaseCaptureDirectory("/captures", capture),200 showcaseCaptureDirectory("/captures", capture),
193 ),201 ),
194 [202 [
195 "/captures/apple/iphone-test/light/ocean",203 NodePath.join("/captures", "apple", "iphone-test", "light", "ocean"),
196 "/captures/apple/iphone-test/light/ember",204 NodePath.join("/captures", "apple", "iphone-test", "light", "ember"),
197 "/captures/apple/iphone-test/dark/ocean",205 NodePath.join("/captures", "apple", "iphone-test", "dark", "ocean"),
198 "/captures/apple/iphone-test/dark/ember",206 NodePath.join("/captures", "apple", "iphone-test", "dark", "ember"),
199 ],207 ],
200 );208 );
201});209});
scripts/mobile-showcase.ts+2−1
@@ -53,7 +53,8 @@ export function resolveAndroidSdkRoot( · 52 unmodified lines
53 const configured = environment.ANDROID_HOME ?? environment.ANDROID_SDK_ROOT;53 const configured = environment.ANDROID_HOME ?? environment.ANDROID_SDK_ROOT;
54 if (configured) return configured;54 if (configured) return configured;
55 const home = environment.HOME ?? environment.USERPROFILE ?? "";55 const home = environment.HOME ?? environment.USERPROFILE ?? "";
56 return NodePath.join(home, platform === "darwin" ? "Library/Android/sdk" : "Android/Sdk");56 const path = platform === "win32" ? NodePath.win32 : NodePath.posix;
57 return path.join(home, platform === "darwin" ? "Library/Android/sdk" : "Android/Sdk");
57}58}
58 59
59const ANDROID_SDK_ROOT = resolveAndroidSdkRoot(NodeProcess.env);60const ANDROID_SDK_ROOT = resolveAndroidSdkRoot(NodeProcess.env);