mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
41059d8e43
* fix(annotate): support Windows markdown paths in CLI annotate flow * fix tmp path * fix resolve-file.ts * Update command to use EXE_PATH variable * Update command path for plugin hooks in install.ps1 * test: add core test suite for path resolution, storage, remote detection, and install scripts - resolve-file: absolute paths, relative paths, case-insensitive search, ignored dirs, extension filtering, ambiguity, Windows separators - storage: slug generation, tilde expansion, version history, deduplication - remote: env var detection (PLANNOTATOR_REMOTE, SSH_TTY), port config and validation - image: tmpdir usage, extension validation - install scripts: JSON structure validation, checksum verification, arch detection, full exe path in hooks 79 tests, 35ms Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * ci: add test workflow and gate release builds on tests - New test.yml: runs `bun test` on PRs and pushes to main - release.yml: tests must pass before build job runs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: resolve pre-existing test failures for CI compatibility - project.test.ts: make repo name assertion portable (works in CI where checkout dir differs from local dev) - vscode mock: add missing APIs needed by editor-annotations.ts (comments, languages, Range, CodeActionKind, decorations) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: remove Viewer.test.tsx and happy-dom dependency Tests didn't exercise any application code — they manually constructed DOM elements inline and verified DOM API behavior, not Viewer.tsx logic. The mock highlighter didn't simulate real hljs, and one test could never fail due to its try/catch structure. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: use targeted markdown glob and restore clean lockfile - Replace **/* glob with **/*.[mM][dD]{,[xX]} to only scan markdown files during case-insensitive search (avoids iterating every file) - Restore package.json key ordering from main, only removing happy-dom - Regenerate bun.lock from main's base to eliminate version drift Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: drop redundant test and fix weak assertion - Remove redundant PLANNOTATOR_REMOTE=TRUE test (already covered by true) - Fix UPLOAD_DIR assertion that would pass even with hardcoded /tmp Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Michael Ramos <mdramos8@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
143 lines
4.9 KiB
TypeScript
143 lines
4.9 KiB
TypeScript
/**
|
|
* Install Script Validation Tests
|
|
*
|
|
* Validates that install scripts produce correct JSON and command structures
|
|
* without actually running the installers.
|
|
*
|
|
* Run: bun test scripts/install.test.ts
|
|
*/
|
|
|
|
import { describe, expect, test } from "bun:test";
|
|
import { readFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
|
|
const scriptsDir = import.meta.dir;
|
|
|
|
describe("install.sh", () => {
|
|
const script = readFileSync(join(scriptsDir, "install.sh"), "utf-8");
|
|
|
|
test("hooks.json heredoc is valid JSON", () => {
|
|
// Extract the JSON between the HOOKS_EOF heredoc markers
|
|
const match = script.match(/cat > "\$PLUGIN_HOOKS" << 'HOOKS_EOF'\n([\s\S]*?)\nHOOKS_EOF/);
|
|
expect(match).toBeTruthy();
|
|
const json = JSON.parse(match![1]);
|
|
expect(json.hooks.PermissionRequest).toBeArray();
|
|
expect(json.hooks.PermissionRequest[0].matcher).toBe("ExitPlanMode");
|
|
expect(json.hooks.PermissionRequest[0].hooks[0].type).toBe("command");
|
|
expect(json.hooks.PermissionRequest[0].hooks[0].command).toBe("plannotator");
|
|
expect(json.hooks.PermissionRequest[0].hooks[0].timeout).toBe(345600);
|
|
});
|
|
|
|
test("installs to ~/.local/bin", () => {
|
|
expect(script).toContain('INSTALL_DIR="$HOME/.local/bin"');
|
|
});
|
|
|
|
test("verifies checksums", () => {
|
|
expect(script).toContain("shasum -a 256");
|
|
expect(script).toContain("sha256sum");
|
|
});
|
|
|
|
test("detects supported platforms", () => {
|
|
expect(script).toContain('Darwin) os="darwin"');
|
|
expect(script).toContain('Linux) os="linux"');
|
|
});
|
|
|
|
test("detects supported architectures", () => {
|
|
expect(script).toContain('x86_64|amd64) arch="x64"');
|
|
expect(script).toContain('arm64|aarch64) arch="arm64"');
|
|
});
|
|
|
|
test("warns about duplicate hooks", () => {
|
|
expect(script).toContain("DUPLICATE HOOK DETECTED");
|
|
expect(script).toContain('"command".*plannotator');
|
|
});
|
|
|
|
test("installs slash commands for Claude Code and OpenCode", () => {
|
|
expect(script).toContain("plannotator-review.md");
|
|
expect(script).toContain("plannotator-annotate.md");
|
|
expect(script).toContain("CLAUDE_COMMANDS_DIR");
|
|
expect(script).toContain("OPENCODE_COMMANDS_DIR");
|
|
});
|
|
});
|
|
|
|
describe("install.ps1", () => {
|
|
const script = readFileSync(join(scriptsDir, "install.ps1"), "utf-8");
|
|
|
|
test("hooks.json has valid structure", () => {
|
|
// PS1 uses @"..."@ (interpolated) with $exePathJson for full exe path.
|
|
// Verify structural keys since the command value is a dynamic variable.
|
|
expect(script).toContain('"PermissionRequest"');
|
|
expect(script).toContain('"matcher": "ExitPlanMode"');
|
|
expect(script).toContain('"type": "command"');
|
|
expect(script).toContain('"timeout": 345600');
|
|
expect(script).toContain('"command":');
|
|
});
|
|
|
|
test("uses full exe path in hooks.json", () => {
|
|
expect(script).toContain("$exePathJson");
|
|
expect(script).toContain(".Replace('\\', '/')");
|
|
});
|
|
|
|
test("handles both PS 5.1 and PS 7+ checksum response types", () => {
|
|
expect(script).toContain("[byte[]]");
|
|
expect(script).toContain("UTF8.GetString");
|
|
});
|
|
|
|
test("detects ARM64 architecture", () => {
|
|
expect(script).toContain('"ARM64"');
|
|
});
|
|
|
|
test("adds to PATH via environment variable", () => {
|
|
expect(script).toContain('SetEnvironmentVariable("Path"');
|
|
expect(script).toContain('"User"');
|
|
});
|
|
|
|
test("warns about duplicate hooks", () => {
|
|
expect(script).toContain("DUPLICATE HOOK DETECTED");
|
|
});
|
|
|
|
test("installs slash commands", () => {
|
|
expect(script).toContain("plannotator-review.md");
|
|
expect(script).toContain("plannotator-annotate.md");
|
|
});
|
|
});
|
|
|
|
describe("install.cmd", () => {
|
|
const script = readFileSync(join(scriptsDir, "install.cmd"), "utf-8");
|
|
|
|
test("hooks.json echo block produces valid JSON structure", () => {
|
|
// The .cmd file uses echo statements to produce JSON.
|
|
expect(script).toContain('echo "hooks": {');
|
|
expect(script).toContain('echo "PermissionRequest": [');
|
|
expect(script).toContain('echo "matcher": "ExitPlanMode",');
|
|
expect(script).toContain('echo "type": "command",');
|
|
expect(script).toContain('echo "command":');
|
|
expect(script).toContain('echo "timeout": 345600');
|
|
});
|
|
|
|
test("uses full exe path in hooks.json", () => {
|
|
expect(script).toContain("EXE_PATH");
|
|
expect(script).toContain('!INSTALL_PATH:\\=/!');
|
|
});
|
|
|
|
test("verifies checksums with certutil", () => {
|
|
expect(script).toContain("certutil -hashfile");
|
|
expect(script).toContain("SHA256");
|
|
});
|
|
|
|
test("checks for 64-bit Windows", () => {
|
|
expect(script).toContain("AMD64");
|
|
expect(script).toContain("ARM64");
|
|
expect(script).toContain("PROCESSOR_ARCHITEW6432"); // WoW64 detection
|
|
});
|
|
|
|
test("warns about duplicate hooks", () => {
|
|
expect(script).toContain("DUPLICATE HOOK DETECTED");
|
|
});
|
|
|
|
test("installs slash commands", () => {
|
|
expect(script).toContain("plannotator-review.md");
|
|
expect(script).toContain("plannotator-annotate.md");
|
|
});
|
|
});
|