import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import test from "node:test"; test("gateway-only deployment runs checks and restart without publishing clients or touching secrets", () => { const root = mkdtempSync(join(tmpdir(), "mock-gateway-deploy-")); try { for (const dir of ["deploy", ".runtime", "bin", "release"]) mkdirSync(join(root, dir)); copyFileSync("deploy/install.sh", join(root, "deploy/install.sh")); writeFileSync(join(root, ".runtime/dingtalk-mcp.env"), "mock-config-preserved"); writeFileSync(join(root, "release/manifest.json"), "mock-existing-client-manifest"); const calls = join(root, "calls.log"); const stub = `#!/bin/bash printf '%s\\n' "$(basename "$0") $*" >> "$MOCK_DEPLOY_CALLS" case "$(basename "$0")" in git) case "$1" in branch) echo "\${MOCK_DEPLOY_BRANCH:-main}" ;; diff) exit "\${MOCK_DEPLOY_DIRTY:-0}" ;; rev-parse) echo mock-commit ;; ls-remote) printf 'mock-commit\\trefs/heads/main\\n' ;; *) exit 77 ;; esac ;; npm) exit "\${MOCK_DEPLOY_TEST_EXIT:-0}" ;; node) case "$1" in -p) echo 1.1.0-rc.20 ;; --env-file=*) exit "\${MOCK_DEPLOY_CONFIG_EXIT:-0}" ;; dist/src/admin.js) exit 0 ;; *) exit 77 ;; esac ;; curl|systemctl) exit 0 ;; *) exit 77 ;; esac `; for (const name of ["git", "node", "npm", "curl", "systemctl", "tea", "sudo"]) { writeFileSync(join(root, "bin", name), stub, { mode: 0o755 }); } const run = (overrides: NodeJS.ProcessEnv = {}) => { writeFileSync(calls, ""); const result = spawnSync("bash", [join(root, "deploy/install.sh"), "--gateway-only"], { cwd: root, encoding: "utf8", env: { ...process.env, PATH: `${join(root, "bin")}:${process.env.PATH}`, MOCK_DEPLOY_CALLS: calls, ...overrides }, }); return { ...result, calls: readFileSync(calls, "utf8") }; }; const success = run(); assert.equal(success.status, 0, success.stderr); assert.match(success.calls, /npm .* test\n/); assert.match(success.calls, /node --env-file=.*loadConfig/s); assert.match(success.calls, /node dist\/src\/admin.js service start --project-root/); assert.match(success.calls, /systemctl --user is-active --quiet/); assert.match(success.calls, /\/healthz/); assert.match(success.calls, /oauth-protected-resource\/mcp/); assert.doesNotMatch(success.calls, /runtime:verify|release:build|release:verify|test-bundle|refs\/tags|tea |sudo /); assert.equal(readFileSync(join(root, ".runtime/dingtalk-mcp.env"), "utf8"), "mock-config-preserved"); assert.equal(readFileSync(join(root, "release/manifest.json"), "utf8"), "mock-existing-client-manifest"); for (const failure of [ { MOCK_DEPLOY_TEST_EXIT: "1" }, { MOCK_DEPLOY_CONFIG_EXIT: "1" }, { MOCK_DEPLOY_BRANCH: "feature" }, { MOCK_DEPLOY_DIRTY: "1" }, ]) { const result = run(failure); assert.notEqual(result.status, 0); assert.doesNotMatch(result.calls, /node dist\/src\/admin.js service start/); } } finally { rmSync(root, { recursive: true, force: true }); } });