|
1 | 1 | import { afterEach, describe, expect, it, vi } from "vitest"; |
2 | 2 | import { clearGitHubResponseCacheForTest, githubRateLimitAdmissionKeyForInstallation, latestGitHubRestRateLimitObservation } from "../../src/github/client"; |
3 | | -import { extractPreviewUrl, findPreviewUrlFromPrComments, getPreviewBuildState } from "../../src/review/visual/preview-url"; |
| 3 | +import { extractPreviewUrl, findPreviewUrlFromChecks, findPreviewUrlFromPrComments, getPreviewBuildState } from "../../src/review/visual/preview-url"; |
4 | 4 |
|
5 | 5 | /** GitHub's `Link` header for a page that advertises a next page (the exact shape findAcrossPages walks). */ |
6 | 6 | const NEXT_LINK = '<https://api.github.com/resource?per_page=100&page=99>; rel="next", <https://api.github.com/resource?per_page=100&page=99>; rel="last"'; |
@@ -166,6 +166,84 @@ describe("preview-url pagination (#7450)", () => { |
166 | 166 | await expect(getPreviewBuildState({ token: "t", repo: REPO, sha: "fail" })).resolves.toBe("absent"); |
167 | 167 | expect(failLater).toHaveBeenCalledTimes(2); |
168 | 168 | }); |
| 169 | + |
| 170 | + // findPreviewUrlFromChecks scans the combined commit-status first, then walks check-runs. These stub the |
| 171 | + // status endpoint to an empty/no-preview payload so control reaches the check-runs walk under test (#7779). |
| 172 | + const emptyStatus = () => Response.json({ statuses: [] }); |
| 173 | + |
| 174 | + it("findPreviewUrlFromChecks follows Link: rel=next and finds the preview check-run on page 2 (#7779)", async () => { |
| 175 | + // The bug: a commit with >100 check-runs pushes the Cloudflare Workers Builds check onto page 2+, which the |
| 176 | + // old single-page read missed. Page 1 is 100 non-preview runs advertising a next page; page 2 carries it. |
| 177 | + const fetchMock = vi.fn(async (input: RequestInfo | URL) => { |
| 178 | + const url = String(input); |
| 179 | + if (url.includes("/status")) return emptyStatus(); |
| 180 | + if (url.includes("/check-runs")) { |
| 181 | + if (isPage2(input)) return Response.json({ check_runs: [{ status: "completed", conclusion: "success", details_url: "https://pr-9.pages.dev/" }] }); |
| 182 | + return Response.json({ check_runs: Array.from({ length: 100 }, () => ({ status: "completed", conclusion: "success", details_url: "https://example.com/ci" })) }, { headers: { link: NEXT_LINK } }); |
| 183 | + } |
| 184 | + return Response.json({}); |
| 185 | + }); |
| 186 | + vi.stubGlobal("fetch", fetchMock); |
| 187 | + await expect(findPreviewUrlFromChecks({ token: "t", repo: REPO, sha: "abc" })).resolves.toBe("https://pr-9.pages.dev"); |
| 188 | + // Page 2 of check-runs was actually fetched (the pre-#7779 bug never read it). |
| 189 | + expect(fetchMock.mock.calls.some((c) => String(c[0]).includes("/check-runs") && isPage2(c[0]))).toBe(true); |
| 190 | + }); |
| 191 | + |
| 192 | + it("findPreviewUrlFromChecks skips a completed non-success check-run and reads the preview from output.summary/text (#7779)", async () => { |
| 193 | + // Exercises the `continue` (completed && conclusion !== success) skip and the details_url ?? summary ?? text |
| 194 | + // fallback chain: the first run is a hard failure (skipped), the second carries the URL only in output.text. |
| 195 | + vi.stubGlobal("fetch", async (input: RequestInfo | URL) => { |
| 196 | + const url = String(input); |
| 197 | + if (url.includes("/status")) return emptyStatus(); |
| 198 | + if (url.includes("/check-runs")) { |
| 199 | + return Response.json({ |
| 200 | + check_runs: [ |
| 201 | + { status: "completed", conclusion: "failure", details_url: "https://should-be-skipped.pages.dev/" }, |
| 202 | + { status: "completed", conclusion: "success", output: { text: "preview at https://from-text.workers.dev/" } }, |
| 203 | + ], |
| 204 | + }); |
| 205 | + } |
| 206 | + return Response.json({}); |
| 207 | + }); |
| 208 | + await expect(findPreviewUrlFromChecks({ token: "t", repo: REPO, sha: "abc" })).resolves.toBe("https://from-text.workers.dev"); |
| 209 | + }); |
| 210 | + |
| 211 | + it("findPreviewUrlFromChecks returns null when no check-run carries a preview URL and there is no next page (#7779)", async () => { |
| 212 | + // The probe finds nothing on the only page and the payload has no `check_runs` array (treated as []): the |
| 213 | + // whole discovery degrades to null rather than throwing. |
| 214 | + vi.stubGlobal("fetch", async (input: RequestInfo | URL) => { |
| 215 | + const url = String(input); |
| 216 | + if (url.includes("/status")) return emptyStatus(); |
| 217 | + if (url.includes("/check-runs")) return Response.json({}); |
| 218 | + return Response.json({}); |
| 219 | + }); |
| 220 | + await expect(findPreviewUrlFromChecks({ token: "t", repo: REPO, sha: "abc" })).resolves.toBeNull(); |
| 221 | + }); |
| 222 | + |
| 223 | + it("findPreviewUrlFromChecks degrades to null when the check-runs read fails, without throwing (#7779)", async () => { |
| 224 | + // The .catch(() => null) around the paginated walk preserves the pre-#7779 best-effort behavior: a check-runs |
| 225 | + // fetch error must not throw out of the function, just yield no URL from that source. |
| 226 | + vi.stubGlobal("fetch", async (input: RequestInfo | URL) => { |
| 227 | + const url = String(input); |
| 228 | + if (url.includes("/status")) return emptyStatus(); |
| 229 | + if (url.includes("/check-runs")) throw new Error("check-runs network down"); |
| 230 | + return Response.json({}); |
| 231 | + }); |
| 232 | + await expect(findPreviewUrlFromChecks({ token: "t", repo: REPO, sha: "abc" })).resolves.toBeNull(); |
| 233 | + }); |
| 234 | + |
| 235 | + it("findPreviewUrlFromChecks returns a preview URL straight from the combined commit-status, before touching check-runs (#7779)", async () => { |
| 236 | + // The status-first path: a success status whose target_url is a preview host short-circuits before the |
| 237 | + // check-runs walk runs at all. |
| 238 | + const fetchMock = vi.fn(async (input: RequestInfo | URL) => { |
| 239 | + const url = String(input); |
| 240 | + if (url.includes("/status")) return Response.json({ statuses: [{ state: "success", target_url: "https://from-status.pages.dev/" }] }); |
| 241 | + return Response.json({}); |
| 242 | + }); |
| 243 | + vi.stubGlobal("fetch", fetchMock); |
| 244 | + await expect(findPreviewUrlFromChecks({ token: "t", repo: REPO, sha: "abc" })).resolves.toBe("https://from-status.pages.dev"); |
| 245 | + expect(fetchMock.mock.calls.some((c) => String(c[0]).includes("/check-runs"))).toBe(false); |
| 246 | + }); |
169 | 247 | }); |
170 | 248 |
|
171 | 249 | describe("extractPreviewUrl", () => { |
|
0 commit comments