From 109bba88601783a5a934c6e4a63912d23b9f45a6 Mon Sep 17 00:00:00 2001 From: James Ives Date: Sun, 19 Jan 2020 15:33:14 -0500 Subject: [PATCH] Test Coverage Improvements (#141) * Test Coverage Improvements * Update env.js --- __tests__/env.js | 2 +- __tests__/execute.test.ts | 3 +- __tests__/git.test.ts | 118 ++++++++++++++++++++++++++++++++++++-- lib/constants.js | 24 ++++---- lib/execute.js | 12 ++-- lib/git.js | 34 +++++------ src/constants.ts | 24 ++++---- src/execute.ts | 13 +++-- src/git.ts | 23 +++++--- 9 files changed, 185 insertions(+), 68 deletions(-) diff --git a/__tests__/env.js b/__tests__/env.js index 06ab3fd5..b6e5dee0 100644 --- a/__tests__/env.js +++ b/__tests__/env.js @@ -1 +1 @@ -process.env.UNIT_TEST = "true"; +process.env.UNIT_TEST = "true" \ No newline at end of file diff --git a/__tests__/execute.test.ts b/__tests__/execute.test.ts index 778f3cd9..75631bef 100644 --- a/__tests__/execute.test.ts +++ b/__tests__/execute.test.ts @@ -1,4 +1,4 @@ -import { execute } from "../src/execute"; +import { execute, stdout } from "../src/execute"; import { exec } from "@actions/exec"; jest.mock("@actions/exec", () => ({ @@ -7,6 +7,7 @@ jest.mock("@actions/exec", () => ({ describe("execute", () => { it("should be called with the correct arguments", async () => { + await stdout("hello"); await execute("echo Montezuma", "./"); expect(exec).toBeCalledWith("echo Montezuma", [], { diff --git a/__tests__/git.test.ts b/__tests__/git.test.ts index aeb199f0..39812deb 100644 --- a/__tests__/git.test.ts +++ b/__tests__/git.test.ts @@ -5,9 +5,15 @@ process.env["GITHUB_SHA"] = "123"; import { action } from "../src/constants"; import { deploy, generateBranch, init, switchToBaseBranch } from "../src/git"; import { execute } from "../src/execute"; +import { setFailed } from "@actions/core"; const originalAction = JSON.stringify(action); +jest.mock("@actions/core", () => ({ + setFailed: jest.fn(), + getInput: jest.fn() +})); + jest.mock("../src/execute", () => ({ execute: jest.fn() })); @@ -21,6 +27,7 @@ describe("git", () => { it("should execute three commands if a GitHub token is provided", async () => { Object.assign(action, { build: "build", + branch: "branch", gitHubToken: "123", pusher: { name: "asd", @@ -36,6 +43,7 @@ describe("git", () => { it("should execute three commands if a Access token is provided", async () => { Object.assign(action, { build: "build", + branch: "branch", accessToken: "123", pusher: { name: "asd", @@ -52,14 +60,18 @@ describe("git", () => { it("should fail if there is no provided GitHub Token or Access Token", async () => { Object.assign(action, { build: "build", + branch: "branch", pusher: { name: "asd", email: "as@cat" - } + }, + gitHubToken: null, + accessToken: null, + ssh: null }); const call = await init(); - + expect(setFailed).toBeCalledTimes(1); expect(execute).toBeCalledTimes(0); expect(call).toBe("Initialization step complete..."); }); @@ -67,6 +79,7 @@ describe("git", () => { it("should fail if the build folder begins with a /", async () => { Object.assign(action, { accessToken: "123", + branch: "branch", build: "/", pusher: { name: "asd", @@ -76,6 +89,7 @@ describe("git", () => { const call = await init(); + expect(setFailed).toBeCalledTimes(1); expect(execute).toBeCalledTimes(0); expect(call).toBe("Initialization step complete..."); }); @@ -83,6 +97,7 @@ describe("git", () => { it("should fail if the build folder begins with a ./", async () => { Object.assign(action, { accessToken: "123", + branch: "branch", build: "./", pusher: { name: "asd", @@ -91,7 +106,7 @@ describe("git", () => { }); const call = await init(); - + expect(setFailed).toBeCalledTimes(1); expect(execute).toBeCalledTimes(0); expect(call).toBe("Initialization step complete..."); }); @@ -99,6 +114,7 @@ describe("git", () => { it("should not fail if root is used", async () => { Object.assign(action, { accessToken: "123", + branch: "branch", build: ".", pusher: { name: "asd", @@ -114,15 +130,52 @@ describe("git", () => { }); describe("generateBranch", () => { - it("should execute five commands", async () => { + it("should execute six commands", async () => { + Object.assign(action, { + accessToken: "123", + branch: "branch", + build: ".", + pusher: { + name: "asd", + email: "as@cat" + } + }); + const call = await generateBranch(); expect(execute).toBeCalledTimes(6); expect(call).toBe("Deployment branch creation step complete... ✅"); }); + + it("should fail if there is no branch", async () => { + Object.assign(action, { + accessToken: "123", + branch: null, + build: ".", + pusher: { + name: "asd", + email: "as@cat" + } + }); + + const call = await generateBranch(); + expect(execute).toBeCalledTimes(0); + expect(setFailed).toBeCalledTimes(1); + expect(call).toBe("Deployment branch creation step complete... ✅"); + }); }); describe("switchToBaseBranch", () => { it("should execute one command", async () => { + Object.assign(action, { + accessToken: "123", + branch: "branch", + build: ".", + pusher: { + name: "asd", + email: "as@cat" + } + }); + const call = await switchToBaseBranch(); expect(execute).toBeCalledTimes(1); expect(call).toBe("Switched to the base branch..."); @@ -133,6 +186,7 @@ describe("git", () => { it("should execute five commands", async () => { Object.assign(action, { build: "build", + branch: "branch", gitHubToken: "123", pusher: { name: "asd", @@ -146,5 +200,61 @@ describe("git", () => { expect(execute).toBeCalledTimes(18); expect(call).toBe("Commit step complete..."); }); + + it("should execute five commands with clean options", async () => { + Object.assign(action, { + build: "build", + branch: "branch", + gitHubToken: "123", + pusher: { + name: "asd", + email: "as@cat" + }, + clean: true, + cleanExclude: '["cat", "montezuma"]' + }); + + const call = await deploy(); + + // Includes the call to generateBranch + expect(execute).toBeCalledTimes(18); + expect(call).toBe("Commit step complete..."); + }); + + it("should gracefully handle incorrectly formatted clean exclude items", async () => { + Object.assign(action, { + build: "build", + branch: "branch", + gitHubToken: "123", + pusher: { + name: "asd", + email: "as@cat" + }, + clean: true, + cleanExclude: '["cat, "montezuma"]' // There is a syntax errror in the string. + }); + + const call = await deploy(); + + // Includes the call to generateBranch + expect(execute).toBeCalledTimes(18); + expect(call).toBe("Commit step complete..."); + }); + + it("should stop early if there is nothing to commit", async () => { + Object.assign(action, { + build: "build", + branch: "branch", + gitHubToken: "123", + pusher: { + name: "asd", + email: "as@cat" + }, + isTest: false // Setting this env variable to false means there will never be anything to commit and the action will exit early. + }); + + await deploy(); + expect(execute).toBeCalledTimes(12); + }); }); }); diff --git a/lib/constants.js b/lib/constants.js index 9e8c1569..c6920843 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -7,24 +7,24 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", { value: true }); -const core = __importStar(require("@actions/core")); +const core_1 = require("@actions/core"); const github = __importStar(require("@actions/github")); const { pusher, repository } = github.context.payload; exports.workspace = process.env.GITHUB_WORKSPACE; -exports.folder = core.getInput("FOLDER", { required: true }); -exports.isTest = process.env.UNIT_TEST; +exports.folder = core_1.getInput("FOLDER", { required: true }); exports.root = "."; // Required action data. exports.action = { - accessToken: core.getInput("ACCESS_TOKEN"), - baseBranch: core.getInput("BASE_BRANCH"), + accessToken: core_1.getInput("ACCESS_TOKEN"), + baseBranch: core_1.getInput("BASE_BRANCH"), build: exports.folder, - branch: core.getInput("BRANCH"), - commitMessage: core.getInput("COMMIT_MESSAGE"), - clean: core.getInput("CLEAN"), - cleanExclude: core.getInput("CLEAN_EXCLUDE"), + branch: core_1.getInput("BRANCH"), + commitMessage: core_1.getInput("COMMIT_MESSAGE"), + clean: core_1.getInput("CLEAN"), + cleanExclude: core_1.getInput("CLEAN_EXCLUDE"), defaultBranch: process.env.GITHUB_SHA ? process.env.GITHUB_SHA : "master", - ssh: core.getInput("SSH"), + isTest: process.env.UNIT_TEST, + ssh: core_1.getInput("SSH"), email: pusher && pusher.email ? pusher.email : `${process.env.GITHUB_ACTOR || @@ -32,13 +32,13 @@ exports.action = { gitHubRepository: repository && repository.full_name ? repository.full_name : process.env.GITHUB_REPOSITORY, - gitHubToken: core.getInput("GITHUB_TOKEN"), + gitHubToken: core_1.getInput("GITHUB_TOKEN"), name: pusher && pusher.name ? pusher.name : process.env.GITHUB_ACTOR ? process.env.GITHUB_ACTOR : "GitHub Pages Deploy Action", - targetFolder: core.getInput("TARGET_FOLDER") + targetFolder: core_1.getInput("TARGET_FOLDER") }; // Token Types exports.tokenType = exports.action.ssh diff --git a/lib/execute.js b/lib/execute.js index 34b45f36..55b9159f 100644 --- a/lib/execute.js +++ b/lib/execute.js @@ -10,6 +10,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }; Object.defineProperty(exports, "__esModule", { value: true }); const exec_1 = require("@actions/exec"); +// Stores the output from execute. +let output; /** Wrapper around the GitHub toolkit exec command which returns the output. * Also allows you to easily toggle the current working directory. * @param {String} cmd = The command to execute. @@ -18,16 +20,18 @@ const exec_1 = require("@actions/exec"); */ function execute(cmd, cwd) { return __awaiter(this, void 0, void 0, function* () { - let output = ""; + output = ""; yield exec_1.exec(cmd, [], { cwd, listeners: { - stdout: (data) => { - output += data.toString().trim(); - } + stdout } }); return Promise.resolve(output); }); } exports.execute = execute; +function stdout(data) { + output += data.toString().trim(); +} +exports.stdout = stdout; diff --git a/lib/git.js b/lib/git.js index e2288b7c..461c108a 100644 --- a/lib/git.js +++ b/lib/git.js @@ -8,18 +8,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; - return result; -}; Object.defineProperty(exports, "__esModule", { value: true }); -const core = __importStar(require("@actions/core")); +const core_1 = require("@actions/core"); +const constants_1 = require("./constants"); const execute_1 = require("./execute"); const util_1 = require("./util"); -const constants_1 = require("./constants"); /** Generates the branch if it doesn't exist on the remote. * @returns {Promise} */ @@ -29,18 +22,14 @@ function init() { if (util_1.isNullOrUndefined(constants_1.action.accessToken) && util_1.isNullOrUndefined(constants_1.action.gitHubToken) && util_1.isNullOrUndefined(constants_1.action.ssh)) { - return core.setFailed("You must provide the action with either a Personal Access Token or the GitHub Token secret in order to deploy. If you wish to use an ssh deploy token then you must set SSH to true."); - } - const sshAuthenticated = yield execute_1.execute(`ssh -T git@github.com | wc -l`, constants_1.workspace); - if (constants_1.action.ssh && !sshAuthenticated) { - return core.setFailed(`You're not authenticated with SSH. Please double check your deploy token configuration and try again.`); - } - else if (constants_1.action.ssh && sshAuthenticated) { - console.log("SSH is correctly authenticated, proceeding... 🔑"); + core_1.setFailed("You must provide the action with either a Personal Access Token or the GitHub Token secret in order to deploy. If you wish to use an ssh deploy token then you must set SSH to true."); + throw "No deployment token/method was provided."; } if (constants_1.action.build.startsWith("/") || constants_1.action.build.startsWith("./")) { - return core.setFailed(`The deployment folder cannot be prefixed with '/' or './'. Instead reference the folder name directly.`); + core_1.setFailed(`The deployment folder cannot be prefixed with '/' or './'. Instead reference the folder name directly.`); + throw "Incorrectly formatted build folder."; } + console.log(`Deploying using ${constants_1.tokenType}... 🔑`); yield execute_1.execute(`git init`, constants_1.workspace); yield execute_1.execute(`git config user.name ${constants_1.action.name}`, constants_1.workspace); yield execute_1.execute(`git config user.email ${constants_1.action.email}`, constants_1.workspace); @@ -49,7 +38,7 @@ function init() { yield execute_1.execute(`git fetch`, constants_1.workspace); } catch (error) { - core.setFailed(`There was an error initializing the repository: ${error}`); + console.log(`There was an error initializing the repository: ${error}`); } finally { return Promise.resolve("Initialization step complete..."); @@ -73,6 +62,9 @@ exports.switchToBaseBranch = switchToBaseBranch; function generateBranch() { return __awaiter(this, void 0, void 0, function* () { try { + if (util_1.isNullOrUndefined(constants_1.action.branch)) { + throw "Branch is required."; + } console.log(`Creating ${constants_1.action.branch} branch... 🔧`); yield switchToBaseBranch(); yield execute_1.execute(`git checkout --orphan ${constants_1.action.branch}`, constants_1.workspace); @@ -82,7 +74,7 @@ function generateBranch() { yield execute_1.execute(`git fetch`, constants_1.workspace); } catch (error) { - core.setFailed(`There was an error creating the deployment branch: ${error} ❌`); + core_1.setFailed(`There was an error creating the deployment branch: ${error} ❌`); } finally { return Promise.resolve("Deployment branch creation step complete... ✅"); @@ -131,7 +123,7 @@ function deploy() { ? `--delete ${excludes} --exclude CNAME --exclude .nojekyll` : ""} --exclude .ssh --exclude .git --exclude .github ${constants_1.action.build === constants_1.root ? `--exclude ${temporaryDeploymentDirectory}` : ""}`, constants_1.workspace); const hasFilesToCommit = yield execute_1.execute(`git status --porcelain`, temporaryDeploymentDirectory); - if (!hasFilesToCommit && !constants_1.isTest) { + if (!hasFilesToCommit && !constants_1.action.isTest) { console.log("There is nothing to commit. Exiting... ✅"); return Promise.resolve(); } diff --git a/src/constants.ts b/src/constants.ts index 3bc15bbd..fa972c37 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,24 +1,24 @@ -import * as core from "@actions/core"; +import { getInput } from "@actions/core"; import * as github from "@actions/github"; const { pusher, repository } = github.context.payload; export const workspace: any = process.env.GITHUB_WORKSPACE; -export const folder = core.getInput("FOLDER", { required: true }); -export const isTest = process.env.UNIT_TEST; +export const folder = getInput("FOLDER", { required: true }); export const root = "."; // Required action data. export const action = { - accessToken: core.getInput("ACCESS_TOKEN"), - baseBranch: core.getInput("BASE_BRANCH"), + accessToken: getInput("ACCESS_TOKEN"), + baseBranch: getInput("BASE_BRANCH"), build: folder, - branch: core.getInput("BRANCH"), - commitMessage: core.getInput("COMMIT_MESSAGE"), - clean: core.getInput("CLEAN"), - cleanExclude: core.getInput("CLEAN_EXCLUDE"), + branch: getInput("BRANCH"), + commitMessage: getInput("COMMIT_MESSAGE"), + clean: getInput("CLEAN"), + cleanExclude: getInput("CLEAN_EXCLUDE"), defaultBranch: process.env.GITHUB_SHA ? process.env.GITHUB_SHA : "master", - ssh: core.getInput("SSH"), + isTest: process.env.UNIT_TEST, + ssh: getInput("SSH"), email: pusher && pusher.email ? pusher.email @@ -28,14 +28,14 @@ export const action = { repository && repository.full_name ? repository.full_name : process.env.GITHUB_REPOSITORY, - gitHubToken: core.getInput("GITHUB_TOKEN"), + gitHubToken: getInput("GITHUB_TOKEN"), name: pusher && pusher.name ? pusher.name : process.env.GITHUB_ACTOR ? process.env.GITHUB_ACTOR : "GitHub Pages Deploy Action", - targetFolder: core.getInput("TARGET_FOLDER") + targetFolder: getInput("TARGET_FOLDER") }; // Token Types diff --git a/src/execute.ts b/src/execute.ts index a8fa2abd..4b653970 100644 --- a/src/execute.ts +++ b/src/execute.ts @@ -1,5 +1,8 @@ import { exec } from "@actions/exec"; +// Stores the output from execute. +let output: string; + /** Wrapper around the GitHub toolkit exec command which returns the output. * Also allows you to easily toggle the current working directory. * @param {String} cmd = The command to execute. @@ -7,16 +10,18 @@ import { exec } from "@actions/exec"; * @returns {Promise} - The output from the command. */ export async function execute(cmd: string, cwd: string): Promise { - let output = ""; + output = ""; await exec(cmd, [], { cwd, listeners: { - stdout: (data: Buffer) => { - output += data.toString().trim(); - } + stdout } }); return Promise.resolve(output); } + +export function stdout(data: any) { + output += data.toString().trim(); +} diff --git a/src/git.ts b/src/git.ts index ca581036..8a381d9c 100644 --- a/src/git.ts +++ b/src/git.ts @@ -1,7 +1,6 @@ -import * as core from "@actions/core"; +import { setFailed } from "@actions/core"; import { action, - isTest, repositoryPath, root, tokenType, @@ -20,15 +19,19 @@ export async function init(): Promise { isNullOrUndefined(action.gitHubToken) && isNullOrUndefined(action.ssh) ) { - return core.setFailed( + setFailed( "You must provide the action with either a Personal Access Token or the GitHub Token secret in order to deploy. If you wish to use an ssh deploy token then you must set SSH to true." ); + + throw "No deployment token/method was provided."; } if (action.build.startsWith("/") || action.build.startsWith("./")) { - return core.setFailed( + setFailed( `The deployment folder cannot be prefixed with '/' or './'. Instead reference the folder name directly.` ); + + throw "Incorrectly formatted build folder."; } console.log(`Deploying using ${tokenType}... 🔑`); @@ -39,7 +42,7 @@ export async function init(): Promise { await execute(`git remote add origin ${repositoryPath}`, workspace); await execute(`git fetch`, workspace); } catch (error) { - core.setFailed(`There was an error initializing the repository: ${error}`); + console.log(`There was an error initializing the repository: ${error}`); } finally { return Promise.resolve("Initialization step complete..."); } @@ -64,6 +67,10 @@ export async function switchToBaseBranch(): Promise { */ export async function generateBranch(): Promise { try { + if (isNullOrUndefined(action.branch)) { + throw "Branch is required."; + } + console.log(`Creating ${action.branch} branch... 🔧`); await switchToBaseBranch(); await execute(`git checkout --orphan ${action.branch}`, workspace); @@ -75,9 +82,7 @@ export async function generateBranch(): Promise { await execute(`git push ${repositoryPath} ${action.branch}`, workspace); await execute(`git fetch`, workspace); } catch (error) { - core.setFailed( - `There was an error creating the deployment branch: ${error} ❌` - ); + setFailed(`There was an error creating the deployment branch: ${error} ❌`); } finally { return Promise.resolve("Deployment branch creation step complete... ✅"); } @@ -149,7 +154,7 @@ export async function deploy(): Promise { temporaryDeploymentDirectory ); - if (!hasFilesToCommit && !isTest) { + if (!hasFilesToCommit && !action.isTest) { console.log("There is nothing to commit. Exiting... ✅"); return Promise.resolve(); }