From fc7a09fef0cb86aabf9d96fde2b15809c2bfc689 Mon Sep 17 00:00:00 2001 From: Maximo Guk <62088388+Maximo-Guk@users.noreply.github.com> Date: Mon, 18 Nov 2024 12:33:14 -0600 Subject: [PATCH] Add tests for github service --- package-lock.json | 10 ++++ package.json | 1 + src/service/github.spec.ts | 54 ++++++++++++++++++++ src/service/github.ts | 78 +++++++++++++++++++++++++++++ src/test/mocks.ts | 10 ++++ src/wranglerArtifactManager.test.ts | 12 ++--- 6 files changed, 159 insertions(+), 6 deletions(-) create mode 100644 src/service/github.spec.ts create mode 100644 src/service/github.ts diff --git a/package-lock.json b/package-lock.json index 1fcfcf98..39a3c4a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@changesets/changelog-github": "^0.5.0", "@changesets/cli": "^2.27.9", "@cloudflare/workers-types": "^4.20241022.0", + "@types/mock-fs": "^4.13.4", "@types/node": "^22.9.0", "@types/semver": "^7.5.8", "@vercel/ncc": "^0.38.2", @@ -1375,6 +1376,15 @@ "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true }, + "node_modules/@types/mock-fs": { + "version": "4.13.4", + "resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.4.tgz", + "integrity": "sha512-mXmM0o6lULPI8z3XNnQCpL0BGxPwx1Ul1wXYEPBGl4efShyxW2Rln0JOPEWGyZaYZMM6OVXM/15zUuFMY52ljg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "22.9.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", diff --git a/package.json b/package.json index 7c876cd4..ca5a9d0e 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "@changesets/changelog-github": "^0.5.0", "@changesets/cli": "^2.27.9", "@cloudflare/workers-types": "^4.20241022.0", + "@types/mock-fs": "^4.13.4", "@types/node": "^22.9.0", "@types/semver": "^7.5.8", "@vercel/ncc": "^0.38.2", diff --git a/src/service/github.spec.ts b/src/service/github.spec.ts new file mode 100644 index 00000000..82118142 --- /dev/null +++ b/src/service/github.spec.ts @@ -0,0 +1,54 @@ +import { afterEach, describe, it, vi } from "vitest"; +import { setupServer } from "msw/node"; +import { createGitHubDeployment, createJobSummary } from "./github"; +import { getOctokit } from "@actions/github"; +import { mockGithubDeployments } from "../test/mocks"; +import { getTestConfig } from "../test/test-utils"; +import mockfs from "mock-fs"; + +afterEach(async () => { + mockfs.restore(); +}); + +describe("github", () => { + it("Calls createGitHubDeployment successfully", async () => { + const githubUser = "mock-user"; + const githubRepoName = "wrangler-action"; + const server = setupServer( + ...mockGithubDeployments({ githubUser, githubRepoName }).handlers, + ); + server.listen({ onUnhandledRequest: "error" }); + vi.stubEnv("GITHUB_REPOSITORY", `${githubUser}/${githubRepoName}`); + + const testConfig = getTestConfig(); + const octokit = getOctokit(testConfig.GITHUB_TOKEN, { request: fetch }); + await createGitHubDeployment({ + config: testConfig, + octokit, + productionBranch: "production-branch", + deploymentId: "fake-deployment-id", + projectName: "fake-project-name", + deploymentUrl: "https://fake-deployment-url.com", + environment: "production", + }); + server.close(); + }); + it("Calls createJobSummary successfully", async () => { + const githubUser = "mock-user"; + const githubRepoName = "wrangler-action"; + const server = setupServer( + ...mockGithubDeployments({ githubUser, githubRepoName }).handlers, + ); + server.listen({ onUnhandledRequest: "error" }); + vi.stubEnv("GITHUB_STEP_SUMMARY", "summary"); + mockfs({ + summary: mockfs.file(), + }); + await createJobSummary({ + commitHash: "fake-commit-hash", + deploymentUrl: "https://fake-deployment-url.com", + aliasUrl: "https://fake-alias-url.com", + }); + server.close(); + }); +}); diff --git a/src/service/github.ts b/src/service/github.ts new file mode 100644 index 00000000..f78c9555 --- /dev/null +++ b/src/service/github.ts @@ -0,0 +1,78 @@ +import { summary } from "@actions/core"; +import { context, getOctokit } from "@actions/github"; +import { env } from "process"; +import { WranglerActionConfig } from "../wranglerAction"; + +type Octokit = ReturnType; + +export async function createGitHubDeployment({ + config, + octokit, + productionBranch, + environment, + deploymentId, + projectName, + deploymentUrl, +}: { + config: WranglerActionConfig; + octokit: Octokit; + productionBranch: string; + environment: string; + deploymentId: string | null; + projectName: string; + deploymentUrl?: string; +}) { + const githubBranch = env.GITHUB_HEAD_REF || env.GITHUB_REF_NAME; + const productionEnvironment = githubBranch === productionBranch; + + const deployment = await octokit.rest.repos.createDeployment({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: githubBranch || context.ref, + auto_merge: false, + description: "Cloudflare Pages", + required_contexts: [], + environment, + production_environment: productionEnvironment, + }); + + if (deployment.status === 201) { + await octokit.rest.repos.createDeploymentStatus({ + owner: context.repo.owner, + repo: context.repo.repo, + deployment_id: deployment.data.id, + environment, + environment_url: deploymentUrl, + production_environment: productionEnvironment, + // don't have project_name or deployment_id I think + log_url: `https://dash.cloudflare.com/${config.CLOUDFLARE_ACCOUNT_ID}/pages/view/${projectName}/${deploymentId}`, + description: "Cloudflare Pages", + state: "success", + auto_inactive: false, + }); + } +} + +export async function createJobSummary({ + commitHash, + deploymentUrl, + aliasUrl, +}: { + commitHash: string; + deploymentUrl?: string; + aliasUrl?: string; +}) { + await summary + .addRaw( + ` +# Deploying with Cloudflare Pages + +| Name | Result | +| ----------------------- | - | +| **Last commit:** | ${commitHash} | +| **Preview URL**: | ${deploymentUrl} | +| **Branch Preview URL**: | ${aliasUrl} | + `, + ) + .write(); +} diff --git a/src/test/mocks.ts b/src/test/mocks.ts index bfa20e55..b64a6969 100644 --- a/src/test/mocks.ts +++ b/src/test/mocks.ts @@ -1,4 +1,5 @@ import { http, HttpResponse } from "msw"; +import { z } from "zod"; export function mockGithubDeployments({ githubUser, @@ -15,6 +16,15 @@ export function mockGithubDeployments({ if (request.headers.get("Authorization") === null) { return HttpResponse.text("error: no auth token", { status: 400 }); } + const GithubDeploymentsRequest = z.object({ + auto_merge: z.literal(false), + description: z.literal("Cloudflare Pages"), + required_contexts: z.array(z.string()).length(0), + environment: z.literal("production"), + production_environment: z.literal(false), + }); + // validate request body + GithubDeploymentsRequest.parse(await request.json()); return HttpResponse.json(null); }, diff --git a/src/wranglerArtifactManager.test.ts b/src/wranglerArtifactManager.test.ts index 00a8840b..c3b1b11a 100644 --- a/src/wranglerArtifactManager.test.ts +++ b/src/wranglerArtifactManager.test.ts @@ -1,4 +1,4 @@ -import mock from "mock-fs"; +import mockfs from "mock-fs"; import { afterEach, describe, expect, it } from "vitest"; import { getDetailedPagesDeployOutput, @@ -6,12 +6,12 @@ import { } from "./wranglerArtifactManager"; afterEach(async () => { - mock.restore(); + mockfs.restore(); }); describe("wranglerArtifactsManager", () => { describe("getWranglerArtifacts()", async () => { it("Returns only wrangler output files from a given directory", async () => { - mock({ + mockfs({ testOutputDir: { "wrangler-output-2024-10-17_18-48-40_463-2e6e83.json": ` {"version": 1, "type":"wrangler-session", "wrangler_version":"3.81.0", "command_line_args":["what's up"], "log_file_path": "/here"} @@ -27,7 +27,7 @@ describe("wranglerArtifactsManager", () => { ]); }); it("Returns an empty list when the output directory doesn't exist", async () => { - mock({ + mockfs({ notTheDirWeWant: {}, }); @@ -38,7 +38,7 @@ describe("wranglerArtifactsManager", () => { describe("getDetailedPagesDeployOutput()", async () => { it("Returns only detailed pages deploy output from wrangler artifacts", async () => { - mock({ + mockfs({ testOutputDir: { "wrangler-output-2024-10-17_18-48-40_463-2e6e83.json": ` {"version": 1, "type":"wrangler-session", "wrangler_version":"3.81.0", "command_line_args":["what's up"], "log_file_path": "/here"} @@ -60,7 +60,7 @@ describe("wranglerArtifactsManager", () => { }); }), it("Skips artifact entries that are not parseable", async () => { - mock({ + mockfs({ testOutputDir: { "wrangler-output-2024-10-17_18-48-40_463-2e6e83.json": ` this line is invalid json.