* Run server tests on Linux+Windows matrix Replace the hand-curated Windows server test allow-list with a full-suite matrix run.\nBoth Ubuntu and Windows now run the same server test command with shared setup and secrets. * Add isPlatform helper and gate Windows-hostile server tests - Add a shared server test isPlatform helper. - Migrate existing Windows-gated spawn, worktree, executable, and worktree-core tests to the helper. - Gate Windows-hostile symlink, macOS path-normalization, and POSIX shell setup tests with skipIf. * Replace POSIX shell calls in test fixtures with Node primitives - Replaced test fixture mkdir/echo shell setup with fs mkdirSync/writeFileSync calls in touched server tests. - Replaced git shell strings with execFileSync/spawnSync argv calls across checkout, worktree, MCP, script, and workspace git fixtures. - Gated the directory suggestion symlink escape tests on Windows because those fixtures require POSIX symlink behavior. * Fix Windows server-test failures and split POSIX-only suites into sibling files - Fix Windows path/cwd assertions in terminal, session/workspace, git-service, checkout-git, MCP, logger, spawn, and registry bootstrap tests.\n- Keep terminal tests runnable on Windows by canonicalizing temp cwd fixtures and ensuring a Windows shell fallback.\n- Gate POSIX-only shell, signal, Unix socket, and git-worktree reuse fixtures that need dedicated Windows coverage later. * Move POSIX-only test blocks into sibling .posix.test.ts files - terminal.test.ts: moved PTY/bash interaction blocks into terminal.posix.test.ts. - worktree.test.ts: moved git-worktree and teardown shell blocks into worktree.posix.test.ts. - worktree-bootstrap.test.ts: moved setup shell and terminal-backed service blocks into worktree-bootstrap.posix.test.ts. - worktree-core.test.ts: moved the POSIX-only worktree-core suite into worktree-core.posix.test.ts and removed the empty original. - provider-availability.test.ts and file-explorer/service.test.ts: moved POSIX PATH/symlink blocks into sibling suites. * Fix Windows 8.3 short-name, EBUSY cleanup, and node-pty shell-path failures in server tests - Normalize temp home directories in directory suggestion assertions to avoid Windows short-name mismatches. - Use Windows-valid terminal shells/cwds in terminal fixtures and wait for terminal-manager PTYs before cleanup. - Replace hardcoded POSIX paths in workspace-git/MCP/loop fixtures with platform-resolved paths or command files. - Gate the two explicitly Linux-only workspace-git watcher tests. * Replace hardcoded POSIX paths with portable Node path constructions in server tests - checkout-git.test.ts and worktree-session.test.ts: compare Windows temp paths with realpathSync.native to avoid 8.3 short-name mismatches. - directory-suggestions.test.ts: canonicalize result and expected paths with realpathSync.native. - session.workspaces.test.ts: replace literal /tmp and /Users fixtures with path.resolve/path.join constructions. - workspace-git-service.primitive.test.ts, loop-service.test.ts, and mcp-server.test.ts: use canonical repo/temp paths and shell-safe relative verify commands. * Fix loop-service verify-check shell and workspace-git-service path-separator on Windows - Run the loop-service verify script through the current Node executable with a relative script path. - Normalize the workspace-git-service expected repo cwd to forward slashes for the listWorktrees assertion. * Fix loop-service worker PTY spawn on Windows - Canonicalize the loop-service temporary root and workspace with realpathSync.native before worker agents use the path as cwd. * Gate loop-service real-worker-PTY test on Windows - Skip the real worker PTY loop test on Windows after ConPTY path resolution still fails with node-pty error 267.\n- Keep the test running on POSIX so the loop behavior remains covered. * Stub getMetricsSnapshot in test mocks to suppress async-leak uncaught exceptions on Windows - Add a no-op AgentManager metrics snapshot to the WebSocket notification test server stub. * Fix terminal-manager Windows-path test to avoid PTY spawn into nonexistent dir - Use an existing temporary cwd for the createTerminal absolute-path validation assertion so node-pty does not spawn into a missing Windows directory.
192 lines
5.6 KiB
TypeScript
192 lines
5.6 KiB
TypeScript
import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
import { isPlatform } from "../test-utils/platform.js";
|
|
import { getWorktreeSetupCommands, getWorktreeTeardownCommands } from "./worktree.js";
|
|
import {
|
|
readPaseoConfigForEdit,
|
|
statPaseoConfigPath,
|
|
writePaseoConfigForEdit,
|
|
} from "./paseo-config-file.js";
|
|
|
|
describe("paseo config file substrate", () => {
|
|
let tempDir: string;
|
|
|
|
beforeEach(() => {
|
|
tempDir = realpathSync(mkdtempSync(join(tmpdir(), "paseo-config-file-test-")));
|
|
});
|
|
|
|
afterEach(() => {
|
|
rmSync(tempDir, { recursive: true, force: true });
|
|
});
|
|
|
|
it("returns null config and revision when paseo.json is missing", () => {
|
|
const result = readPaseoConfigForEdit(tempDir);
|
|
|
|
expect(result).toEqual({ ok: true, config: null, revision: null });
|
|
});
|
|
|
|
it("returns invalid_project_config for invalid JSON", () => {
|
|
writeFileSync(join(tempDir, "paseo.json"), "{ invalid json\n");
|
|
|
|
const result = readPaseoConfigForEdit(tempDir);
|
|
|
|
expect(result).toEqual({
|
|
ok: false,
|
|
error: { code: "invalid_project_config" },
|
|
});
|
|
});
|
|
|
|
it("preserves raw lifecycle string and array forms with a revision token", () => {
|
|
writeFileSync(
|
|
join(tempDir, "paseo.json"),
|
|
JSON.stringify({
|
|
worktree: {
|
|
setup: "npm install",
|
|
teardown: ["npm run clean", "npm run reset"],
|
|
},
|
|
}),
|
|
);
|
|
|
|
const result = readPaseoConfigForEdit(tempDir);
|
|
|
|
expect(result).toEqual({
|
|
ok: true,
|
|
config: {
|
|
worktree: {
|
|
setup: "npm install",
|
|
teardown: ["npm run clean", "npm run reset"],
|
|
},
|
|
},
|
|
revision: statPaseoConfigPath(tempDir),
|
|
});
|
|
});
|
|
|
|
it("keeps runtime lifecycle commands normalized for execution", () => {
|
|
writeFileSync(
|
|
join(tempDir, "paseo.json"),
|
|
JSON.stringify({
|
|
worktree: {
|
|
setup: "npm install",
|
|
teardown: ["npm run clean", "", 42, "npm run reset"],
|
|
},
|
|
}),
|
|
);
|
|
|
|
expect(getWorktreeSetupCommands(tempDir)).toEqual(["npm install"]);
|
|
expect(getWorktreeTeardownCommands(tempDir)).toEqual(["npm run clean", "npm run reset"]);
|
|
});
|
|
|
|
it("writes pretty JSON with a trailing newline when revision matches", () => {
|
|
writeFileSync(join(tempDir, "paseo.json"), JSON.stringify({ worktree: { setup: "old" } }));
|
|
const expectedRevision = statPaseoConfigPath(tempDir);
|
|
|
|
const result = writePaseoConfigForEdit({
|
|
repoRoot: tempDir,
|
|
config: { worktree: { setup: "npm install" } },
|
|
expectedRevision,
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
ok: true,
|
|
config: { worktree: { setup: "npm install" } },
|
|
revision: statPaseoConfigPath(tempDir),
|
|
});
|
|
expect(readFileSync(join(tempDir, "paseo.json"), "utf8")).toBe(
|
|
'{\n "worktree": {\n "setup": "npm install"\n }\n}\n',
|
|
);
|
|
});
|
|
|
|
// POSIX-only: Windows mtime granularity can collapse the two revisions in this fixture.
|
|
it.skipIf(isPlatform("win32"))(
|
|
"rejects stale writes when the current revision changed before rename",
|
|
() => {
|
|
writeFileSync(join(tempDir, "paseo.json"), JSON.stringify({ worktree: { setup: "old" } }));
|
|
const expectedRevision = statPaseoConfigPath(tempDir);
|
|
writeFileSync(join(tempDir, "paseo.json"), JSON.stringify({ worktree: { setup: "new" } }));
|
|
const currentRevision = statPaseoConfigPath(tempDir);
|
|
|
|
const result = writePaseoConfigForEdit({
|
|
repoRoot: tempDir,
|
|
config: { worktree: { setup: "from editor" } },
|
|
expectedRevision,
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
ok: false,
|
|
error: { code: "stale_project_config", currentRevision },
|
|
});
|
|
expect(readFileSync(join(tempDir, "paseo.json"), "utf8")).toBe(
|
|
JSON.stringify({ worktree: { setup: "new" } }),
|
|
);
|
|
},
|
|
);
|
|
|
|
it("round-trips unknown top-level, worktree, and script-entry fields", () => {
|
|
const config = {
|
|
extraTop: { keep: true },
|
|
worktree: {
|
|
setup: ["npm install"],
|
|
customWorktreeField: "preserve me",
|
|
},
|
|
scripts: {
|
|
dev: {
|
|
command: "npm run dev",
|
|
type: "service",
|
|
customScriptField: 123,
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = writePaseoConfigForEdit({
|
|
repoRoot: tempDir,
|
|
config,
|
|
expectedRevision: null,
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
ok: true,
|
|
config,
|
|
revision: statPaseoConfigPath(tempDir),
|
|
});
|
|
expect(readPaseoConfigForEdit(tempDir)).toEqual({
|
|
ok: true,
|
|
config,
|
|
revision: statPaseoConfigPath(tempDir),
|
|
});
|
|
});
|
|
|
|
it("returns write_failed for filesystem write exceptions", () => {
|
|
const fileRoot = join(tempDir, "not-a-directory");
|
|
writeFileSync(fileRoot, "file");
|
|
|
|
const result = writePaseoConfigForEdit({
|
|
repoRoot: fileRoot,
|
|
config: { worktree: { setup: "npm install" } },
|
|
expectedRevision: null,
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
ok: false,
|
|
error: { code: "write_failed" },
|
|
});
|
|
});
|
|
|
|
it("creates paseo.json when the file is still missing and expected revision is null", () => {
|
|
mkdirSync(join(tempDir, "nested"));
|
|
|
|
const result = writePaseoConfigForEdit({
|
|
repoRoot: join(tempDir, "nested"),
|
|
config: { scripts: { dev: { command: "npm run dev" } } },
|
|
expectedRevision: null,
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
ok: true,
|
|
config: { scripts: { dev: { command: "npm run dev" } } },
|
|
revision: statPaseoConfigPath(join(tempDir, "nested")),
|
|
});
|
|
});
|
|
});
|