|
1 | 1 | import { afterEach, describe, expect, it, vi } from "vitest"; |
2 | 2 | import { clearGitHubResponseCacheForTest, githubRateLimitAdmissionKeyForInstallation, latestGitHubRestRateLimitObservation } from "../../src/github/client"; |
3 | | -import { extractPreviewUrl, findPreviewUrlFromPrComments, getLatestDeploymentStatus, getPreviewBuildState } from "../../src/review/visual/preview-url"; |
| 3 | +import { |
| 4 | + extractPreviewUrl, |
| 5 | + findPreviewUrlFromChecks, |
| 6 | + findPreviewUrlFromPrComments, |
| 7 | + getLatestDeploymentStatus, |
| 8 | + getPreviewBuildState, |
| 9 | +} from "../../src/review/visual/preview-url"; |
4 | 10 |
|
5 | 11 | /** GitHub's `Link` header for a page that advertises a next page (the exact shape findAcrossPages walks). */ |
6 | 12 | 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"'; |
@@ -140,6 +146,124 @@ describe("preview-url pagination (#7450)", () => { |
140 | 146 | expect(fetchMock).toHaveBeenCalledTimes(2); |
141 | 147 | }); |
142 | 148 |
|
| 149 | + it("findPreviewUrlFromChecks follows Link: rel=next and finds the preview URL on page 2 (#7779)", async () => { |
| 150 | + const page1 = { |
| 151 | + check_runs: Array.from({ length: 100 }, (_v, i) => ({ |
| 152 | + name: `ci-${i}`, |
| 153 | + status: "completed", |
| 154 | + conclusion: "success", |
| 155 | + details_url: "https://example.com/ci", |
| 156 | + })), |
| 157 | + }; |
| 158 | + const page2 = { |
| 159 | + check_runs: [ |
| 160 | + { |
| 161 | + name: "Cloudflare Workers Builds", |
| 162 | + status: "completed", |
| 163 | + conclusion: "success", |
| 164 | + details_url: "https://pr-42.app.workers.dev/path", |
| 165 | + }, |
| 166 | + ], |
| 167 | + }; |
| 168 | + const fetchMock = vi.fn(async (input: RequestInfo | URL) => { |
| 169 | + const url = String(input); |
| 170 | + if (url.includes("/status") && !url.includes("check-runs")) return Response.json({ statuses: [] }); |
| 171 | + if (url.includes("check-runs")) { |
| 172 | + return isPage2(input) ? Response.json(page2) : Response.json(page1, { headers: { link: NEXT_LINK } }); |
| 173 | + } |
| 174 | + return Response.json({}); |
| 175 | + }); |
| 176 | + vi.stubGlobal("fetch", fetchMock); |
| 177 | + |
| 178 | + await expect(findPreviewUrlFromChecks({ token: "t", repo: REPO, sha: "deadbeef" })).resolves.toBe("https://pr-42.app.workers.dev"); |
| 179 | + const checkRunCalls = fetchMock.mock.calls.map((call) => String(call[0])).filter((url) => url.includes("check-runs")); |
| 180 | + expect(checkRunCalls).toHaveLength(2); |
| 181 | + expect(checkRunCalls[0]).toContain("/commits/deadbeef/check-runs?per_page=100"); |
| 182 | + expect(checkRunCalls[0]).not.toContain("&page="); |
| 183 | + expect(checkRunCalls[1]).toContain("&page=2"); |
| 184 | + }); |
| 185 | + |
| 186 | + it("findPreviewUrlFromChecks stops as soon as a preview URL is found on page 1 (#7779)", async () => { |
| 187 | + const fetchMock = vi.fn(async (input: RequestInfo | URL) => { |
| 188 | + const url = String(input); |
| 189 | + if (url.includes("/status") && !url.includes("check-runs")) return Response.json({ statuses: [] }); |
| 190 | + return Response.json( |
| 191 | + { |
| 192 | + check_runs: [ |
| 193 | + { |
| 194 | + name: "Cloudflare Workers Builds", |
| 195 | + status: "completed", |
| 196 | + conclusion: "success", |
| 197 | + details_url: "https://pr-1.app.workers.dev", |
| 198 | + }, |
| 199 | + ], |
| 200 | + }, |
| 201 | + { headers: { link: NEXT_LINK } }, |
| 202 | + ); |
| 203 | + }); |
| 204 | + vi.stubGlobal("fetch", fetchMock); |
| 205 | + await expect(findPreviewUrlFromChecks({ token: "t", repo: REPO, sha: "abc" })).resolves.toBe("https://pr-1.app.workers.dev"); |
| 206 | + expect(fetchMock.mock.calls.map((call) => String(call[0])).filter((url) => url.includes("check-runs"))).toHaveLength(1); |
| 207 | + }); |
| 208 | + |
| 209 | + it("findPreviewUrlFromChecks skips failed check-runs and reads output summary/text (#7779)", async () => { |
| 210 | + const fetchMock = vi.fn(async (input: RequestInfo | URL) => { |
| 211 | + const url = String(input); |
| 212 | + if (url.includes("/status") && !url.includes("check-runs")) return Response.json({ statuses: [] }); |
| 213 | + return Response.json({ |
| 214 | + check_runs: [ |
| 215 | + { |
| 216 | + name: "lint", |
| 217 | + status: "completed", |
| 218 | + conclusion: "failure", |
| 219 | + details_url: "https://pr-stale.app.workers.dev", |
| 220 | + }, |
| 221 | + { |
| 222 | + name: "Cloudflare Workers Builds", |
| 223 | + status: "completed", |
| 224 | + conclusion: "success", |
| 225 | + output: { summary: "Preview: https://pr-summary.app.workers.dev/x" }, |
| 226 | + }, |
| 227 | + ], |
| 228 | + }); |
| 229 | + }); |
| 230 | + vi.stubGlobal("fetch", fetchMock); |
| 231 | + await expect(findPreviewUrlFromChecks({ token: "t", repo: REPO, sha: "sum" })).resolves.toBe("https://pr-summary.app.workers.dev"); |
| 232 | + |
| 233 | + vi.stubGlobal("fetch", async (input: RequestInfo | URL) => { |
| 234 | + const url = String(input); |
| 235 | + if (url.includes("/status") && !url.includes("check-runs")) return Response.json({ statuses: [] }); |
| 236 | + return Response.json({ |
| 237 | + check_runs: [ |
| 238 | + { |
| 239 | + name: "Cloudflare Workers Builds", |
| 240 | + status: "completed", |
| 241 | + conclusion: "success", |
| 242 | + output: { text: "Deployed to https://pr-text.app.workers.dev" }, |
| 243 | + }, |
| 244 | + ], |
| 245 | + }); |
| 246 | + }); |
| 247 | + await expect(findPreviewUrlFromChecks({ token: "t", repo: REPO, sha: "txt" })).resolves.toBe("https://pr-text.app.workers.dev"); |
| 248 | + }); |
| 249 | + |
| 250 | + it("findPreviewUrlFromChecks returns null when check-runs pages have no preview URL (#7779)", async () => { |
| 251 | + const fetchMock = vi.fn(async (input: RequestInfo | URL) => { |
| 252 | + const url = String(input); |
| 253 | + if (url.includes("/status") && !url.includes("check-runs")) return Response.json({ statuses: [] }); |
| 254 | + if (url.includes("check-runs")) { |
| 255 | + // Missing check_runs exercises the ?? [] arm; page 2 has only non-preview details. |
| 256 | + return isPage2(input) |
| 257 | + ? Response.json({ check_runs: [{ name: "ci", status: "completed", conclusion: "success", details_url: "https://example.com" }] }) |
| 258 | + : Response.json({}, { headers: { link: NEXT_LINK } }); |
| 259 | + } |
| 260 | + return Response.json({}); |
| 261 | + }); |
| 262 | + vi.stubGlobal("fetch", fetchMock); |
| 263 | + await expect(findPreviewUrlFromChecks({ token: "t", repo: REPO, sha: "none" })).resolves.toBeNull(); |
| 264 | + expect(fetchMock.mock.calls.map((call) => String(call[0])).filter((url) => url.includes("check-runs"))).toHaveLength(2); |
| 265 | + }); |
| 266 | + |
143 | 267 | it("getPreviewBuildState classifies a completed Workers Builds check as succeeded or failed", async () => { |
144 | 268 | vi.stubGlobal("fetch", async () => Response.json({ check_runs: [{ name: "cloudflare pages", status: "completed", conclusion: "success" }] })); |
145 | 269 | await expect(getPreviewBuildState({ token: "t", repo: REPO, sha: "s1" })).resolves.toBe("succeeded"); |
|
0 commit comments