Something went wrong. Try again.
Find the cast and crew shared by two movies or TV shows crosscredit.haltcase.dev
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
import { previewName } from "../../scripts/deployment.ts";
const mocks = vi.hoisted(() => ({ exec: vi.fn< ( command: string, args: string[], options?: { stdio?: string; env?: NodeJS.ProcessEnv } ) => string >(), write: vi.fn(), unlink: vi.fn(), rmdir: vi.fn(), fetch: vi.fn<typeof fetch>()}));
vi.mock("node:child_process", () => ({ execFileSync: mocks.exec }));
vi.mock("node:fs", () => ({ mkdirSync: vi.fn(), mkdtempSync: () => ".cloudflare/ci-secrets-test", writeFileSync: mocks.write, unlinkSync: mocks.unlink, rmdirSync: mocks.rmdir}));
function cfCalls(command: string): unknown[][] { return mocks.exec.mock.calls.filter((call: unknown[]) => { const [, args] = call;
return Array.isArray(args) && args[1] === command; });}
beforeEach(() => { vi.resetModules(); vi.clearAllMocks(); vi.stubGlobal("fetch", mocks.fetch); mocks.fetch.mockResolvedValue(new Response(null, { status: 200 })); vi.stubEnv("CLOUDFLARE_API_TOKEN", "test-api-token"); vi.stubEnv("CLOUDFLARE_ACCOUNT_ID", "test-account"); vi.stubEnv("TMDB_READ_ACCESS_TOKEN", "test-tmdb-token"); vi.stubEnv("TANGLED_PIPELINE_KIND", "push"); vi.stubEnv("TANGLED_REF_TYPE", "branch"); vi.stubEnv("TANGLED_REF_NAME", "main"); vi.stubEnv("TANGLED_SHA", "branch-sha"); vi.stubEnv("CROSSCREDIT_CI_ACTION", ""); vi.stubEnv("TANGLED_PR_SOURCE_BRANCH", "feature/search"); vi.stubEnv("TANGLED_PR_TARGET_BRANCH", "main"); vi.stubEnv("TANGLED_PR_SOURCE_SHA", "branch-sha"); vi.stubEnv("TANGLED_COMMENT_APP_PASSWORD", "");
vi.spyOn(console, "info").mockImplementation(() => { // Keep expected CI progress messages out of test output. });
mocks.exec.mockImplementation((command: string, args: string[]) => { if (args[1] === "previews" && args[2] === "deploy") { return JSON.stringify({ type: "preview", version: 1, preview_name: previewName("feature/search"), preview_urls: ["https://pr-search-crosscredit.example.workers.dev"], deployment_urls: [] }); }
if (command !== "git") { return "[]"; }
if (args[0] === "ls-remote") { return "branch-sha\trefs/heads/main"; }
return ""; });});
afterEach(() => { vi.unstubAllEnvs(); vi.unstubAllGlobals(); vi.restoreAllMocks();});
describe("production CI deployment", () => { it.each(["schedule", "manual"])("rejects %s events", async (kind) => { vi.stubEnv("TANGLED_PIPELINE_KIND", kind); await expect(import("../../scripts/ci.ts")).rejects.toThrow("Expected a Tangled push to main"); expect(mocks.exec).not.toHaveBeenCalled(); });
it("rejects non-main branch pushes", async () => { vi.stubEnv("TANGLED_REF_NAME", "feature/search"); await expect(import("../../scripts/ci.ts")).rejects.toThrow("Expected a Tangled push to main"); expect(mocks.exec).not.toHaveBeenCalled(); });
it("checks and builds main before deploying production", async () => { await import("../../scripts/ci.ts");
expect( mocks.exec.mock.calls.filter(([command]) => command === "pnpm").map(([, args]) => args) ).toEqual([["check"], ["typecheck"], ["test"], ["build", "--mode", "production"]]);
expect(cfCalls("deploy")).toHaveLength(1); expect(cfCalls("workers")).toHaveLength(0); expect(mocks.fetch).not.toHaveBeenCalled(); });
it("stops before building or publishing when a quality check fails", async () => { mocks.exec.mockImplementation(() => { throw new Error("check failed"); });
await expect(import("../../scripts/ci.ts")).rejects.toThrow("check failed"); expect(mocks.exec.mock.calls.map(([, args]) => args)).toEqual([["check"]]); expect(mocks.write).not.toHaveBeenCalled(); });
it("rejects an unknown CI action before running commands", async () => { vi.stubEnv("CROSSCREDIT_CI_ACTION", "typo"); await expect(import("../../scripts/ci.ts")).rejects.toThrow("Unknown CI action"); expect(mocks.exec).not.toHaveBeenCalled(); expect(mocks.fetch).not.toHaveBeenCalled(); });
it("skips deployment if main advances during the build", async () => { let reads = 0;
mocks.exec.mockImplementation((command) => { if (command !== "git") { return ""; }
reads += 1;
return reads === 1 ? "branch-sha\trefs/heads/main" : "new-sha\trefs/heads/main"; });
await import("../../scripts/ci.ts"); expect(cfCalls("deploy")).toHaveLength(0); expect(mocks.write).not.toHaveBeenCalled(); });
it("does not deploy an old push over the branch's newer commit", async () => { vi.stubEnv("TANGLED_PIPELINE_KIND", "push"); vi.stubEnv("TANGLED_REF_TYPE", "branch"); vi.stubEnv("TANGLED_REF_NAME", "main"); vi.stubEnv("TANGLED_SHA", "outdated-sha"); await import("../../scripts/ci.ts"); expect(cfCalls("deploy")).toHaveLength(0); expect(mocks.write).not.toHaveBeenCalled(); });
it("removes the temporary secrets file even when deploying fails", async () => { vi.stubEnv("TANGLED_PIPELINE_KIND", "push"); vi.stubEnv("TANGLED_REF_TYPE", "branch"); vi.stubEnv("TANGLED_REF_NAME", "main"); vi.stubEnv("TANGLED_SHA", "branch-sha");
const original = mocks.exec.getMockImplementation();
mocks.exec.mockImplementation((command: string, args: string[]) => { if (args[1] === "deploy") { throw new Error("deployment failed"); }
return original?.(command, args) ?? ""; });
await expect(import("../../scripts/ci.ts")).rejects.toThrow("deployment failed");
expect(mocks.write).toHaveBeenCalledWith( expect.any(String), JSON.stringify({ TMDB_READ_ACCESS_TOKEN: "test-tmdb-token" }), { mode: 0o600 } );
expect(mocks.unlink).toHaveBeenCalledWith( expect.stringMatching(/ci-secrets-test[/\\]secrets\.json$/) );
expect(mocks.rmdir).toHaveBeenCalledWith(".cloudflare/ci-secrets-test"); });});
describe("native PR previews", () => { it("uploads a native preview of the existing Worker without publishing production or writing secrets", async () => { vi.stubEnv("TANGLED_PIPELINE_KIND", "pull_request"); await import("../../scripts/ci.ts");
const build = mocks.exec.mock.calls.find( ([command, args]) => command === "pnpm" && args[0] === "build" );
expect(build?.[1]).toEqual(["build", "--mode", "preview"]); expect(build?.[2]?.env?.CLOUDFLARE_PREVIEW_BUILD).toBe("true");
expect(cfCalls("previews").map(([, args]) => args)).toEqual([ [ expect.any(String), "previews", "deploy", previewName("feature/search"), "--prebuilt", "--mode", "preview", "--worker", "crosscredit" ] ]);
expect(cfCalls("deploy")).toHaveLength(0); expect(cfCalls("workers")).toHaveLength(0); expect(mocks.write).not.toHaveBeenCalled(); expect(mocks.fetch).not.toHaveBeenCalled(); });
it("does not try to comment when preview upload fails", async () => { vi.stubEnv("TANGLED_PIPELINE_KIND", "pull_request");
const original = mocks.exec.getMockImplementation();
mocks.exec.mockImplementation((command, args, options) => { if (args[1] === "previews") { throw new Error("preview upload failed"); }
return original?.(command, args, options) ?? ""; });
await expect(import("../../scripts/ci.ts")).rejects.toThrow("preview upload failed"); expect(mocks.fetch).not.toHaveBeenCalled(); });
it("deletes only the native source-branch preview on the cleanup workflow", async () => { vi.stubEnv("TANGLED_PIPELINE_KIND", "pull_request"); vi.stubEnv("CROSSCREDIT_CI_ACTION", "delete-preview"); await import("../../scripts/ci.ts"); expect(mocks.exec).not.toHaveBeenCalled(); expect(mocks.fetch).toHaveBeenCalledOnce();
expect(mocks.fetch.mock.calls[0]?.[0]).toBe( `https://api.cloudflare.com/client/v4/accounts/test-account/workers/workers/crosscredit/previews/${previewName("feature/search")}` );
expect(mocks.fetch.mock.calls[0]?.[1]?.method).toBe("DELETE");
expect(mocks.fetch.mock.calls[0]?.[1]?.headers).toEqual({ Authorization: "Bearer test-api-token" }); });
it("accepts cleanup when the Preview is already absent", async () => { vi.stubEnv("TANGLED_PIPELINE_KIND", "pull_request"); vi.stubEnv("CROSSCREDIT_CI_ACTION", "delete-preview");
mocks.fetch.mockResolvedValue( Response.json({ errors: [{ code: 10_025, message: "Preview not found" }] }, { status: 404 }) );
await import("../../scripts/ci.ts"); expect(mocks.fetch).toHaveBeenCalledOnce(); });
it.each([403, 404, 500])("keeps other cleanup failures visible (HTTP %s)", async (status) => { vi.stubEnv("TANGLED_PIPELINE_KIND", "pull_request"); vi.stubEnv("CROSSCREDIT_CI_ACTION", "delete-preview"); mocks.fetch.mockResolvedValue(Response.json({ errors: [{ code: 9999 }] }, { status }));
await expect(import("../../scripts/ci.ts")).rejects.toThrow( `Preview deletion failed (HTTP ${status})` ); });
it.each(["", "delete-preview"])( "skips untrusted PR operations (%s) when secrets are withheld", async (action) => { vi.stubEnv("TANGLED_PIPELINE_KIND", "pull_request"); vi.stubEnv("CROSSCREDIT_CI_ACTION", action); vi.stubEnv("CLOUDFLARE_API_TOKEN", ""); await import("../../scripts/ci.ts"); expect(mocks.exec).not.toHaveBeenCalled(); expect(mocks.fetch).not.toHaveBeenCalled(); } );
it("rejects cleanup on a production push", async () => { vi.stubEnv("CROSSCREDIT_CI_ACTION", "delete-preview"); await expect(import("../../scripts/ci.ts")).rejects.toThrow("Expected a Tangled push"); expect(mocks.exec).not.toHaveBeenCalled(); });
it("skips superseded PR revisions", async () => { vi.stubEnv("TANGLED_PIPELINE_KIND", "pull_request"); vi.stubEnv("TANGLED_PR_SOURCE_SHA", "older-sha"); await import("../../scripts/ci.ts"); expect(cfCalls("previews")).toHaveLength(0); });
it("rejects PRs targeting another branch", async () => { vi.stubEnv("TANGLED_PIPELINE_KIND", "pull_request"); vi.stubEnv("TANGLED_PR_TARGET_BRANCH", "release"); await expect(import("../../scripts/ci.ts")).rejects.toThrow("Expected a Tangled push"); expect(mocks.exec).not.toHaveBeenCalled(); });});