snapshot of https://github.com/marmelab/atomic-crm.git (main, 2026-09-18; blobs > 131072B omitted)
Some checks failed
✅ Check / 🔬 ESLint (push) Failing after 21s
✅ Check / 🏷️ Typecheck (push) Failing after 11s
✅ Check / 🔎 Test (push) Failing after 11s
✅ Check / e2e-test (push) Failing after 11s
✅ Check / 🔨 Build (push) Failing after 11s
🚀 Deploy / 🚀 Deploy (doc) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (demo) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (supabase) (push) Failing after 18s
Some checks failed
✅ Check / 🔬 ESLint (push) Failing after 21s
✅ Check / 🏷️ Typecheck (push) Failing after 11s
✅ Check / 🔎 Test (push) Failing after 11s
✅ Check / e2e-test (push) Failing after 11s
✅ Check / 🔨 Build (push) Failing after 11s
🚀 Deploy / 🚀 Deploy (doc) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (demo) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (supabase) (push) Failing after 18s
This commit is contained in:
100
.claude/hooks/test/session-bootstrap.test.mjs
Normal file
100
.claude/hooks/test/session-bootstrap.test.mjs
Normal file
@@ -0,0 +1,100 @@
|
||||
// Tests for session-bootstrap.mjs — the SessionStart hook that injects
|
||||
// <session_dir> via hookSpecificOutput.additionalContext so the harness can run
|
||||
// on any surface without a launch script. Key invariant: the injected dir is
|
||||
// ctx.sessionDir = <TMP_ROOT>/<sanitized repo>/<session_id>, so its basename is
|
||||
// the real session id (what setup-worktree and the orchestrator also key off).
|
||||
|
||||
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { afterAll, describe, test, expect } from "vitest";
|
||||
import { sanitizePath } from "../lib/paths.mjs";
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const HOOK = join(HERE, "..", "session-bootstrap.mjs");
|
||||
const TMP = mkdtempSync(join(tmpdir(), "session-bootstrap-test-"));
|
||||
const APP_DIR = join(TMP, "repo");
|
||||
const CRM_TMP_ROOT = join(TMP, "scratch");
|
||||
const SESSION_ID = "abcd1234-1111-2222-3333-444455556666";
|
||||
|
||||
const baseEnv = { ...process.env, APP_DIR, CRM_TMP_ROOT };
|
||||
delete baseEnv.CHAT_SESSION_DIR;
|
||||
delete baseEnv.CLAUDE_CODE_SESSION_ID;
|
||||
|
||||
afterAll(() => rmSync(TMP, { recursive: true, force: true }));
|
||||
|
||||
const run = (payload, extraEnv = {}) =>
|
||||
spawnSync("node", [HOOK], {
|
||||
input: payload === undefined ? "" : JSON.stringify(payload),
|
||||
env: { ...baseEnv, ...extraEnv },
|
||||
encoding: "utf8",
|
||||
});
|
||||
|
||||
describe("session-bootstrap", () => {
|
||||
test("injects <session_dir> built from the real session id", () => {
|
||||
const r = run({ session_id: SESSION_ID });
|
||||
expect(r.status).toBe(0);
|
||||
const expectedDir = join(CRM_TMP_ROOT, sanitizePath(APP_DIR), SESSION_ID);
|
||||
expect(r.stdout).toContain(`<session_dir>${expectedDir}</session_dir>`);
|
||||
// Alignment invariant: basename(session_dir) === session_id.
|
||||
const m = r.stdout.match(/<session_dir>(.+?)<\/session_dir>/);
|
||||
expect(m && m[1].split("/").pop()).toBe(SESSION_ID);
|
||||
});
|
||||
|
||||
test("emits valid additionalContext JSON for SessionStart", () => {
|
||||
const r = run({ session_id: SESSION_ID });
|
||||
const parsed = JSON.parse(r.stdout.trim());
|
||||
expect(parsed.hookSpecificOutput.hookEventName).toBe("SessionStart");
|
||||
expect(parsed.hookSpecificOutput.additionalContext).toContain(
|
||||
"<session_dir>",
|
||||
);
|
||||
});
|
||||
|
||||
test("stays out of the way when a managed launcher owns the session", () => {
|
||||
const r = run(
|
||||
{ session_id: SESSION_ID },
|
||||
{ CHAT_SESSION_DIR: "/tmp/atomic-crm-harness/managed" },
|
||||
);
|
||||
expect(r.status).toBe(0);
|
||||
expect(r.stdout).not.toContain("<session_dir>");
|
||||
});
|
||||
|
||||
test("exits cleanly with no output when there is no payload", () => {
|
||||
const r = run(undefined);
|
||||
expect(r.status).toBe(0);
|
||||
expect(r.stdout).not.toContain("<session_dir>");
|
||||
});
|
||||
|
||||
test("emits no resume banner for a fresh session with no harness state", () => {
|
||||
const r = run({ session_id: SESSION_ID });
|
||||
expect(r.stdout).not.toContain("<harness_resume>");
|
||||
});
|
||||
|
||||
test("injects a resume banner when THIS session has in-flight harness state", () => {
|
||||
// An unmerged ticket in the session dir = an interrupted plan-gate session.
|
||||
// Isolated app/scratch so the shared module-level dirs stay banner-free.
|
||||
const app = mkdtempSync(join(tmpdir(), "sb-inflight-app-"));
|
||||
const scratch = mkdtempSync(join(tmpdir(), "sb-inflight-scratch-"));
|
||||
const sid = "beef1234-aaaa-bbbb-cccc-ddddeeeeffff";
|
||||
const sessionDir = join(scratch, sanitizePath(app), sid);
|
||||
mkdirSync(sessionDir, { recursive: true });
|
||||
writeFileSync(
|
||||
join(sessionDir, "TASK-001.json"),
|
||||
JSON.stringify({ id: "TASK-001", status: "planned" }),
|
||||
);
|
||||
const r = spawnSync("node", [HOOK], {
|
||||
input: JSON.stringify({ session_id: sid }),
|
||||
env: { ...baseEnv, APP_DIR: app, CRM_TMP_ROOT: scratch },
|
||||
encoding: "utf8",
|
||||
});
|
||||
rmSync(app, { recursive: true, force: true });
|
||||
rmSync(scratch, { recursive: true, force: true });
|
||||
expect(r.status).toBe(0);
|
||||
expect(r.stdout).toContain("<harness_resume>");
|
||||
expect(r.stdout).toContain("plan gate");
|
||||
// The banner is appended, not a replacement: <session_dir> is still present.
|
||||
expect(r.stdout).toContain("<session_dir>");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user