patchwarden 0.6.4 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +54 -20
- package/README.md +48 -21
- package/dist/agents/agentRouter.d.ts +40 -0
- package/dist/agents/agentRouter.js +95 -0
- package/dist/config.d.ts +1 -1
- package/dist/config.js +6 -2
- package/dist/controlCenter.js +212 -3
- package/dist/direct/directSessionStore.d.ts +2 -0
- package/dist/direct/directVerification.js +7 -0
- package/dist/doctor.js +32 -1
- package/dist/goal/acceptanceEngine.d.ts +62 -0
- package/dist/goal/acceptanceEngine.js +103 -0
- package/dist/goal/acceptanceTemplate.d.ts +10 -0
- package/dist/goal/acceptanceTemplate.js +104 -0
- package/dist/goal/goalGraph.d.ts +58 -0
- package/dist/goal/goalGraph.js +189 -0
- package/dist/goal/goalProgress.d.ts +81 -0
- package/dist/goal/goalProgress.js +216 -0
- package/dist/goal/goalStatus.d.ts +60 -0
- package/dist/goal/goalStatus.js +137 -0
- package/dist/goal/goalStore.d.ts +79 -0
- package/dist/goal/goalStore.js +211 -0
- package/dist/goal/handoffExport.d.ts +34 -0
- package/dist/goal/handoffExport.js +183 -0
- package/dist/goal/subgoalSync.d.ts +40 -0
- package/dist/goal/subgoalSync.js +72 -0
- package/dist/goal/worktreeManager.d.ts +96 -0
- package/dist/goal/worktreeManager.js +292 -0
- package/dist/logging.d.ts +44 -0
- package/dist/logging.js +68 -0
- package/dist/policy/projectPolicy.d.ts +55 -0
- package/dist/policy/projectPolicy.js +286 -0
- package/dist/release/releaseGate.d.ts +99 -0
- package/dist/release/releaseGate.js +475 -0
- package/dist/runner/postTaskCleanup.d.ts +13 -0
- package/dist/runner/postTaskCleanup.js +147 -0
- package/dist/runner/runTask.js +50 -10
- package/dist/security/discoveryTokenStore.d.ts +62 -0
- package/dist/security/discoveryTokenStore.js +100 -0
- package/dist/security/toolInvocationGuard.d.ts +35 -0
- package/dist/security/toolInvocationGuard.js +153 -0
- package/dist/smoke-test.js +18 -10
- package/dist/taskRuntime.d.ts +17 -0
- package/dist/test/unit/acceptance-engine.test.d.ts +1 -0
- package/dist/test/unit/acceptance-engine.test.js +228 -0
- package/dist/test/unit/agent-router.test.d.ts +1 -0
- package/dist/test/unit/agent-router.test.js +287 -0
- package/dist/test/unit/audit-checks.test.d.ts +1 -0
- package/dist/test/unit/audit-checks.test.js +350 -0
- package/dist/test/unit/diagnose-task.test.d.ts +1 -0
- package/dist/test/unit/diagnose-task.test.js +457 -0
- package/dist/test/unit/discovery-token-store.test.d.ts +1 -0
- package/dist/test/unit/discovery-token-store.test.js +139 -0
- package/dist/test/unit/evidence-pack.test.d.ts +1 -0
- package/dist/test/unit/evidence-pack.test.js +130 -0
- package/dist/test/unit/goal-graph.test.d.ts +1 -0
- package/dist/test/unit/goal-graph.test.js +298 -0
- package/dist/test/unit/goal-progress.test.d.ts +1 -0
- package/dist/test/unit/goal-progress.test.js +381 -0
- package/dist/test/unit/goal-status.test.d.ts +1 -0
- package/dist/test/unit/goal-status.test.js +215 -0
- package/dist/test/unit/goal-store.test.d.ts +1 -0
- package/dist/test/unit/goal-store.test.js +253 -0
- package/dist/test/unit/goal-subgoal-task.test.d.ts +1 -0
- package/dist/test/unit/goal-subgoal-task.test.js +55 -0
- package/dist/test/unit/goal-tools-registry.test.d.ts +1 -0
- package/dist/test/unit/goal-tools-registry.test.js +190 -0
- package/dist/test/unit/handoff-export.test.d.ts +1 -0
- package/dist/test/unit/handoff-export.test.js +263 -0
- package/dist/test/unit/invoke-discovered-tool.test.d.ts +1 -0
- package/dist/test/unit/invoke-discovered-tool.test.js +167 -0
- package/dist/test/unit/logging.test.js +127 -5
- package/dist/test/unit/post-task-cleanup.test.d.ts +1 -0
- package/dist/test/unit/post-task-cleanup.test.js +48 -0
- package/dist/test/unit/project-policy-release-mode.test.d.ts +1 -0
- package/dist/test/unit/project-policy-release-mode.test.js +125 -0
- package/dist/test/unit/reconcile-tasks.test.d.ts +1 -0
- package/dist/test/unit/reconcile-tasks.test.js +456 -0
- package/dist/test/unit/release-gate.test.d.ts +1 -0
- package/dist/test/unit/release-gate.test.js +242 -0
- package/dist/test/unit/run-task-loop.test.d.ts +1 -0
- package/dist/test/unit/run-task-loop.test.js +380 -0
- package/dist/test/unit/safe-views.test.d.ts +1 -0
- package/dist/test/unit/safe-views.test.js +171 -0
- package/dist/test/unit/schema-drift-check.test.d.ts +1 -0
- package/dist/test/unit/schema-drift-check.test.js +176 -0
- package/dist/test/unit/subgoal-sync.test.d.ts +1 -0
- package/dist/test/unit/subgoal-sync.test.js +183 -0
- package/dist/test/unit/tool-invocation-guard.test.d.ts +1 -0
- package/dist/test/unit/tool-invocation-guard.test.js +432 -0
- package/dist/test/unit/tool-usage-stats.test.d.ts +1 -0
- package/dist/test/unit/tool-usage-stats.test.js +300 -0
- package/dist/test/unit/toolSearch.test.d.ts +1 -0
- package/dist/test/unit/toolSearch.test.js +571 -0
- package/dist/test/unit/worktree-manager.test.d.ts +1 -0
- package/dist/test/unit/worktree-manager.test.js +176 -0
- package/dist/tools/auditTask.d.ts +103 -1
- package/dist/tools/auditTask.js +461 -8
- package/dist/tools/cancelTask.js +1 -1
- package/dist/tools/checkReleaseGate.d.ts +21 -0
- package/dist/tools/checkReleaseGate.js +22 -0
- package/dist/tools/createTask.d.ts +18 -2
- package/dist/tools/createTask.js +29 -1
- package/dist/tools/diagnoseTask.d.ts +45 -0
- package/dist/tools/diagnoseTask.js +347 -0
- package/dist/tools/discardWorktree.d.ts +24 -0
- package/dist/tools/discardWorktree.js +27 -0
- package/dist/tools/discoverTools.d.ts +11 -0
- package/dist/tools/discoverTools.js +39 -0
- package/dist/tools/evidencePack.d.ts +39 -0
- package/dist/tools/evidencePack.js +168 -0
- package/dist/tools/explainTool.d.ts +11 -0
- package/dist/tools/explainTool.js +21 -0
- package/dist/tools/getTaskSummary.js +2 -1
- package/dist/tools/goalSubgoalTask.d.ts +51 -0
- package/dist/tools/goalSubgoalTask.js +110 -0
- package/dist/tools/healthCheck.d.ts +1 -0
- package/dist/tools/healthCheck.js +5 -1
- package/dist/tools/invokeDiscoveredTool.d.ts +37 -0
- package/dist/tools/invokeDiscoveredTool.js +112 -0
- package/dist/tools/listTasks.d.ts +3 -1
- package/dist/tools/listTasks.js +14 -1
- package/dist/tools/mergeWorktree.d.ts +24 -0
- package/dist/tools/mergeWorktree.js +27 -0
- package/dist/tools/recommendAgentForTask.d.ts +19 -0
- package/dist/tools/recommendAgentForTask.js +56 -0
- package/dist/tools/reconcileTasks.d.ts +41 -0
- package/dist/tools/reconcileTasks.js +352 -0
- package/dist/tools/registry.js +926 -2
- package/dist/tools/releaseMode.d.ts +50 -0
- package/dist/tools/releaseMode.js +370 -0
- package/dist/tools/runDirectVerificationBundle.d.ts +26 -0
- package/dist/tools/runDirectVerificationBundle.js +64 -0
- package/dist/tools/runTaskLoop.d.ts +57 -0
- package/dist/tools/runTaskLoop.js +417 -0
- package/dist/tools/runVerification.d.ts +4 -0
- package/dist/tools/runVerification.js +4 -0
- package/dist/tools/safeStatus.d.ts +31 -1
- package/dist/tools/safeStatus.js +43 -1
- package/dist/tools/safeViews.d.ts +262 -0
- package/dist/tools/safeViews.js +252 -0
- package/dist/tools/schemaDriftCheck.d.ts +46 -0
- package/dist/tools/schemaDriftCheck.js +80 -0
- package/dist/tools/taskLineage.d.ts +91 -0
- package/dist/tools/taskLineage.js +175 -0
- package/dist/tools/toolCatalog.d.ts +4 -3
- package/dist/tools/toolCatalog.js +39 -11
- package/dist/tools/toolRegistry.d.ts +61 -0
- package/dist/tools/toolRegistry.js +834 -0
- package/dist/tools/toolSearch.d.ts +110 -0
- package/dist/tools/toolSearch.js +331 -0
- package/dist/tools/toolUsageStats.d.ts +41 -0
- package/dist/tools/toolUsageStats.js +116 -0
- package/dist/tools/waitForTask.js +1 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/dist/watcherStatus.d.ts +29 -0
- package/dist/watcherStatus.js +92 -1
- package/docs/chatgpt-usage.md +31 -0
- package/docs/control-center/README.md +9 -0
- package/package.json +3 -2
- package/scripts/checks/control-center-smoke.js +87 -0
- package/scripts/checks/control-smoke.js +2 -2
- package/scripts/checks/doctor-smoke.js +1 -0
- package/scripts/checks/lifecycle-smoke.js +13 -9
- package/scripts/checks/mcp-manifest-check.js +65 -6
- package/scripts/checks/mcp-smoke.js +60 -9
- package/scripts/checks/watcher-supervisor-smoke.js +2 -2
- package/src/agents/agentRouter.ts +149 -0
- package/src/config.ts +9 -3
- package/src/controlCenter.ts +215 -4
- package/src/direct/directSessionStore.ts +2 -0
- package/src/direct/directVerification.ts +7 -0
- package/src/doctor.ts +36 -1
- package/src/goal/acceptanceEngine.ts +160 -0
- package/src/goal/acceptanceTemplate.ts +121 -0
- package/src/goal/goalGraph.ts +225 -0
- package/src/goal/goalProgress.ts +301 -0
- package/src/goal/goalStatus.ts +234 -0
- package/src/goal/goalStore.ts +306 -0
- package/src/goal/handoffExport.ts +211 -0
- package/src/goal/subgoalSync.ts +82 -0
- package/src/goal/worktreeManager.ts +404 -0
- package/src/logging.ts +91 -0
- package/src/policy/projectPolicy.ts +344 -0
- package/src/release/releaseGate.ts +567 -0
- package/src/runner/postTaskCleanup.ts +154 -0
- package/src/runner/runTask.ts +49 -10
- package/src/security/discoveryTokenStore.ts +157 -0
- package/src/security/toolInvocationGuard.ts +251 -0
- package/src/smoke-test.ts +15 -7
- package/src/taskRuntime.ts +17 -0
- package/src/test/unit/acceptance-engine.test.ts +261 -0
- package/src/test/unit/agent-router.test.ts +342 -0
- package/src/test/unit/audit-checks.test.ts +567 -0
- package/src/test/unit/diagnose-task.test.ts +544 -0
- package/src/test/unit/discovery-token-store.test.ts +181 -0
- package/src/test/unit/evidence-pack.test.ts +142 -0
- package/src/test/unit/goal-graph.test.ts +347 -0
- package/src/test/unit/goal-progress.test.ts +538 -0
- package/src/test/unit/goal-status.test.ts +270 -0
- package/src/test/unit/goal-store.test.ts +318 -0
- package/src/test/unit/goal-subgoal-task.test.ts +72 -0
- package/src/test/unit/goal-tools-registry.test.ts +243 -0
- package/src/test/unit/handoff-export.test.ts +295 -0
- package/src/test/unit/invoke-discovered-tool.test.ts +216 -0
- package/src/test/unit/logging.test.ts +177 -5
- package/src/test/unit/post-task-cleanup.test.ts +53 -0
- package/src/test/unit/project-policy-release-mode.test.ts +156 -0
- package/src/test/unit/reconcile-tasks.test.ts +551 -0
- package/src/test/unit/release-gate.test.ts +314 -0
- package/src/test/unit/run-task-loop.test.ts +425 -0
- package/src/test/unit/safe-views.test.ts +184 -0
- package/src/test/unit/schema-drift-check.test.ts +260 -0
- package/src/test/unit/subgoal-sync.test.ts +236 -0
- package/src/test/unit/tool-invocation-guard.test.ts +542 -0
- package/src/test/unit/tool-usage-stats.test.ts +384 -0
- package/src/test/unit/toolSearch.test.ts +756 -0
- package/src/test/unit/worktree-manager.test.ts +247 -0
- package/src/tools/auditTask.ts +831 -402
- package/src/tools/cancelTask.ts +1 -1
- package/src/tools/checkReleaseGate.ts +45 -0
- package/src/tools/createTask.ts +64 -6
- package/src/tools/diagnoseTask.ts +460 -0
- package/src/tools/discardWorktree.ts +42 -0
- package/src/tools/discoverTools.ts +51 -0
- package/src/tools/evidencePack.ts +205 -0
- package/src/tools/explainTool.ts +26 -0
- package/src/tools/getTaskSummary.ts +2 -1
- package/src/tools/goalSubgoalTask.ts +170 -0
- package/src/tools/healthCheck.ts +4 -1
- package/src/tools/invokeDiscoveredTool.ts +163 -0
- package/src/tools/listTasks.ts +16 -2
- package/src/tools/listWorkspace.ts +71 -71
- package/src/tools/mergeWorktree.ts +42 -0
- package/src/tools/recommendAgentForTask.ts +79 -0
- package/src/tools/reconcileTasks.ts +464 -0
- package/src/tools/registry.ts +1093 -73
- package/src/tools/releaseMode.ts +450 -0
- package/src/tools/runDirectVerificationBundle.ts +98 -0
- package/src/tools/runTaskLoop.ts +526 -0
- package/src/tools/runVerification.ts +8 -0
- package/src/tools/safeStatus.ts +73 -2
- package/src/tools/safeViews.ts +273 -0
- package/src/tools/schemaDriftCheck.ts +120 -0
- package/src/tools/taskLineage.ts +300 -0
- package/src/tools/toolCatalog.ts +40 -11
- package/src/tools/toolRegistry.ts +896 -0
- package/src/tools/toolSearch.ts +464 -0
- package/src/tools/toolUsageStats.ts +151 -0
- package/src/tools/waitForTask.ts +1 -0
- package/src/version.ts +2 -2
- package/src/watcherStatus.ts +97 -1
- package/ui/pages/dashboard.html +143 -2
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import { strict as assert } from "node:assert";
|
|
3
|
+
import {
|
|
4
|
+
runReleaseGateCheck,
|
|
5
|
+
checkPublishedVerified,
|
|
6
|
+
checkGitHubReleaseVerified,
|
|
7
|
+
checkCiVerified,
|
|
8
|
+
computeTokenDigest,
|
|
9
|
+
type HttpGetFn,
|
|
10
|
+
type HttpResponse,
|
|
11
|
+
type ReleaseGateDeps,
|
|
12
|
+
} from "../../release/releaseGate.js";
|
|
13
|
+
|
|
14
|
+
// ── Mock helpers ───────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Build a mock httpGet that responds based on the URL.
|
|
18
|
+
* Each responder returns an HttpResponse synchronously (wrapped in a Promise).
|
|
19
|
+
*/
|
|
20
|
+
function mockHttpGet(responder: (url: string) => HttpResponse): HttpGetFn {
|
|
21
|
+
return async (url: string) => responder(url);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Build a mock httpGet that captures headers (for token-leak assertions).
|
|
26
|
+
*/
|
|
27
|
+
function mockHttpGetCapture(
|
|
28
|
+
responder: (url: string) => HttpResponse,
|
|
29
|
+
capture: { headers?: Record<string, string> },
|
|
30
|
+
): HttpGetFn {
|
|
31
|
+
return async (url: string, headers?: Record<string, string>) => {
|
|
32
|
+
capture.headers = { ...(headers || {}) };
|
|
33
|
+
return responder(url);
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Build a mock httpGet that always throws (simulates network error).
|
|
39
|
+
*/
|
|
40
|
+
function mockHttpGetError(error: Error): HttpGetFn {
|
|
41
|
+
return async () => {
|
|
42
|
+
throw error;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── 1. checkPublishedVerified ──────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
describe("checkPublishedVerified", () => {
|
|
49
|
+
it("version exists → passed", async () => {
|
|
50
|
+
const httpGet = mockHttpGet(() => ({
|
|
51
|
+
statusCode: 200,
|
|
52
|
+
data: { versions: { "1.0.0": {}, "0.9.0": {} } },
|
|
53
|
+
}));
|
|
54
|
+
const result = await checkPublishedVerified("patchwarden", "1.0.0", httpGet);
|
|
55
|
+
assert.equal(result.status, "passed");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("version does not exist → failed", async () => {
|
|
59
|
+
const httpGet = mockHttpGet(() => ({
|
|
60
|
+
statusCode: 200,
|
|
61
|
+
data: { versions: { "0.9.0": {} } },
|
|
62
|
+
}));
|
|
63
|
+
const result = await checkPublishedVerified("patchwarden", "9.9.9", httpGet);
|
|
64
|
+
assert.equal(result.status, "failed");
|
|
65
|
+
assert.ok(result.reason?.includes("9.9.9"));
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("network error → not_checked", async () => {
|
|
69
|
+
const httpGet = mockHttpGetError(new Error("connect ECONNREFUSED"));
|
|
70
|
+
const result = await checkPublishedVerified("patchwarden", "1.0.0", httpGet);
|
|
71
|
+
assert.equal(result.status, "not_checked");
|
|
72
|
+
assert.ok(result.reason?.includes("Network error"));
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("package 404 → failed", async () => {
|
|
76
|
+
const httpGet = mockHttpGet(() => ({
|
|
77
|
+
statusCode: 404,
|
|
78
|
+
data: { error: "Not found" },
|
|
79
|
+
}));
|
|
80
|
+
const result = await checkPublishedVerified("nonexistent-pkg", "1.0.0", httpGet);
|
|
81
|
+
assert.equal(result.status, "failed");
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// ── 2. checkGitHubReleaseVerified ──────────────────────────────────
|
|
86
|
+
|
|
87
|
+
describe("checkGitHubReleaseVerified", () => {
|
|
88
|
+
it("200 → passed", async () => {
|
|
89
|
+
const httpGet = mockHttpGet(() => ({
|
|
90
|
+
statusCode: 200,
|
|
91
|
+
data: { tag_name: "v1.0.0" },
|
|
92
|
+
}));
|
|
93
|
+
const result = await checkGitHubReleaseVerified("user/repo", "v1.0.0", httpGet);
|
|
94
|
+
assert.equal(result.status, "passed");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("404 → failed", async () => {
|
|
98
|
+
const httpGet = mockHttpGet(() => ({
|
|
99
|
+
statusCode: 404,
|
|
100
|
+
data: { message: "Not Found" },
|
|
101
|
+
}));
|
|
102
|
+
const result = await checkGitHubReleaseVerified("user/repo", "v1.0.0", httpGet);
|
|
103
|
+
assert.equal(result.status, "failed");
|
|
104
|
+
assert.ok(result.reason?.includes("v1.0.0"));
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("network error → not_checked", async () => {
|
|
108
|
+
const httpGet = mockHttpGetError(new Error("connect ETIMEDOUT"));
|
|
109
|
+
const result = await checkGitHubReleaseVerified("user/repo", "v1.0.0", httpGet);
|
|
110
|
+
assert.equal(result.status, "not_checked");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("token is used as Bearer but never appears in result", async () => {
|
|
114
|
+
const TOKEN = "test-token";
|
|
115
|
+
process.env.GITHUB_TOKEN = TOKEN;
|
|
116
|
+
const capture: { headers?: Record<string, string> } = {};
|
|
117
|
+
const httpGet = mockHttpGetCapture(
|
|
118
|
+
() => ({ statusCode: 200, data: { tag_name: "v1.0.0" } }),
|
|
119
|
+
capture,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const result = await checkGitHubReleaseVerified("user/repo", "v1.0.0", httpGet);
|
|
124
|
+
assert.equal(result.status, "passed");
|
|
125
|
+
|
|
126
|
+
// Authorization header was set correctly (token used internally)
|
|
127
|
+
assert.equal(capture.headers?.["Authorization"], `Bearer ${TOKEN}`);
|
|
128
|
+
|
|
129
|
+
// Raw token must NOT appear anywhere in the serialized result
|
|
130
|
+
const resultStr = JSON.stringify(result);
|
|
131
|
+
assert.ok(
|
|
132
|
+
!resultStr.includes(TOKEN),
|
|
133
|
+
"raw GITHUB_TOKEN must not leak into the result object",
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
// The token digest is different from the raw token
|
|
137
|
+
const digest = computeTokenDigest(TOKEN);
|
|
138
|
+
assert.ok(digest !== TOKEN, "digest must differ from raw token");
|
|
139
|
+
assert.ok(digest.startsWith("sha256:"));
|
|
140
|
+
} finally {
|
|
141
|
+
delete process.env.GITHUB_TOKEN;
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// ── 3. checkCiVerified ─────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
describe("checkCiVerified", () => {
|
|
149
|
+
it("conclusion=success → passed", async () => {
|
|
150
|
+
const httpGet = mockHttpGet(() => ({
|
|
151
|
+
statusCode: 200,
|
|
152
|
+
data: { workflow_runs: [{ conclusion: "success" }] },
|
|
153
|
+
}));
|
|
154
|
+
const result = await checkCiVerified("user/repo", "main", httpGet);
|
|
155
|
+
assert.equal(result.status, "passed");
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("conclusion=failure → failed", async () => {
|
|
159
|
+
const httpGet = mockHttpGet(() => ({
|
|
160
|
+
statusCode: 200,
|
|
161
|
+
data: { workflow_runs: [{ conclusion: "failure" }] },
|
|
162
|
+
}));
|
|
163
|
+
const result = await checkCiVerified("user/repo", "main", httpGet);
|
|
164
|
+
assert.equal(result.status, "failed");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("conclusion=null → not_checked", async () => {
|
|
168
|
+
const httpGet = mockHttpGet(() => ({
|
|
169
|
+
statusCode: 200,
|
|
170
|
+
data: { workflow_runs: [{ conclusion: null }] },
|
|
171
|
+
}));
|
|
172
|
+
const result = await checkCiVerified("user/repo", "main", httpGet);
|
|
173
|
+
assert.equal(result.status, "not_checked");
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("network error → not_checked", async () => {
|
|
177
|
+
const httpGet = mockHttpGetError(new Error("connect ECONNREFUSED"));
|
|
178
|
+
const result = await checkCiVerified("user/repo", "main", httpGet);
|
|
179
|
+
assert.equal(result.status, "not_checked");
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// ── 4. runReleaseGateCheck orchestration ───────────────────────────
|
|
184
|
+
|
|
185
|
+
describe("runReleaseGateCheck", () => {
|
|
186
|
+
it("target=local_ready passed → local_ready=passed, rest not_checked, no blocked_reason", async () => {
|
|
187
|
+
const deps: ReleaseGateDeps = {
|
|
188
|
+
checkLocalReady: () => ({ status: "passed" }),
|
|
189
|
+
};
|
|
190
|
+
const result = await runReleaseGateCheck("/fake/repo", "local_ready", {}, deps);
|
|
191
|
+
assert.equal(result.target_stage, "local_ready");
|
|
192
|
+
assert.equal(result.stages.local_ready, "passed");
|
|
193
|
+
assert.equal(result.stages.packed_ready, "not_checked");
|
|
194
|
+
assert.equal(result.stages.published_verified, "not_checked");
|
|
195
|
+
assert.equal(result.stages.github_release_verified, "not_checked");
|
|
196
|
+
assert.equal(result.stages.ci_verified, "not_checked");
|
|
197
|
+
assert.equal(result.blocked_reason, undefined);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("local_ready failed → subsequent stages all not_checked, blocked_reason non-empty", async () => {
|
|
201
|
+
const deps: ReleaseGateDeps = {
|
|
202
|
+
checkLocalReady: () => ({ status: "failed", reason: "build exited with code 1" }),
|
|
203
|
+
};
|
|
204
|
+
const result = await runReleaseGateCheck(
|
|
205
|
+
"/fake/repo",
|
|
206
|
+
"ci_verified",
|
|
207
|
+
{
|
|
208
|
+
packageName: "patchwarden",
|
|
209
|
+
version: "1.0.0",
|
|
210
|
+
githubRepo: "user/repo",
|
|
211
|
+
branch: "main",
|
|
212
|
+
},
|
|
213
|
+
deps,
|
|
214
|
+
);
|
|
215
|
+
assert.equal(result.stages.local_ready, "failed");
|
|
216
|
+
assert.equal(result.stages.packed_ready, "not_checked");
|
|
217
|
+
assert.equal(result.stages.published_verified, "not_checked");
|
|
218
|
+
assert.equal(result.stages.github_release_verified, "not_checked");
|
|
219
|
+
assert.equal(result.stages.ci_verified, "not_checked");
|
|
220
|
+
assert.ok(result.blocked_reason, "blocked_reason should be non-empty");
|
|
221
|
+
assert.ok(
|
|
222
|
+
result.blocked_reason!.includes("local_ready"),
|
|
223
|
+
"blocked_reason should mention the failed stage",
|
|
224
|
+
);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("full pass-through: all stages passed up to ci_verified", async () => {
|
|
228
|
+
const deps: ReleaseGateDeps = {
|
|
229
|
+
checkLocalReady: () => ({ status: "passed" }),
|
|
230
|
+
checkPackedReady: () => ({ status: "passed", manifestPath: "/fake/release-artifact-manifest.json" }),
|
|
231
|
+
checkPublishedVerified: async () => ({ status: "passed" }),
|
|
232
|
+
checkGitHubReleaseVerified: async () => ({ status: "passed" }),
|
|
233
|
+
checkCiVerified: async () => ({ status: "passed" }),
|
|
234
|
+
};
|
|
235
|
+
const result = await runReleaseGateCheck(
|
|
236
|
+
"/fake/repo",
|
|
237
|
+
"ci_verified",
|
|
238
|
+
{
|
|
239
|
+
packageName: "patchwarden",
|
|
240
|
+
version: "1.0.0",
|
|
241
|
+
githubRepo: "user/repo",
|
|
242
|
+
branch: "main",
|
|
243
|
+
},
|
|
244
|
+
deps,
|
|
245
|
+
);
|
|
246
|
+
assert.equal(result.stages.local_ready, "passed");
|
|
247
|
+
assert.equal(result.stages.packed_ready, "passed");
|
|
248
|
+
assert.equal(result.stages.published_verified, "passed");
|
|
249
|
+
assert.equal(result.stages.github_release_verified, "passed");
|
|
250
|
+
assert.equal(result.stages.ci_verified, "passed");
|
|
251
|
+
assert.equal(result.blocked_reason, undefined);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("missing options for a required stage → failed with clear reason", async () => {
|
|
255
|
+
const deps: ReleaseGateDeps = {
|
|
256
|
+
checkLocalReady: () => ({ status: "passed" }),
|
|
257
|
+
checkPackedReady: () => ({ status: "passed" }),
|
|
258
|
+
};
|
|
259
|
+
// published_verified needs packageName + version, neither provided
|
|
260
|
+
const result = await runReleaseGateCheck(
|
|
261
|
+
"/fake/repo",
|
|
262
|
+
"published_verified",
|
|
263
|
+
{},
|
|
264
|
+
deps,
|
|
265
|
+
);
|
|
266
|
+
assert.equal(result.stages.local_ready, "passed");
|
|
267
|
+
assert.equal(result.stages.packed_ready, "passed");
|
|
268
|
+
assert.equal(result.stages.published_verified, "failed");
|
|
269
|
+
assert.ok(result.blocked_reason?.includes("published_verified"));
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// ── 5. Function name spelling (Verified, not Verfied) ─────────────
|
|
274
|
+
|
|
275
|
+
describe("function name spelling", () => {
|
|
276
|
+
it("checkPublishedVerified is a function (not Verfied)", () => {
|
|
277
|
+
assert.equal(typeof checkPublishedVerified, "function");
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("checkGitHubReleaseVerified is a function", () => {
|
|
281
|
+
assert.equal(typeof checkGitHubReleaseVerified, "function");
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("checkCiVerified is a function", () => {
|
|
285
|
+
assert.equal(typeof checkCiVerified, "function");
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("runReleaseGateCheck is a function", () => {
|
|
289
|
+
assert.equal(typeof runReleaseGateCheck, "function");
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// ── 6. computeTokenDigest ──────────────────────────────────────────
|
|
294
|
+
|
|
295
|
+
describe("computeTokenDigest", () => {
|
|
296
|
+
it("returns sha256: prefix with hex digest", () => {
|
|
297
|
+
const digest = computeTokenDigest("test-token");
|
|
298
|
+
assert.ok(digest.startsWith("sha256:"));
|
|
299
|
+
// sha256 hex is 64 chars + "sha256:" prefix = 71 chars
|
|
300
|
+
assert.equal(digest.length, 71);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it("does not contain the raw token", () => {
|
|
304
|
+
const TOKEN = "test-token";
|
|
305
|
+
const digest = computeTokenDigest(TOKEN);
|
|
306
|
+
assert.ok(!digest.includes(TOKEN), "digest must not contain the raw token");
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it("is deterministic for the same input", () => {
|
|
310
|
+
const d1 = computeTokenDigest("abc");
|
|
311
|
+
const d2 = computeTokenDigest("abc");
|
|
312
|
+
assert.equal(d1, d2);
|
|
313
|
+
});
|
|
314
|
+
});
|