Skip to content

Commit 2d82279

Browse files
committed
fix: make all unit tests cross-platform for Windows (#39)
Replace hardcoded forward-slash paths with path.join() in test assertions so path separators match on all platforms. Use process.platform for real-filesystem PATH discovery tests instead of hardcoding 'linux'. Skip the permission-based test on Windows where Unix file mode is not enforced. Fixes #39 Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
1 parent d5c2941 commit 2d82279

3 files changed

Lines changed: 45 additions & 38 deletions

File tree

test/unit/binary.test.ts

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ test("resolveManagedInstallPaths uses cargo-dist style archive names", () => {
219219

220220
assert.equal(paths.archiveFileName, "patchloom-aarch64-apple-darwin.tar.xz");
221221
assert.equal(paths.checksumFileName, "patchloom-aarch64-apple-darwin.tar.xz.sha256");
222-
assert.match(paths.binaryPath, /managed-bin\/patchloom$/);
222+
assert.equal(paths.binaryPath, path.join("/managed/install", "0.1.0", "managed-bin", "patchloom"));
223223
});
224224

225225
test("buildManagedInstallReleaseAssets builds archive and checksum urls", () => {
@@ -327,11 +327,11 @@ test("resolveManagedInstallTransactionPaths keeps staged files separate from the
327327
assert.ok(target);
328328
const paths = resolveManagedInstallTransactionPaths("/managed/install", "0.1.0", target);
329329

330-
assert.equal(paths.archivePath, "/managed/install/0.1.0/patchloom-aarch64-apple-darwin.tar.xz");
331-
assert.equal(paths.stagedArchivePath, "/managed/install/0.1.0/.staging/patchloom-aarch64-apple-darwin.tar.xz");
332-
assert.equal(paths.stagedChecksumPath, "/managed/install/0.1.0/.staging/patchloom-aarch64-apple-darwin.tar.xz.sha256");
333-
assert.equal(paths.stagedBinaryPath, "/managed/install/0.1.0/.staging/managed-bin/patchloom");
334-
assert.equal(paths.backupBinaryPath, "/managed/install/0.1.0/managed-bin/patchloom.bak");
330+
assert.equal(paths.archivePath, path.join("/managed/install", "0.1.0", "patchloom-aarch64-apple-darwin.tar.xz"));
331+
assert.equal(paths.stagedArchivePath, path.join("/managed/install", "0.1.0", ".staging", "patchloom-aarch64-apple-darwin.tar.xz"));
332+
assert.equal(paths.stagedChecksumPath, path.join("/managed/install", "0.1.0", ".staging", "patchloom-aarch64-apple-darwin.tar.xz.sha256"));
333+
assert.equal(paths.stagedBinaryPath, path.join("/managed/install", "0.1.0", ".staging", "managed-bin", "patchloom"));
334+
assert.equal(paths.backupBinaryPath, `${path.join("/managed/install", "0.1.0", "managed-bin", "patchloom")}.bak`);
335335
});
336336

337337
test("inspectManagedInstallStatus includes the last managed install failure for diagnostics", async () => {
@@ -353,7 +353,7 @@ test("inspectManagedInstallStatus includes the last managed install failure for
353353

354354
assert.deepEqual(status, {
355355
exists: false,
356-
binaryPath: "/managed/install/0.1.0/managed-bin/patchloom",
356+
binaryPath: path.join("/managed/install", "0.1.0", "managed-bin", "patchloom"),
357357
version: "0.1.0",
358358
target,
359359
failure: {
@@ -381,7 +381,7 @@ test("clearManagedInstallStaging removes the entire staging directory", async ()
381381
});
382382

383383
assert.deepEqual(operations, [
384-
"rmdir /managed/install/0.1.0/.staging"
384+
`rmdir ${paths.stagingRoot}`
385385
]);
386386
});
387387

@@ -420,12 +420,12 @@ test("promoteManagedInstallBinary replaces the live binary and clears stale back
420420
});
421421

422422
assert.deepEqual(operations, [
423-
"mkdir /managed/install/0.1.0/managed-bin",
424-
"remove /managed/install/0.1.0/managed-bin/patchloom.bak",
425-
"rename /managed/install/0.1.0/managed-bin/patchloom -> /managed/install/0.1.0/managed-bin/patchloom.bak",
426-
"rename /managed/install/0.1.0/.staging/managed-bin/patchloom -> /managed/install/0.1.0/managed-bin/patchloom",
427-
"remove /managed/install/0.1.0/managed-bin/patchloom.bak",
428-
`remove /managed/storage/${PATCHLOOM_MANAGED_INSTALL_FAILURE_FILE}`
423+
`mkdir ${path.dirname(paths.binaryPath)}`,
424+
`remove ${paths.backupBinaryPath}`,
425+
`rename ${paths.binaryPath} -> ${paths.backupBinaryPath}`,
426+
`rename ${paths.stagedBinaryPath} -> ${paths.binaryPath}`,
427+
`remove ${paths.backupBinaryPath}`,
428+
`remove ${path.join("/managed/storage", PATCHLOOM_MANAGED_INSTALL_FAILURE_FILE)}`
429429
]);
430430

431431
const status = await inspectManagedInstallStatus({
@@ -481,13 +481,14 @@ test("promoteManagedInstallBinary restores the previous binary when replacement
481481
);
482482

483483
try {
484+
const failurePath = path.join("/managed/storage", PATCHLOOM_MANAGED_INSTALL_FAILURE_FILE);
484485
assert.deepEqual(operations, [
485-
"mkdir /managed/install/0.1.0/managed-bin",
486-
"rename /managed/install/0.1.0/managed-bin/patchloom -> /managed/install/0.1.0/managed-bin/patchloom.bak",
487-
"rename /managed/install/0.1.0/.staging/managed-bin/patchloom -> /managed/install/0.1.0/managed-bin/patchloom",
488-
"rename /managed/install/0.1.0/managed-bin/patchloom.bak -> /managed/install/0.1.0/managed-bin/patchloom",
489-
"mkdir /managed/storage",
490-
`write /managed/storage/${PATCHLOOM_MANAGED_INSTALL_FAILURE_FILE} => {\n \"stage\": \"replace\",\n \"reason\": \"replace-failed\",\n \"message\": \"Failed to replace managed Patchloom binary (simulated rename failure).\"\n}`
486+
`mkdir ${path.dirname(paths.binaryPath)}`,
487+
`rename ${paths.binaryPath} -> ${paths.backupBinaryPath}`,
488+
`rename ${paths.stagedBinaryPath} -> ${paths.binaryPath}`,
489+
`rename ${paths.backupBinaryPath} -> ${paths.binaryPath}`,
490+
`mkdir ${path.dirname(failurePath)}`,
491+
`write ${failurePath} => {\n \"stage\": \"replace\",\n \"reason\": \"replace-failed\",\n \"message\": \"Failed to replace managed Patchloom binary (simulated rename failure).\"\n}`
491492
]);
492493

493494
const status = await inspectManagedInstallStatus({
@@ -510,16 +511,17 @@ test("promoteManagedInstallBinary restores the previous binary when replacement
510511
test("inspectManagedInstallStatus reports discovered managed binaries", async () => {
511512
const target = detectManagedInstallTarget("darwin", "arm64");
512513
assert.ok(target);
514+
const expectedBinaryPath = path.join("/managed/install", "0.1.0", "managed-bin", "patchloom");
513515
const status = await inspectManagedInstallStatus({
514516
installRoot: "/managed/install",
515517
version: "v0.1.0",
516518
target,
517-
fileExists: async (filePath) => filePath.endsWith("/0.1.0/managed-bin/patchloom")
519+
fileExists: async (filePath) => filePath === expectedBinaryPath
518520
});
519521

520522
assert.deepEqual(status, {
521523
exists: true,
522-
binaryPath: "/managed/install/0.1.0/managed-bin/patchloom",
524+
binaryPath: expectedBinaryPath,
523525
version: "0.1.0",
524526
target
525527
});
@@ -531,7 +533,7 @@ test("loadManagedInstallFailure reads persisted failure diagnostics from storage
531533
const failure = await loadManagedInstallFailure({
532534
storageRoot: "/managed/storage",
533535
readFile: async (filePath) => {
534-
assert.equal(filePath, `/managed/storage/${PATCHLOOM_MANAGED_INSTALL_FAILURE_FILE}`);
536+
assert.equal(filePath, path.join("/managed/storage", PATCHLOOM_MANAGED_INSTALL_FAILURE_FILE));
535537
return JSON.stringify({
536538
stage: "verify",
537539
reason: "checksum-mismatch",
@@ -572,10 +574,11 @@ test("persistManagedInstallFailure and clearManagedInstallFailureRecord update t
572574
}
573575
});
574576

577+
const failurePath = path.join("/managed/storage", PATCHLOOM_MANAGED_INSTALL_FAILURE_FILE);
575578
assert.deepEqual(writes, [
576-
"mkdir /managed/storage",
577-
`write /managed/storage/${PATCHLOOM_MANAGED_INSTALL_FAILURE_FILE} => {\n \"stage\": \"extract\",\n \"reason\": \"extract-failed\",\n \"message\": \"Archive extraction failed.\"\n}`,
578-
`remove /managed/storage/${PATCHLOOM_MANAGED_INSTALL_FAILURE_FILE}`
579+
`mkdir ${path.dirname(failurePath)}`,
580+
`write ${failurePath} => {\n \"stage\": \"extract\",\n \"reason\": \"extract-failed\",\n \"message\": \"Archive extraction failed.\"\n}`,
581+
`remove ${failurePath}`
579582
]);
580583
assert.equal(await loadManagedInstallFailure({}), undefined);
581584
});
@@ -630,21 +633,22 @@ test("normalizeReleaseVersion removes a leading v", () => {
630633
});
631634

632635
test("resolvePatchloomStatusWithInputs falls back to a managed install when present", async () => {
636+
const expectedBinaryPath = path.join("/managed/install", "0.1.0", "managed-bin", "patchloom");
633637
const status = await resolvePatchloomStatusWithInputs({
634638
configuredPath: "",
635639
pathValue: "/usr/local/bin:/bin",
636640
platform: "darwin",
637641
arch: "arm64",
638642
managedInstallRoot: "/managed/install",
639643
managedInstallVersion: "0.1.0",
640-
managedFileExists: async (filePath) => filePath.endsWith("/0.1.0/managed-bin/patchloom"),
641-
canExecute: async (candidate) => candidate === "/managed/install/0.1.0/managed-bin/patchloom",
644+
managedFileExists: async (filePath) => filePath === expectedBinaryPath,
645+
canExecute: async (candidate) => candidate === expectedBinaryPath,
642646
getVersion: async () => "patchloom 0.1.0"
643647
});
644648

645649
assert.equal(status.ready, true);
646650
assert.equal(status.source, "managed");
647-
assert.equal(status.binaryPath, "/managed/install/0.1.0/managed-bin/patchloom");
651+
assert.equal(status.binaryPath, expectedBinaryPath);
648652
assert.equal(status.managedInstall?.exists, true);
649653
});
650654

test/unit/binaryDiscovery.test.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,17 @@ test("findOnPath discovers a real executable in a temp directory", async () => {
2525
const fakeBinary = path.join(dir, "patchloom");
2626
await fs.writeFile(fakeBinary, "#!/bin/sh\necho patchloom 0.1.0\n", { mode: 0o755 });
2727

28-
const found = await findOnPath(dir, "linux");
28+
const found = await findOnPath(dir, process.platform);
2929
assert.equal(found, fakeBinary);
3030
});
3131
});
3232

33-
test("findOnPath skips non-executable files", async () => {
33+
test("findOnPath skips non-executable files", { skip: process.platform === "win32" ? "Windows does not enforce Unix file permissions" : undefined }, async () => {
3434
await withTempDir(async (dir) => {
3535
const fakeBinary = path.join(dir, "patchloom");
3636
await fs.writeFile(fakeBinary, "#!/bin/sh\necho patchloom 0.1.0\n", { mode: 0o644 });
3737

38-
const found = await findOnPath(dir, "linux");
38+
const found = await findOnPath(dir, process.platform);
3939
assert.equal(found, undefined);
4040
});
4141
});
@@ -50,7 +50,8 @@ test("findOnPath searches multiple PATH directories in order", async () => {
5050
await fs.writeFile(path.join(firstDir, "patchloom"), "#!/bin/sh\necho first\n", { mode: 0o755 });
5151
await fs.writeFile(path.join(secondDir, "patchloom"), "#!/bin/sh\necho second\n", { mode: 0o755 });
5252

53-
const found = await findOnPath(`${firstDir}:${secondDir}`, "linux");
53+
const pathSep = process.platform === "win32" ? ";" : ":";
54+
const found = await findOnPath(`${firstDir}${pathSep}${secondDir}`, process.platform);
5455
assert.equal(found, path.join(firstDir, "patchloom"), "should find the first match in PATH order");
5556
});
5657
});
@@ -66,7 +67,8 @@ test("findOnPath deduplicates PATH entries", async () => {
6667
await fs.writeFile(fakeBinary, "#!/bin/sh\necho v1\n", { mode: 0o755 });
6768
let checkCount = 0;
6869

69-
const found = await findOnPath(`${dir}:${dir}:${dir}`, "linux", async (candidate) => {
70+
const pathSep = process.platform === "win32" ? ";" : ":";
71+
const found = await findOnPath(`${dir}${pathSep}${dir}${pathSep}${dir}`, process.platform, async (candidate) => {
7072
checkCount++;
7173
try {
7274
await fs.access(candidate, fs.constants.X_OK);

test/unit/initializeProject.test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import assert from "node:assert/strict";
2+
import * as path from "node:path";
23
import test from "node:test";
34
import { MINIMUM_SUPPORTED_PATCHLOOM_VERSION } from "../../src/binary/patchloom.js";
45
import { classifyAgentsFile, generateAgentRules } from "../../src/commands/initializeProject.js";
@@ -290,7 +291,7 @@ test("inspectMcpTargets reports configured targets", async () => {
290291
workspaceFolderPath: "/workspace/demo",
291292
homeDir: "/Users/demo",
292293
readFile: async (filePath) => {
293-
if (filePath.endsWith(".vscode/mcp.json")) {
294+
if (filePath.endsWith(path.join(".vscode", "mcp.json"))) {
294295
return '{"servers":{"patchloom":{"command":"patchloom","args":["mcp-server"]}}}';
295296
}
296297
return undefined;
@@ -317,7 +318,7 @@ test("configureMcpTargets creates or updates only the selected target kinds", as
317318
includeKinds: ["cursor-workspace"],
318319
patchloomPathSetting: "/custom/patchloom",
319320
readFile: async (filePath) => {
320-
if (filePath.endsWith(".cursor/mcp.json")) {
321+
if (filePath.endsWith(path.join(".cursor", "mcp.json"))) {
321322
return '{"servers":{"other":{"command":"other"}}}';
322323
}
323324
return undefined;
@@ -328,9 +329,9 @@ test("configureMcpTargets creates or updates only the selected target kinds", as
328329
});
329330

330331
assert.equal(results.length, 1);
331-
const cursorPath = "/workspace/demo/.cursor/mcp.json";
332+
const cursorPath = path.join("/workspace/demo", ".cursor", "mcp.json");
332333
assert.equal(writes.has(cursorPath), true);
333-
assert.equal(writes.has("/workspace/demo/.vscode/mcp.json"), false);
334+
assert.equal(writes.has(path.join("/workspace/demo", ".vscode", "mcp.json")), false);
334335
assert.match(writes.get(cursorPath) ?? "", /patchloom/);
335336
assert.match(writes.get(cursorPath) ?? "", /mcp-server/);
336337
assert.match(writes.get(cursorPath) ?? "", /other/);

0 commit comments

Comments
 (0)