Split responsibilities between Input and BuildParameters models

This commit is contained in:
Webber 2020-05-21 17:44:56 +02:00 committed by Webber Takken
parent 02ff5bbef2
commit 7e17091251
10 changed files with 329 additions and 131 deletions

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
import { Action, BuildParameters, Cache, Docker, Input, ImageTag } from './model'; import { Action, BuildParameters, Cache, Docker, ImageTag } from './model';
const core = require('@actions/core'); const core = require('@actions/core');
@ -7,7 +7,8 @@ async function action() {
Cache.verify(); Cache.verify();
const { dockerfile, workspace, actionFolder } = Action; const { dockerfile, workspace, actionFolder } = Action;
const buildParameters = BuildParameters.create(await Input.getFromUser());
const buildParameters = await BuildParameters.create();
const baseImage = new ImageTag(buildParameters); const baseImage = new ImageTag(buildParameters);
// Build docker image // Build docker image

View File

@ -0,0 +1,38 @@
/* eslint-disable unicorn/prevent-abbreviations */
// Import these named export into your test file:
export const mockProjectPath = jest.fn().mockResolvedValue('mockProjectPath');
export const mockIsDirtyAllowed = jest.fn().mockResolvedValue(false);
export const mockBranch = jest.fn().mockResolvedValue('mockBranch');
export const mockHeadRef = jest.fn().mockResolvedValue('mockHeadRef');
export const mockRef = jest.fn().mockResolvedValue('mockRef');
export const mockDetermineVersion = jest.fn().mockResolvedValue('1.2.3');
export const mockGenerateSemanticVersion = jest.fn().mockResolvedValue('2.3.4');
export const mockGenerateTagVersion = jest.fn().mockResolvedValue('1.0');
export const mockParseSemanticVersion = jest.fn().mockResolvedValue({});
export const mockFetch = jest.fn().mockImplementation(() => {});
export const mockGetVersionDescription = jest.fn().mockResolvedValue('1.2-3-g12345678-dirty');
export const mockIsDirty = jest.fn().mockResolvedValue(false);
export const mockGetTag = jest.fn().mockResolvedValue('v1.0');
export const mockHasAnyVersionTags = jest.fn().mockResolvedValue(true);
export const mockGetTotalNumberOfCommits = jest.fn().mockResolvedValue(3);
export const mockGit = jest.fn().mockImplementation(() => {});
export default {
projectPath: mockProjectPath,
isDirtyAllowed: mockIsDirtyAllowed,
branch: mockBranch,
headRef: mockHeadRef,
ref: mockRef,
determineVersion: mockDetermineVersion,
generateSemanticVersion: mockGenerateSemanticVersion,
generateTagVersion: mockGenerateTagVersion,
parseSemanticVersion: mockParseSemanticVersion,
fetch: mockFetch,
getVersionDescription: mockGetVersionDescription,
isDirty: mockIsDirty,
getTag: mockGetTag,
hasAnyVersionTags: mockHasAnyVersionTags,
getTotalNumberOfCommits: mockGetTotalNumberOfCommits,
git: mockGit,
};

View File

@ -1,28 +1,25 @@
import Input from './input';
import Platform from './platform'; import Platform from './platform';
import Versioning from './versioning';
class BuildParameters { class BuildParameters {
static create(parameters) { static async create() {
const { const buildFile = this.parseBuildFile(Input.buildName, Input.targetPlatform);
version, const buildVersion = await Versioning.determineVersion(
targetPlatform, Input.versioningStrategy,
projectPath, Input.specifiedVersion,
buildName, );
buildsPath,
buildMethod,
buildVersion,
customParameters,
} = parameters;
return { return {
version, version: Input.unityVersion,
platform: targetPlatform, platform: Input.targetPlatform,
projectPath, projectPath: Input.projectPath,
buildName, buildName: Input.buildName,
buildPath: `${buildsPath}/${targetPlatform}`, buildPath: `${Input.buildsPath}/${Input.targetPlatform}`,
buildFile: this.parseBuildFile(buildName, targetPlatform), buildFile,
buildMethod, buildMethod: Input.buildMethod,
buildVersion, buildVersion,
customParameters, customParameters: Input.customParameters,
}; };
} }

View File

@ -1,82 +1,110 @@
import Versioning from './versioning';
import BuildParameters from './build-parameters'; import BuildParameters from './build-parameters';
import Input from './input';
import Platform from './platform'; import Platform from './platform';
const determineVersion = jest
.spyOn(Versioning, 'determineVersion')
.mockImplementation(() => '1.3.37');
afterEach(() => {
jest.clearAllMocks();
});
describe('BuildParameters', () => { describe('BuildParameters', () => {
describe('create', () => { describe('create', () => {
const someParameters = { it('does not throw', async () => {
version: 'someVersion', await expect(BuildParameters.create()).resolves.not.toThrow();
targetPlatform: 'somePlatform',
projectPath: 'path/to/project',
buildName: 'someBuildName',
buildsPath: 'someBuildsPath',
buildMethod: 'Namespace.Class.Method',
customParameters: '-someParam someValue',
};
it('does not throw', () => {
expect(() => BuildParameters.create(someParameters)).not.toThrow();
}); });
it('returns the version', () => { it('determines the version only once', async () => {
expect(BuildParameters.create(someParameters).version).toStrictEqual(someParameters.version); await BuildParameters.create();
expect(determineVersion).toHaveBeenCalledTimes(1);
}); });
it('returns the platform', () => { it('returns the version', async () => {
expect(BuildParameters.create(someParameters).platform).toStrictEqual( const mockValue = 'someVersion';
someParameters.targetPlatform, jest.spyOn(Input, 'unityVersion', 'get').mockReturnValue(mockValue);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ version: mockValue }),
); );
}); });
it('returns the project path', () => { it('returns the platform', async () => {
expect(BuildParameters.create(someParameters).projectPath).toStrictEqual( const mockValue = 'somePlatform';
someParameters.projectPath, jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(mockValue);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ platform: mockValue }),
); );
}); });
it('returns the build name', () => { it('returns the project path', async () => {
expect(BuildParameters.create(someParameters).buildName).toStrictEqual( const mockValue = 'path/to/project';
someParameters.buildName, jest.spyOn(Input, 'projectPath', 'get').mockReturnValue(mockValue);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ projectPath: mockValue }),
); );
}); });
it('returns the build path', () => { it('returns the build name', async () => {
expect(BuildParameters.create(someParameters).buildPath).toStrictEqual( const mockValue = 'someBuildName';
`${someParameters.buildsPath}/${someParameters.targetPlatform}`, jest.spyOn(Input, 'buildName', 'get').mockReturnValue(mockValue);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ buildName: mockValue }),
); );
}); });
describe('build file', () => { it('returns the build path', async () => {
it('returns the build file', () => { const mockPath = 'somePath';
expect(BuildParameters.create(someParameters).buildFile).toStrictEqual( const mockPlatform = 'somePlatform';
someParameters.buildName, const expectedBuildPath = `${mockPath}/${mockPlatform}`;
jest.spyOn(Input, 'buildsPath', 'get').mockReturnValue(mockPath);
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(mockPlatform);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ buildPath: expectedBuildPath }),
);
});
it('returns the build file', async () => {
const mockValue = 'someBuildName';
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(mockValue);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ buildFile: mockValue }),
); );
}); });
test.each([Platform.types.StandaloneWindows, Platform.types.StandaloneWindows64])( test.each([Platform.types.StandaloneWindows, Platform.types.StandaloneWindows64])(
'appends exe for %s', 'appends exe for %s',
(targetPlatform) => { async (targetPlatform) => {
expect( jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
BuildParameters.create({ ...someParameters, targetPlatform }).buildFile, jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
).toStrictEqual(`${someParameters.buildName}.exe`); await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ buildFile: `${targetPlatform}.exe` }),
);
}, },
); );
test.each([Platform.types.Android])('appends apk for %s', (targetPlatform) => { test.each([Platform.types.Android])('appends apk for %s', async (targetPlatform) => {
expect( jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
BuildParameters.create({ ...someParameters, targetPlatform }).buildFile, jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
).toStrictEqual(`${someParameters.buildName}.apk`); await expect(BuildParameters.create()).resolves.toEqual(
}); expect.objectContaining({ buildFile: `${targetPlatform}.apk` }),
});
it('returns the build method', () => {
expect(BuildParameters.create(someParameters).buildMethod).toStrictEqual(
someParameters.buildMethod,
); );
}); });
it('returns the custom parameters', () => { it('returns the build method', async () => {
expect(BuildParameters.create(someParameters).customParameters).toStrictEqual( const mockValue = 'Namespace.ClassName.BuildMethod';
someParameters.customParameters, jest.spyOn(Input, 'buildMethod', 'get').mockReturnValue(mockValue);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ buildMethod: mockValue }),
);
});
it('returns the custom parameters', async () => {
const mockValue = '-profile SomeProfile -someBoolean -someValue exampleValue';
jest.spyOn(Input, 'customParameters', 'get').mockReturnValue(mockValue);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ customParameters: mockValue }),
); );
}); });
}); });

View File

@ -1,3 +1,4 @@
import * as core from '@actions/core';
import fs from 'fs'; import fs from 'fs';
import Action from './action'; import Action from './action';
import Project from './project'; import Project from './project';
@ -14,11 +15,11 @@ class Cache {
return; return;
} }
// eslint-disable-next-line no-console core.warning(`
console.log(`
Library folder does not exist. Library folder does not exist.
Consider setting up caching to speed up your workflow Consider setting up caching to speed up your workflow,
If this is not your first build.`); if this is not your first build.
`);
} }
} }

View File

@ -1,41 +1,54 @@
import Platform from './platform'; import Platform from './platform';
import Versioning from './versioning';
const core = require('@actions/core'); const core = require('@actions/core');
/**
* Input variables specified in workflows using "with" prop.
*
* Note that input is always passed as a string, even booleans.
*/
class Input { class Input {
static async getFromUser() { static get unityVersion() {
// Input variables specified in workflows using "with" prop. return core.getInput('unityVersion');
const version = core.getInput('unityVersion'); }
const targetPlatform = core.getInput('targetPlatform') || Platform.default;
static get targetPlatform() {
return core.getInput('targetPlatform') || Platform.default;
}
static get projectPath() {
const rawProjectPath = core.getInput('projectPath') || '.'; const rawProjectPath = core.getInput('projectPath') || '.';
const buildName = core.getInput('buildName') || targetPlatform; return rawProjectPath.replace(/\/$/, '');
const buildsPath = core.getInput('buildsPath') || 'build'; }
const buildMethod = core.getInput('buildMethod'); // processed in docker file
const versioningStrategy = core.getInput('versioning') || 'Semantic';
const specifiedVersion = core.getInput('version') || '';
const rawAllowDirtyBuild = core.getInput('allowDirtyBuild') || 'false';
const customParameters = core.getInput('customParameters') || '';
// Sanitise input static get buildName() {
const projectPath = rawProjectPath.replace(/\/$/, ''); return core.getInput('buildName') || this.targetPlatform;
const allowDirtyBuild = rawAllowDirtyBuild === 'true' ? 'true' : 'false'; }
// Parse input static get buildsPath() {
const buildVersion = await Versioning.determineVersion(versioningStrategy, specifiedVersion); return core.getInput('buildsPath') || 'build';
}
// Return validated input static get buildMethod() {
return { return core.getInput('buildMethod'); // processed in docker file
version, }
targetPlatform,
projectPath, static get versioningStrategy() {
buildName, return core.getInput('versioning') || 'Semantic';
buildsPath, }
buildMethod,
buildVersion, static get specifiedVersion() {
allowDirtyBuild, return core.getInput('version') || '';
customParameters, }
};
static get allowDirtyBuild() {
const input = core.getInput('allowDirtyBuild') || 'false';
return input === 'true' ? 'true' : 'false';
}
static get customParameters() {
return core.getInput('customParameters') || '';
} }
} }

View File

@ -1,27 +1,151 @@
import Input from './input'; import * as core from '@actions/core';
import Versioning from './versioning';
const determineVersion = jest import Input from './input';
.spyOn(Versioning, 'determineVersion') import Platform from './platform';
.mockImplementation(() => '1.3.37');
afterEach(() => { afterEach(() => {
jest.clearAllMocks(); jest.restoreAllMocks();
}); });
describe('Input', () => { describe('Input', () => {
describe('getFromUser', () => { describe('unityVersion', () => {
it('does not throw', async () => { it('returns the default value', () => {
await expect(Input.getFromUser()).resolves.not.toBeNull(); expect(Input.unityVersion).toStrictEqual('');
}); });
it('returns an object', async () => { it('takes input from the users workflow', () => {
await expect(typeof (await Input.getFromUser())).toStrictEqual('object'); const mockValue = '2020.4.99f9';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.unityVersion).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
}); });
it('calls version generator once', async () => { describe('targetPlatform', () => {
await Input.getFromUser(); it('returns the default value', () => {
expect(determineVersion).toHaveBeenCalledTimes(1); expect(Input.targetPlatform).toStrictEqual(Platform.default);
});
it('takes input from the users workflow', () => {
const mockValue = 'Android';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.targetPlatform).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('projectPath', () => {
it('returns the default value', () => {
expect(Input.projectPath).toStrictEqual('.');
});
it('takes input from the users workflow', () => {
const mockValue = 'customProjectPath';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.projectPath).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('buildName', () => {
it('returns the default value', () => {
expect(Input.buildName).toStrictEqual(Input.targetPlatform);
});
it('takes input from the users workflow', () => {
const mockValue = 'Build';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.buildName).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
it('takes special characters as input', () => {
const mockValue = '1ßúëld2';
jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.buildName).toStrictEqual(mockValue);
});
});
describe('buildsPath', () => {
it('returns the default value', () => {
expect(Input.buildsPath).toStrictEqual('build');
});
it('takes input from the users workflow', () => {
const mockValue = 'customBuildsPath';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.buildsPath).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('buildMethod', () => {
it('returns the default value', () => {
expect(Input.buildMethod).toStrictEqual('');
});
it('takes input from the users workflow', () => {
const mockValue = 'Namespace.ClassName.Method';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.buildMethod).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('versioningStrategy', () => {
it('returns the default value', () => {
expect(Input.versioningStrategy).toStrictEqual('Semantic');
});
it('takes input from the users workflow', () => {
const mockValue = 'Anything';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.versioningStrategy).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('specifiedVersion', () => {
it('returns the default value', () => {
expect(Input.specifiedVersion).toStrictEqual('');
});
it('takes input from the users workflow', () => {
const mockValue = '1.33.7';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.specifiedVersion).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('allowDirtyBuild', () => {
it('returns the default value', () => {
expect(Input.allowDirtyBuild).toStrictEqual('false');
});
it('returns true when string true is passed', () => {
const spy = jest.spyOn(core, 'getInput').mockReturnValue('true');
expect(Input.allowDirtyBuild).toStrictEqual('true');
expect(spy).toHaveBeenCalledTimes(1);
});
it('returns false when string false is passed', () => {
const spy = jest.spyOn(core, 'getInput').mockReturnValue('false');
expect(Input.allowDirtyBuild).toStrictEqual('false');
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('customParameters', () => {
it('returns the default value', () => {
expect(Input.customParameters).toStrictEqual('');
});
it('takes input from the users workflow', () => {
const mockValue = '-imAFlag';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.customParameters).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
}); });
}); });
}); });

View File

@ -1,11 +1,10 @@
import * as core from '@actions/core'; import Input from './input';
import Unity from './unity'; import Unity from './unity';
import Action from './action'; import Action from './action';
class Project { class Project {
static get relativePath() { static get relativePath() {
// Todo - properly use Input for this. const { projectPath } = Input;
const projectPath = core.getInput('projectPath') || '.';
return `${projectPath}`; return `${projectPath}`;
} }

View File

@ -1,19 +1,16 @@
import * as core from '@actions/core'; import * as core from '@actions/core';
import NotImplementedException from './error/not-implemented-exception'; import NotImplementedException from './error/not-implemented-exception';
import ValidationError from './error/validation-error'; import ValidationError from './error/validation-error';
import Input from './input';
import System from './system'; import System from './system';
export default class Versioning { export default class Versioning {
static get projectPath() { static get projectPath() {
// Todo - Unify duplication with Input.js, without async accessor return Input.projectPath;
return core.getInput('projectPath') || '.';
} }
static get isDirtyAllowed() { static get isDirtyAllowed() {
// Todo - Unify duplication with Input.js, without async accessor return Input.allowDirtyBuild === 'true';
const allowDirtyBuild = core.getInput('allowDirtyBuild') || 'false';
return allowDirtyBuild === 'true';
} }
static get strategies() { static get strategies() {