Files
gstack/test/setup-windows-fallback.test.ts
T
Garry Tan f5c2fee3a9 test: wave coverage for sanitizer, link_or_copy, build script, doc drift
Four new test files (29 cases total):

browse/test/server-sanitize-surrogates.test.ts:
  - 11 unit cases for sanitizeLoneSurrogates (passthrough, valid pair,
    lone high/low mid-string, trailing/leading lone, adjacent doubles,
    pair-then-lone, lone-then-pair, empty)
  - 2 bug-repro tests pinning the regression intent (UTF-8 round-trip,
    JSON.parse round-trip with codepoint assertion)
  - 4 wiring invariants asserting the architectural choke points stay
    intact (handleCommandInternalImpl rename, central sanitization
    line, sanitizeReplacer function exists, SSE producers stringify
    with replacer)
  Function extracted from server.ts via regex + eval'd in test scope
  so no production-code export is needed.

test/setup-windows-fallback.test.ts:
  - Static invariant (D7): zero raw `ln` calls outside the
    _link_or_copy helper body and comments
  - Helper-existence assertions
  - 4-cell behavior matrix (file/dir × Windows/Unix) via awk-style
    helper extraction + bash -c sourcing
  - Windows-note printer registration check
  Mirrors test/setup-conductor-worktree.test.ts patterns.

test/build-script-shell-compat.test.ts:
  - Regex assertion that package.json scripts.* contain no bash brace
    groups (Bun-Windows-hostile)
  - Subshell-precedence check for `.version` redirects
  Strips single-quoted strings before regexing so embedded JS code
  inside echo '...' doesn't false-positive.

test/docs-config-keys.test.ts:
  - DEPRECATED_KEYS denylist scanned across docs/**/*.md
  - Round-trip test for `gstack-config get artifacts_sync_mode`
  Defends the v1.27.0.0 rename from doc drift.

Updates to two existing tests:
  - test/setup-conductor-worktree.test.ts: expect `_link_or_copy`
    instead of `ln -snf` at the Conductor-worktree guard call site
  - test/gen-skill-docs.test.ts: same swap at three assertion sites
    (Codex section, Claude link_claude_skill_dirs body, Codex
    link_codex_skill_dirs body)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 14:02:10 -07:00

123 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, test, expect } from 'bun:test';
import { spawnSync } from 'child_process';
import * as path from 'path';
import * as fs from 'fs';
import * as os from 'os';
const ROOT = path.resolve(import.meta.dir, '..');
const SETUP_SCRIPT = path.join(ROOT, 'setup');
const SETUP_SRC = fs.readFileSync(SETUP_SCRIPT, 'utf-8');
// Slice out the _link_or_copy helper body via awk-style anchors so the test is
// resilient to line-number drift.
function extractHelper(): string {
const start = SETUP_SRC.indexOf('_link_or_copy() {');
const end = SETUP_SRC.indexOf('\n}\n', start);
if (start < 0 || end < 0) throw new Error('Could not locate _link_or_copy() in setup');
return SETUP_SRC.slice(start, end + 2);
}
describe('setup: _link_or_copy invariant (D7)', () => {
test('helper function is defined near the top of setup', () => {
expect(SETUP_SRC).toContain('_link_or_copy() {');
expect(SETUP_SRC).toContain('if [ "$IS_WINDOWS" -eq 1 ]; then');
});
test('zero raw `ln` calls outside the helper body and comments', () => {
// Pull the helper body out of the source first so its internal `ln -snf`
// (the Unix branch) is exempted from the invariant.
const helper = extractHelper();
const withoutHelper = SETUP_SRC.replace(helper, '');
// Strip shell comments to allow prose mentions of `ln -snf` in docstrings.
const lines = withoutHelper.split('\n');
const offending: { lineNo: number; line: string }[] = [];
lines.forEach((line, idx) => {
const trimmed = line.trim();
if (trimmed.startsWith('#')) return;
// Match standalone `ln ` invocations (allow `ln` as a substring in
// variable names like `linked`, `_LINK`).
if (/(^|[\s;&|`])ln\s+-/.test(line)) {
offending.push({ lineNo: idx + 1, line: line.trim() });
}
});
expect(offending).toEqual([]);
});
test('Windows-copy note message exists in setup', () => {
expect(SETUP_SRC).toContain('Windows install uses file copies');
expect(SETUP_SRC).toContain('_print_windows_copy_note_once');
});
test('link_claude_skill_dirs calls the Windows note printer', () => {
const fnStart = SETUP_SRC.indexOf('link_claude_skill_dirs() {');
const fnEnd = SETUP_SRC.indexOf('\n}\n', fnStart);
const fnBody = SETUP_SRC.slice(fnStart, fnEnd);
expect(fnBody).toContain('_print_windows_copy_note_once');
});
});
describe('setup: _link_or_copy helper — behavior matrix', () => {
// Source the helper into a temp shell with IS_WINDOWS set and exercise
// each cell of the file/dir × Windows/Unix matrix.
function runHelper(
isWindows: '0' | '1',
srcKind: 'file' | 'dir',
): { ok: boolean; targetIsSymlink: boolean; targetExists: boolean; stderr: string } {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-helper-'));
try {
const src = path.join(tmp, 'source');
const dst = path.join(tmp, 'dest');
if (srcKind === 'file') {
fs.writeFileSync(src, 'hello\n');
} else {
fs.mkdirSync(src);
fs.writeFileSync(path.join(src, 'inner.txt'), 'hello\n');
}
const helper = extractHelper();
// IS_WINDOWS must exist as a shell-readable var before sourcing.
const script = `IS_WINDOWS=${isWindows}\n${helper}\n_link_or_copy "${src}" "${dst}"\n`;
const result = spawnSync('bash', ['-c', script], {
encoding: 'utf-8',
timeout: 5000,
});
const lst = fs.lstatSync(dst, { throwIfNoEntry: false });
return {
ok: result.status === 0,
targetIsSymlink: lst?.isSymbolicLink() ?? false,
targetExists: lst !== undefined,
stderr: result.stderr,
};
} finally {
fs.rmSync(tmp, { recursive: true, force: true });
}
}
test('IS_WINDOWS=0 + file → symlink (existing Unix behavior)', () => {
const r = runHelper('0', 'file');
expect(r.ok).toBe(true);
expect(r.targetExists).toBe(true);
expect(r.targetIsSymlink).toBe(true);
});
test('IS_WINDOWS=0 + dir → symlink', () => {
const r = runHelper('0', 'dir');
expect(r.ok).toBe(true);
expect(r.targetIsSymlink).toBe(true);
});
test('IS_WINDOWS=1 + file → regular file copy (no symlink)', () => {
const r = runHelper('1', 'file');
expect(r.ok).toBe(true);
expect(r.targetExists).toBe(true);
expect(r.targetIsSymlink).toBe(false);
});
test('IS_WINDOWS=1 + dir → real directory copy', () => {
const r = runHelper('1', 'dir');
expect(r.ok).toBe(true);
expect(r.targetExists).toBe(true);
expect(r.targetIsSymlink).toBe(false);
});
});