Files
msd-core/tests/bug-2957-claude-global-postinstall-message.test.cjs
Tom Boucher 23b52f1a14 fix(3597): windows test parity batch — CRLF parsers, ESM file URLs, posix-tmp, bash-only skips, retry bump
Six independent windows-only failure clusters identified from the
windows-22 CI log on 1be0e4e2:

- scripts/command-contract-helpers.cjs: parseFrontmatter split on `\n`
  → on Windows checkout (autocrlf=true) every line carries trailing \r,
  lines.indexOf('---', 1) returns -1, all fields read as missing. Drove
  the bulk of three "67 subtests failed" suite-level errors covering
  workstreams.md, workspace.md, verify-work.md, add-tests.md, etc.
  Fix: split(/\r?\n/).

- tests/enh-3271-sdk-adr-structure.test.cjs: same CRLF pattern — H2
  captures pulled '\r' into headings like "decision\r", breaking
  equality checks on "## Decision" / "## Consequences". Fix: same.

- tests/runtime-bridge-sync-smoke.test.cjs: await import(BRIDGE_PATH)
  passed a Windows absolute path to Node's ESM loader, which rejects
  with "Only URLs with a scheme in: file, data, and node are
  supported." Fix: wrap once at module scope with pathToFileURL.
  pathToFileURL is a no-op for POSIX absolute paths.

- tests/bug-2957-claude-global-postinstall-message.test.cjs: hardcoded
  '/tmp/gsd-test-settings.json' resolved to D:\tmp\... on Windows
  where the parent dir doesn't exist → ENOENT on fs.writeFileSync
  inside finishInstall. Fix: os.tmpdir() + pid suffix.

- tests/bug-2774-worktree-cleanup-workspace-safety.test.cjs: the
  "while/read loop" subtest and the "end-to-end against real git
  worktrees" describe both assert POSIX shell behavior (process
  substitution `< <(...)`, RUNNER~1 8.3-shortname mismatch). Skip on
  win32 with explicit reasons (satisfies the
  no-unconditional-win32-skip guard).

- tests/bug-2838-summary-rescue-gitignored-planning.test.cjs: entire
  describe extracts bash rescue blocks from workflow .md files and
  runs them; the shell contract itself is the test's point. Skip on
  win32 with reason.

- tests/helpers.cjs cleanup(): bumped rmSync retry budget from
  10×100ms to 20×250ms — 1s wasn't enough for Windows Defender's
  deferred handle release; bumping to 5s should absorb the residual
  EBUSY failures observed in bug-1736 / bug-2248 / bug-2698 after the
  first retry bump landed in 1be0e4e2.

Validated: holodeck (ubuntu docker) 11224/0 pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 12:03:44 -04:00

73 lines
2.7 KiB
JavaScript

'use strict';
process.env.GSD_TEST_MODE = '1';
/**
* Bug #2957: post-install message for `--claude --global` must instruct
* users to restart Claude Code and offer the skill-name fallback, since
* the skills-only install layout (CC 2.1.88+) leaves nothing in
* commands/gsd/ for the slash menu to read on older configurations.
*
* Captures the call to finishInstall(runtime='claude', isGlobal=true) and
* asserts the printed message contains both invocation paths.
*/
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const path = require('node:path');
const os = require('node:os');
const ROOT = path.join(__dirname, '..');
const SETTINGS_PATH = path.join(os.tmpdir(), `gsd-test-settings-${process.pid}.json`);
const installModule = require(path.join(ROOT, 'bin', 'install.js'));
function captureFinishInstallOutput(runtime, isGlobal) {
const original = console.log;
const lines = [];
console.log = (...args) => { lines.push(args.join(' ')); };
try {
installModule.finishInstall(
SETTINGS_PATH,
{},
null,
false,
runtime,
isGlobal,
null,
);
} finally {
console.log = original;
}
// Strip ANSI color escapes so message-content assertions don't couple to colors.
return lines.join('\n').replace(/\x1B\[[0-9;]*m/g, '');
}
describe('Bug #2957: claude+global post-install message', () => {
test('claude+global message tells the user to restart and offers skill-name fallback', () => {
const output = captureFinishInstallOutput('claude', true);
assert.match(output, /restart claude code/i, 'should mention restart');
assert.match(output, /\/gsd-new-project/, 'should still mention /gsd-new-project');
assert.match(output, /gsd-new-project skill/i, 'should mention the skill name fallback');
assert.doesNotMatch(
output,
/open a blank directory/i,
'global claude install should replace, not extend, the legacy generic instruction',
);
});
test('claude+local message keeps the original /gsd-new-project instruction', () => {
const output = captureFinishInstallOutput('claude', false);
assert.match(output, /\/gsd-new-project/, 'should still mention /gsd-new-project');
assert.doesNotMatch(output, /restart claude code/i, 'local install does not require the skills restart note');
});
test('non-claude runtimes keep their original message format', () => {
const output = captureFinishInstallOutput('opencode', true);
assert.match(output, /Open a blank directory/, 'opencode message should be unchanged');
assert.doesNotMatch(output, /restart/i, 'opencode message should not have the claude-specific restart note');
});
});