git-package.test.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. import assert from "node:assert/strict";
  2. import { execFileSync, spawnSync } from "node:child_process";
  3. import { chmod, mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
  4. import { tmpdir } from "node:os";
  5. import { join } from "node:path";
  6. import { pathToFileURL } from "node:url";
  7. import test from "node:test";
  8. const verifier = new URL("../../scripts/verify-git-package.mjs", import.meta.url);
  9. function runVerifier(args: string[], cache?: string) {
  10. return spawnSync(process.execPath, [verifier.pathname, ...args], {
  11. encoding: "utf8",
  12. env: cache === undefined
  13. ? process.env
  14. : { ...process.env, CODEX_CHIEF_OF_STAFF_NPM_CACHE: cache },
  15. timeout: 60_000,
  16. });
  17. }
  18. test("Git-package verifier requires exactly one immutable package spec without leaking credentials", () => {
  19. const secret = "credential-must-not-appear";
  20. const invalid = [
  21. [],
  22. ["git+https://git.example.invalid/team/codex-chief-of-staff.git#main"],
  23. [`git+https://user:${secret}@git.example.invalid/team/codex-chief-of-staff.git#main`],
  24. ["git+https://git.example.invalid/team/codex-chief-of-staff.git#abc123"],
  25. [
  26. "git+https://git.example.invalid/team/codex-chief-of-staff.git#v0.1.1",
  27. "git+https://git.example.invalid/team/codex-chief-of-staff.git#v0.1.0",
  28. ],
  29. ];
  30. for (const args of invalid) {
  31. const result = runVerifier(args);
  32. assert.notEqual(result.status, 0);
  33. assert.equal(result.stderr.includes(secret), false);
  34. assert.match(result.stderr, /Git-package verification failed:/);
  35. }
  36. });
  37. test("Git-package verifier installs and initializes a disposable prebuilt Git revision", async () => {
  38. const root = await mkdtemp(join(tmpdir(), "codex-chief-of-staff-git-test-"));
  39. const repository = join(root, "codex-chief-of-staff.git");
  40. const cache = join(root, "npm-cache");
  41. try {
  42. await mkdir(join(repository, "dist", "src"), { recursive: true });
  43. await writeFile(join(repository, "package.json"), `${JSON.stringify({
  44. name: "codex-chief-of-staff",
  45. version: "9.9.9",
  46. private: true,
  47. type: "module",
  48. bin: { "codex-chief-of-staff": "dist/src/index.js" },
  49. files: ["dist/src"],
  50. engines: { node: ">=24 <25" },
  51. }, null, 2)}\n`);
  52. const server = `#!/usr/bin/env node
  53. const tools = ["overview", "list_threads", "inspect_thread", "wait_for_change"];
  54. let buffered = "";
  55. const send = (id, result) => process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id, result }) + "\\n");
  56. process.stdin.setEncoding("utf8");
  57. process.stdin.on("data", (chunk) => {
  58. buffered += chunk;
  59. for (;;) {
  60. const newline = buffered.indexOf("\\n");
  61. if (newline < 0) break;
  62. const line = buffered.slice(0, newline);
  63. buffered = buffered.slice(newline + 1);
  64. if (line === "") continue;
  65. const request = JSON.parse(line);
  66. if (request.id === undefined) continue;
  67. if (request.method === "initialize") {
  68. send(request.id, {
  69. protocolVersion: request.params.protocolVersion,
  70. capabilities: { tools: {} },
  71. serverInfo: { name: "fixture-chief-of-staff", version: "9.9.9" },
  72. instructions: "Call overview first for current Codex thread state.",
  73. });
  74. } else if (request.method === "tools/list") {
  75. send(request.id, { tools: tools.map((name) => ({
  76. name,
  77. description: name,
  78. inputSchema: { type: "object", properties: {}, additionalProperties: false },
  79. })) });
  80. }
  81. }
  82. });
  83. `;
  84. const executable = join(repository, "dist", "src", "index.js");
  85. await writeFile(executable, server);
  86. await chmod(executable, 0o755);
  87. execFileSync("git", ["init", "--quiet"], { cwd: repository });
  88. execFileSync("git", ["config", "user.name", "Codex Test"], { cwd: repository });
  89. execFileSync("git", ["config", "user.email", "codex-test@example.invalid"], { cwd: repository });
  90. execFileSync("git", ["add", "."], { cwd: repository });
  91. execFileSync("git", ["commit", "--quiet", "-m", "prebuilt fixture"], { cwd: repository });
  92. const revision = execFileSync("git", ["rev-parse", "HEAD"], {
  93. cwd: repository,
  94. encoding: "utf8",
  95. }).trim();
  96. const packageSpec = `git+${pathToFileURL(repository).href}#${revision}`;
  97. const result = runVerifier([packageSpec], cache);
  98. assert.equal(result.status, 0, result.stderr);
  99. const report = JSON.parse(result.stdout) as {
  100. ok: boolean;
  101. revision: string;
  102. storage: string;
  103. instructions: string;
  104. tools: string[];
  105. };
  106. assert.equal(report.ok, true);
  107. assert.equal(report.revision, revision);
  108. assert.equal(report.storage, "isolated-prefix");
  109. assert.equal(report.instructions, "verified");
  110. assert.deepEqual(report.tools, ["overview", "list_threads", "inspect_thread", "wait_for_change"]);
  111. } finally {
  112. await rm(root, { recursive: true, force: true });
  113. }
  114. });