more tests

This commit is contained in:
Moritz Schmitz von Hülst 2020-11-12 22:54:32 +01:00
parent c9d97025ca
commit 953789d88f
8 changed files with 287 additions and 221 deletions

View File

@ -1,165 +0,0 @@
import * as core from "@actions/core";
import { gte, inc, parse, ReleaseType, SemVer, valid } from "semver";
import { analyzeCommits } from "@semantic-release/commit-analyzer";
import { generateNotes } from "@semantic-release/release-notes-generator";
import {
getBranchFromRef,
getCommits,
getLatestPrereleaseTag,
getLatestTag,
getValidTags,
mapCustomReleaseTypes,
} from "./utils";
import { createTag } from "./github";
export async function run() {
try {
const defaultBump = core.getInput("default_bump") as ReleaseType | "false";
const tagPrefix = core.getInput("tag_prefix");
const customTag = core.getInput("custom_tag");
const releaseBranches = core.getInput("release_branches");
const preReleaseBranches = core.getInput("pre_release_branches");
const appendToPreReleaseTag = core.getInput("append_to_pre_release_tag");
const createAnnotatedTag = !!core.getInput("create_annotated_tag");
const dryRun = core.getInput("dry_run");
const customReleaseTypes = core.getInput("custom_release_types");
let mappedReleaseTypes;
if (customReleaseTypes) {
mappedReleaseTypes = mapCustomReleaseTypes(customReleaseTypes);
}
const { GITHUB_REF, GITHUB_SHA } = process.env;
if (!GITHUB_REF) {
core.setFailed("Missing GITHUB_REF.");
return;
}
if (!GITHUB_SHA) {
core.setFailed("Missing GITHUB_SHA.");
return;
}
const currentBranch = getBranchFromRef(GITHUB_REF);
const isReleaseBranch = releaseBranches
.split(",")
.some((branch) => currentBranch.match(branch));
const isPreReleaseBranch = preReleaseBranches
.split(",")
.some((branch) => currentBranch.match(branch));
const isPrerelease = !isReleaseBranch && isPreReleaseBranch;
const identifier = appendToPreReleaseTag ? appendToPreReleaseTag : currentBranch;
const validTags = await getValidTags();
const latestTag = getLatestTag(validTags);
const latestPrereleaseTag = getLatestPrereleaseTag(
validTags,
identifier
);
const commits = await getCommits(latestTag.commit.sha);
let newVersion: string;
if (customTag) {
newVersion = customTag;
} else {
let previousTag: SemVer | null;
if (!latestPrereleaseTag) {
previousTag = parse(latestTag.name);
} else {
previousTag = parse(
gte(latestTag.name, latestPrereleaseTag.name)
? latestTag.name
: latestPrereleaseTag.name
);
}
if (!previousTag) {
core.setFailed("Could not parse previous tag.");
return;
}
core.info(`Previous tag was ${previousTag}.`);
core.setOutput("previous_tag", previousTag.version);
const bump = await analyzeCommits(
{ releaseTypes: mappedReleaseTypes },
{ commits, logger: { log: console.info.bind(console) } }
);
if (!bump && defaultBump === "false") {
core.debug(
"No commit specifies the version bump. Skipping the tag creation."
);
return;
}
const releaseType: ReleaseType = isPrerelease
? `pre${bump || defaultBump}`
: bump || defaultBump;
const incrementedVersion = inc(
previousTag,
releaseType,
identifier
);
if (!incrementedVersion) {
core.setFailed("Could not increment version.");
return;
}
if (!valid(incrementedVersion)) {
core.setFailed(`${incrementedVersion} is not a valid semver.`);
return;
}
newVersion = incrementedVersion;
}
core.info(`New version is ${newVersion}.`);
core.setOutput("new_version", newVersion);
const newTag = `${tagPrefix}${newVersion}`;
core.info(`New tag after applying prefix is ${newTag}.`);
core.setOutput("new_tag", newTag);
const changelog = await generateNotes(
{},
{
commits,
logger: { log: console.info.bind(console) },
options: {
repositoryUrl: `https://github.com/${process.env.GITHUB_REPOSITORY}`,
},
lastRelease: { gitTag: latestTag.name },
nextRelease: { gitTag: newTag, version: newVersion },
}
);
core.info(`Changelog is ${changelog}.`);
core.setOutput("changelog", changelog);
if (!isReleaseBranch && !isPreReleaseBranch) {
core.info(
"This branch is neither a release nor a pre-release branch. Skipping the tag creation."
);
return;
}
if (validTags.map((tag) => tag.name).includes(newTag)) {
core.info("This tag already exists. Skipping the tag creation.");
return;
}
if (/true/i.test(dryRun)) {
core.info("Dry run: not performing tag action.");
return;
}
await createTag(newTag, createAnnotatedTag, GITHUB_SHA);
} catch (error) {
core.setFailed(error.message);
}
}

3
src/index.ts Normal file
View File

@ -0,0 +1,3 @@
import main from './main';
main();

View File

@ -1,3 +1,165 @@
import { run } from './action';
import * as core from "@actions/core";
import { gte, inc, parse, ReleaseType, SemVer, valid } from "semver";
import { analyzeCommits } from "@semantic-release/commit-analyzer";
import { generateNotes } from "@semantic-release/release-notes-generator";
import {
getBranchFromRef,
getCommits,
getLatestPrereleaseTag,
getLatestTag,
getValidTags,
mapCustomReleaseTypes,
} from "./utils";
import { createTag } from "./github";
run();
export default async () => {
try {
const defaultBump = core.getInput("default_bump") as ReleaseType | "false";
const tagPrefix = core.getInput("tag_prefix");
const customTag = core.getInput("custom_tag");
const releaseBranches = core.getInput("release_branches");
const preReleaseBranches = core.getInput("pre_release_branches");
const appendToPreReleaseTag = core.getInput("append_to_pre_release_tag");
const createAnnotatedTag = !!core.getInput("create_annotated_tag");
const dryRun = core.getInput("dry_run");
const customReleaseTypes = core.getInput("custom_release_types");
let mappedReleaseTypes;
if (customReleaseTypes) {
mappedReleaseTypes = mapCustomReleaseTypes(customReleaseTypes);
}
const { GITHUB_REF, GITHUB_SHA } = process.env;
if (!GITHUB_REF) {
core.setFailed("Missing GITHUB_REF.");
return;
}
if (!GITHUB_SHA) {
core.setFailed("Missing GITHUB_SHA.");
return;
}
const currentBranch = getBranchFromRef(GITHUB_REF);
const isReleaseBranch = releaseBranches
.split(",")
.some((branch) => currentBranch.match(branch));
const isPreReleaseBranch = preReleaseBranches
.split(",")
.some((branch) => currentBranch.match(branch));
const isPrerelease = !isReleaseBranch && isPreReleaseBranch;
const identifier = appendToPreReleaseTag ? appendToPreReleaseTag : currentBranch;
const validTags = await getValidTags();
const latestTag = getLatestTag(validTags);
const latestPrereleaseTag = getLatestPrereleaseTag(
validTags,
identifier
);
const commits = await getCommits(latestTag.commit.sha);
let newVersion: string;
if (customTag) {
newVersion = customTag;
} else {
let previousTag: SemVer | null;
if (!latestPrereleaseTag) {
previousTag = parse(latestTag.name);
} else {
previousTag = parse(
gte(latestTag.name, latestPrereleaseTag.name)
? latestTag.name
: latestPrereleaseTag.name
);
}
if (!previousTag) {
core.setFailed("Could not parse previous tag.");
return;
}
core.info(`Previous tag was ${previousTag}.`);
core.setOutput("previous_tag", previousTag.version);
const bump = await analyzeCommits(
{ releaseTypes: mappedReleaseTypes },
{ commits, logger: { log: console.info.bind(console) } }
);
if (!bump && defaultBump === "false") {
core.debug(
"No commit specifies the version bump. Skipping the tag creation."
);
return;
}
const releaseType: ReleaseType = isPrerelease
? `pre${bump || defaultBump}`
: bump || defaultBump;
const incrementedVersion = inc(
previousTag,
releaseType,
identifier
);
if (!incrementedVersion) {
core.setFailed("Could not increment version.");
return;
}
if (!valid(incrementedVersion)) {
core.setFailed(`${incrementedVersion} is not a valid semver.`);
return;
}
newVersion = incrementedVersion;
}
core.info(`New version is ${newVersion}.`);
core.setOutput("new_version", newVersion);
const newTag = `${tagPrefix}${newVersion}`;
core.info(`New tag after applying prefix is ${newTag}.`);
core.setOutput("new_tag", newTag);
const changelog = await generateNotes(
{},
{
commits,
logger: { log: console.info.bind(console) },
options: {
repositoryUrl: `https://github.com/${process.env.GITHUB_REPOSITORY}`,
},
lastRelease: { gitTag: latestTag.name },
nextRelease: { gitTag: newTag, version: newVersion },
}
);
core.info(`Changelog is ${changelog}.`);
core.setOutput("changelog", changelog);
if (!isReleaseBranch && !isPreReleaseBranch) {
core.info(
"This branch is neither a release nor a pre-release branch. Skipping the tag creation."
);
return;
}
if (validTags.map((tag) => tag.name).includes(newTag)) {
core.info("This tag already exists. Skipping the tag creation.");
return;
}
if (/true/i.test(dryRun)) {
core.info("Dry run: not performing tag action.");
return;
}
await createTag(newTag, createAnnotatedTag, GITHUB_SHA);
} catch (error) {
core.setFailed(error.message);
}
}

View File

@ -1,47 +0,0 @@
import { run } from '../src/action';
import * as utils from '../src/utils';
import * as github from '../src/github';
import * as core from '@actions/core';
const mockCreateTag = jest.spyOn(github, 'createTag').mockImplementation(async () => {
});
const mockSetFailed = jest.spyOn(core, 'setFailed');
describe('main tests', () => {
const mockGetCommits = (commits) => jest.spyOn(utils, 'getCommits').mockImplementation(async (sha) => {
return commits;
});
const mockGetValidTags = (validTags) => jest.spyOn(utils, 'getValidTags').mockImplementation(async () => {
return validTags;
});
beforeEach(() => {
jest.clearAllMocks();
process.env['GITHUB_REF'] = 'refs/heads/master';
process.env['GITHUB_SHA'] = 'GITHUB_SHA';
});
it('happy path', async () => {
/*
* Given
*/
const mockValidTags = mockGetValidTags([]);
const commits = [
{ message: 'fix: this is my first fix' },
]
const mockCommits = mockGetCommits(commits);
/*
* When
*/
await run();
/*
* Then
*/
expect(mockValidTags).toHaveBeenCalled();
expect(mockCommits).toHaveBeenCalledWith('HEAD');
expect(mockCreateTag).toHaveBeenCalledWith('0.0.1', expect.any(Boolean), expect.any(String));
expect(mockSetFailed).not.toBeCalled();
})
});

25
tests/helper.test.ts Normal file
View File

@ -0,0 +1,25 @@
import yaml from 'js-yaml';
import fs from 'fs';
import path from 'path';
export function setInput(key, value) {
process.env[`INPUT_${key.toUpperCase()}`] = value;
}
export function setInputs(map) {
Object.keys(map).forEach(key => setInput(key, map[key]));
}
export function parseDefaultInputs() {
const actionYaml = fs.readFileSync(path.join(process.cwd(), 'action.yml'));
const actionJson = yaml.safeLoad(actionYaml);
return Object
.keys(actionJson.inputs)
.filter(key => actionJson.inputs[key].default)
.reduce((obj, key) => ({ ...obj, [key]: actionJson.inputs[key].default }), {});
}
// Don't know how to have this file only for test but not have 'tsc' complain. So I made it a test file...
describe('helper', () => {
it('works', () => {});
});

View File

@ -1,7 +0,0 @@
export function setInput(key, value) {
process.env[`INPUT_${key.toUpperCase()}`] = value;
}
export function setInputs(map) {
Object.keys(map).forEach(key => setInput(key, map[key]));
}

92
tests/main.test.ts Normal file
View File

@ -0,0 +1,92 @@
import main from '../src/main';
import * as utils from '../src/utils';
import * as github from '../src/github';
import * as core from '@actions/core';
import { setInputs, parseDefaultInputs, setInput } from "./helper.test";
const mockCreateTag = jest.spyOn(github, 'createTag').mockImplementation(async () => {
});
const mockSetFailed = jest.spyOn(core, 'setFailed');
describe('main tests', () => {
const mockGetCommits = (commits) => jest.spyOn(utils, 'getCommits').mockImplementation(async (sha) => {
return commits;
});
const mockGetValidTags = (validTags) => jest.spyOn(utils, 'getValidTags').mockImplementation(async () => {
return validTags;
});
beforeEach(() => {
jest.clearAllMocks();
process.env['GITHUB_REF'] = 'refs/heads/master';
process.env['GITHUB_SHA'] = 'GITHUB_SHA';
setInputs(parseDefaultInputs());
});
it('does create initial tag', async () => {
/*
* Given
*/
const mockValidTags = mockGetValidTags([]);
const commits = [
{ message: 'fix: this is my first fix' },
]
const mockCommits = mockGetCommits(commits);
/*
* When
*/
await main();
/*
* Then
*/
expect(mockValidTags).toHaveBeenCalled();
expect(mockCommits).toHaveBeenCalledWith('HEAD');
expect(mockCreateTag).toHaveBeenCalledWith('v0.0.1', expect.any(Boolean), expect.any(String));
expect(mockSetFailed).not.toBeCalled();
});
it('does create patch tag without commits', async () => {
/*
* Given
*/
const mockValidTags = mockGetValidTags([{ name: 'v1.2.3', commit: { sha: '012345' } }]);
const mockCommits = mockGetCommits([]);
/*
* When
*/
await main();
/*
* Then
*/
expect(mockValidTags).toHaveBeenCalled();
expect(mockCommits).toHaveBeenCalledWith('012345');
expect(mockCreateTag).toHaveBeenCalledWith('v1.2.4', expect.any(Boolean), expect.any(String));
expect(mockSetFailed).not.toBeCalled();
});
it('does not create tag without commits and default_bump set to false', async () => {
/*
* Given
*/
setInput('default_bump', 'false');
const mockValidTags = mockGetValidTags([{ name: 'v1.2.3', commit: { sha: '012345' } }]);
const mockCommits = mockGetCommits([]);
/*
* When
*/
await main();
/*
* Then
*/
expect(mockValidTags).toHaveBeenCalled();
expect(mockCommits).toHaveBeenCalledWith('012345');
expect(mockCreateTag).not.toBeCalled();
expect(mockSetFailed).not.toBeCalled();
});
});

View File

@ -1,7 +1,10 @@
import * as utils from '../src/utils';
import * as core from '@actions/core';
import { getValidTags } from '../src/utils';
import * as github from '../src/github';
jest.spyOn(core, 'debug').mockImplementation(() => {});
describe('utils', () => {
it('maps custom release types', () => {
/*