| 1 | const assert = require("node:assert/strict"); |
| 2 | const test = require("node:test"); |
| 3 | |
| 4 | const { |
| 5 | renderComment, |
| 6 | resolve, |
| 7 | upsertCommentForCurrentHead, |
| 8 | validateResult, |
| 9 | } = require("./thread-transfer-report.cjs"); |
| 10 | |
| 11 | function result(overrides = {}) { |
| 12 | const observed = { |
| 13 | totalWireBytes: 2_200_000, |
| 14 | threadSnapshotWireBytes: 1_950_000, |
| 15 | threadSnapshotDecodedBytes: 9_100_000, |
| 16 | measuredTurnWebSocketWireBytes: 250_000, |
| 17 | measuredTurnWebSocketDecodedBytes: 1_150_000, |
| 18 | measuredTurnWebSocketMessages: 15, |
| 19 | }; |
| 20 | const ceiling = { |
| 21 | totalWireBytes: 2_900_000, |
| 22 | threadSnapshotWireBytes: 2_600_000, |
| 23 | measuredTurnWebSocketWireBytes: 320_000, |
| 24 | measuredTurnWebSocketDecodedBytes: 1_550_000, |
| 25 | measuredTurnWebSocketMessages: 20, |
| 26 | }; |
| 27 | return { |
| 28 | schemaVersion: 1, |
| 29 | scenario: { |
| 30 | id: "thread-transfer-v1", |
| 31 | historyTurns: 10, |
| 32 | historyCommandToolsPerTurn: 5, |
| 33 | historyMcpResultBytes: 900_000, |
| 34 | measuredCommandTools: 20, |
| 35 | measuredMcpResultBytes: 1_100_000, |
| 36 | }, |
| 37 | providers: { |
| 38 | codex: { observed: { ...observed, ...overrides }, ceiling }, |
| 39 | claudeAgent: { observed, ceiling }, |
| 40 | }, |
| 41 | }; |
| 42 | } |
| 43 | |
| 44 | test("validates the fixed artifact schema", () => { |
| 45 | assert.equal(validateResult(result()).schemaVersion, 1); |
| 46 | assert.throws( |
| 47 | () => validateResult({ ...result(), injectedMarkdown: "@everyone" }), |
| 48 | /unexpected fields/, |
| 49 | ); |
| 50 | assert.throws( |
| 51 | () => validateResult(result({ totalWireBytes: "lots" })), |
| 52 | /non-negative safe integer/, |
| 53 | ); |
| 54 | }); |
| 55 | |
| 56 | test("renders baseline, impact, ceiling, and ceiling changes", () => { |
| 57 | const baseline = result(); |
| 58 | const current = result({ measuredTurnWebSocketWireBytes: 260_000 }); |
| 59 | current.providers.codex.ceiling = { |
| 60 | ...current.providers.codex.ceiling, |
| 61 | measuredTurnWebSocketWireBytes: 330_000, |
| 62 | }; |
| 63 | const comment = renderComment({ |
| 64 | current, |
| 65 | baseline, |
| 66 | currentRun: { |
| 67 | sha: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", |
| 68 | conclusion: "success", |
| 69 | url: "https://github.com/pingdotgg/t3code/actions/runs/2", |
| 70 | }, |
| 71 | baselineRun: { |
| 72 | sha: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", |
| 73 | matchesBase: true, |
| 74 | url: "https://github.com/pingdotgg/t3code/actions/runs/1", |
| 75 | }, |
| 76 | }); |
| 77 | |
| 78 | assert.match(comment, /Main baseline \| This PR \| Impact \| PR ceiling/); |
| 79 | assert.match(comment, /\+9\.8 KiB \(\+4\.0%\)/); |
| 80 | assert.match(comment, /This PR changes transfer ceilings/); |
| 81 | assert.match(comment, /312\.5 KiB → 322\.3 KiB/); |
| 82 | assert.match(comment, /<!-- t3-thread-transfer-report -->/); |
| 83 | assert.match( |
| 84 | comment, |
| 85 | /<!-- t3-thread-transfer-result-sha:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb -->/, |
| 86 | ); |
| 87 | }); |
| 88 | |
| 89 | test("resolves a fallback PR with a redacted head repo and exact main baseline", async () => { |
| 90 | const outputs = {}; |
| 91 | const listWorkflowRunArtifacts = () => {}; |
| 92 | const listWorkflowRuns = () => {}; |
| 93 | const listPullRequestsAssociatedWithCommit = () => {}; |
| 94 | const github = { |
| 95 | paginate: async (method, input) => { |
| 96 | if (method === listPullRequestsAssociatedWithCommit) { |
| 97 | return [ |
| 98 | { |
| 99 | number: 5350, |
| 100 | state: "open", |
| 101 | head: { sha: "head-sha", ref: "feature-branch", repo: null }, |
| 102 | }, |
| 103 | ]; |
| 104 | } |
| 105 | if (method === listWorkflowRunArtifacts) { |
| 106 | return [ |
| 107 | { |
| 108 | name: "thread-transfer-results", |
| 109 | expired: false, |
| 110 | runId: input.run_id, |
| 111 | }, |
| 112 | ]; |
| 113 | } |
| 114 | if (method === listWorkflowRuns) { |
| 115 | return [{ id: 1, head_sha: "base-sha" }]; |
| 116 | } |
| 117 | throw new Error("unexpected pagination call"); |
| 118 | }, |
| 119 | rest: { |
| 120 | actions: { listWorkflowRunArtifacts, listWorkflowRuns }, |
| 121 | pulls: { |
| 122 | get: async () => ({ |
| 123 | data: { |
| 124 | head: { sha: "head-sha" }, |
| 125 | base: { sha: "base-sha", ref: "main" }, |
| 126 | }, |
| 127 | }), |
| 128 | }, |
| 129 | repos: { listPullRequestsAssociatedWithCommit }, |
| 130 | }, |
| 131 | }; |
| 132 | await resolve({ |
| 133 | github, |
| 134 | context: { |
| 135 | repo: { owner: "pingdotgg", repo: "t3code" }, |
| 136 | payload: { |
| 137 | workflow_run: { |
| 138 | id: 2, |
| 139 | event: "pull_request", |
| 140 | workflow_id: 3, |
| 141 | head_sha: "head-sha", |
| 142 | head_branch: "feature-branch", |
| 143 | head_repository: { full_name: "pingdotgg/t3code" }, |
| 144 | conclusion: "success", |
| 145 | pull_requests: [], |
| 146 | }, |
| 147 | }, |
| 148 | }, |
| 149 | core: { |
| 150 | info: () => {}, |
| 151 | setOutput: (key, value) => { |
| 152 | outputs[key] = value; |
| 153 | }, |
| 154 | }, |
| 155 | }); |
| 156 | |
| 157 | assert.equal(outputs.publish, "true"); |
| 158 | assert.equal(outputs.pull_number, "5350"); |
| 159 | assert.equal(outputs.pr_artifact, "true"); |
| 160 | assert.equal(outputs.baseline_run_id, "1"); |
| 161 | assert.equal(outputs.baseline_matches_base, "true"); |
| 162 | }); |
| 163 | |
| 164 | test("does not guess when a fallback commit belongs to multiple PRs", async () => { |
| 165 | const outputs = {}; |
| 166 | const listPullRequestsAssociatedWithCommit = () => {}; |
| 167 | let fetchedPull = false; |
| 168 | await resolve({ |
| 169 | github: { |
| 170 | paginate: async (method) => { |
| 171 | assert.equal(method, listPullRequestsAssociatedWithCommit); |
| 172 | return [5350, 5351].map((number) => ({ |
| 173 | number, |
| 174 | state: "open", |
| 175 | head: { |
| 176 | sha: "head-sha", |
| 177 | ref: "feature-branch", |
| 178 | repo: { full_name: "pingdotgg/t3code" }, |
| 179 | }, |
| 180 | })); |
| 181 | }, |
| 182 | rest: { |
| 183 | actions: {}, |
| 184 | pulls: { |
| 185 | get: async () => { |
| 186 | fetchedPull = true; |
| 187 | }, |
| 188 | }, |
| 189 | repos: { listPullRequestsAssociatedWithCommit }, |
| 190 | }, |
| 191 | }, |
| 192 | context: { |
| 193 | repo: { owner: "pingdotgg", repo: "t3code" }, |
| 194 | payload: { |
| 195 | workflow_run: { |
| 196 | id: 2, |
| 197 | event: "pull_request", |
| 198 | workflow_id: 3, |
| 199 | head_sha: "head-sha", |
| 200 | head_branch: "feature-branch", |
| 201 | head_repository: { full_name: "pingdotgg/t3code" }, |
| 202 | conclusion: "success", |
| 203 | pull_requests: [], |
| 204 | }, |
| 205 | }, |
| 206 | }, |
| 207 | core: { |
| 208 | info: () => {}, |
| 209 | setOutput: (key, value) => { |
| 210 | outputs[key] = value; |
| 211 | }, |
| 212 | }, |
| 213 | }); |
| 214 | |
| 215 | assert.equal(outputs.publish, "false"); |
| 216 | assert.equal(fetchedPull, false); |
| 217 | }); |
| 218 | |
| 219 | test("does not publish a stale result after the PR head advances", async () => { |
| 220 | let listedComments = false; |
| 221 | const info = []; |
| 222 | const published = await upsertCommentForCurrentHead( |
| 223 | { |
| 224 | paginate: async () => { |
| 225 | listedComments = true; |
| 226 | return []; |
| 227 | }, |
| 228 | rest: { |
| 229 | issues: { |
| 230 | listComments: () => {}, |
| 231 | createComment: () => { |
| 232 | throw new Error("must not create a stale comment"); |
| 233 | }, |
| 234 | updateComment: () => { |
| 235 | throw new Error("must not update a stale comment"); |
| 236 | }, |
| 237 | }, |
| 238 | pulls: { |
| 239 | get: async () => ({ data: { head: { sha: "new-head-sha" } } }), |
| 240 | }, |
| 241 | }, |
| 242 | }, |
| 243 | { repo: { owner: "pingdotgg", repo: "t3code" } }, |
| 244 | { info: (message) => info.push(message) }, |
| 245 | 5350, |
| 246 | "old-head-sha", |
| 247 | "stale body", |
| 248 | ); |
| 249 | |
| 250 | assert.equal(published, false); |
| 251 | assert.equal(listedComments, false); |
| 252 | assert.deepEqual(info, ["Skipping stale CI result old-head-sha; PR head is new-head-sha."]); |
| 253 | }); |
| 254 | |
| 255 | test("preserves a successful result when a same-SHA rerun has no artifact", async () => { |
| 256 | let updatedComment = false; |
| 257 | const sha = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; |
| 258 | const published = await upsertCommentForCurrentHead( |
| 259 | { |
| 260 | paginate: async () => [ |
| 261 | { |
| 262 | id: 1, |
| 263 | user: { login: "github-actions[bot]" }, |
| 264 | body: `<!-- t3-thread-transfer-report -->\n<!-- t3-thread-transfer-result-sha:${sha} -->`, |
| 265 | }, |
| 266 | ], |
| 267 | rest: { |
| 268 | issues: { |
| 269 | listComments: () => {}, |
| 270 | createComment: () => { |
| 271 | updatedComment = true; |
| 272 | }, |
| 273 | updateComment: () => { |
| 274 | updatedComment = true; |
| 275 | }, |
| 276 | }, |
| 277 | pulls: { |
| 278 | get: async () => ({ data: { head: { sha } } }), |
| 279 | }, |
| 280 | }, |
| 281 | }, |
| 282 | { repo: { owner: "pingdotgg", repo: "t3code" } }, |
| 283 | { info: () => {} }, |
| 284 | 5350, |
| 285 | sha, |
| 286 | "missing artifact warning", |
| 287 | { preserveResultSha: sha }, |
| 288 | ); |
| 289 | |
| 290 | assert.equal(published, true); |
| 291 | assert.equal(updatedComment, false); |
| 292 | }); |
| 293 | |
| 294 | test("accepts V2 without comparing it to the V1 scenario", () => { |
| 295 | const current = result(); |
| 296 | current.scenario.id = "thread-transfer-v2"; |
| 297 | current.providers.codex.ceiling = { ...current.providers.codex.ceiling, totalWireBytes: 3000000 }; |
| 298 | assert.equal(validateResult(current), current); |
| 299 | const comment = renderComment({ |
| 300 | current, |
| 301 | baseline: result(), |
| 302 | currentRun: { sha: "bbbbbbbb", conclusion: "success", url: "https://example.com/current" }, |
| 303 | baselineRun: { sha: "aaaaaaaa", matchesBase: true, url: "https://example.com/baseline" }, |
| 304 | }); |
| 305 | assert.match(comment, /fixture changed/); |
| 306 | assert.doesNotMatch(comment, /This PR changes transfer ceilings/); |
| 307 | assert.doesNotMatch(comment, /[+-]\d+\.\d+%/); |
| 308 | current.scenario.id = "unrecognized"; |
| 309 | assert.throws(() => validateResult(current), /not supported/); |
| 310 | }); |