Skip to content

Commit e75b533

Browse files
committed
Add Unit Tests
- **New Features** - Enhanced testing configurations and setups for improved development practices. - Improved module import system for better reliability and maintenance. - Implemented dependency injection in class functionalities for enhanced testability and modularity. - **Bug Fixes** - Implemented checks to prevent operations on empty tags and ensure proper logging in specific methods. - **Tests** - Introduced comprehensive test suites for key classes to ensure robustness and functionality integrity. - **Refactor** - Modified constructors in several classes to adhere to dependency injection principles. - **Documentation** - Updated project settings and configurations to align with new development tools and practices.
1 parent f4c02bd commit e75b533

14 files changed

Lines changed: 2348 additions & 37 deletions

.vscode/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"jest.jestCommandLine": "yarn jest",
3+
"jest.rootPath": "src",
4+
"favorites.resources": []
5+
}

dist/index.js

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31693,13 +31693,13 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
3169331693
const core = __importStar(__nccwpck_require__(2186));
3169431694
const artifacts_1 = __nccwpck_require__(1870);
3169531695
const tags_1 = __nccwpck_require__(7816);
31696-
const create_git_1 = __nccwpck_require__(6704);
3169731696
const temporary_branch_1 = __nccwpck_require__(2786);
31697+
const create_git_1 = __nccwpck_require__(6704);
3169831698
const configuration_1 = __nccwpck_require__(5778);
3169931699
async function main() {
3170031700
const configuration = new configuration_1.Configuration(core.getInput.bind(core));
3170131701
const git = (0, create_git_1.createGit)();
31702-
const tags = new tags_1.Tags();
31702+
const tags = new tags_1.Tags(git);
3170331703
const artifacts = new artifacts_1.Artifacts(git, tags, configuration);
3170431704
const temporaryBranch = new temporary_branch_1.TemporaryBranch(git);
3170531705
Promise.resolve()
@@ -31760,7 +31760,9 @@ class Artifacts {
3176031760
core.startGroup('📦 Creating artifacts');
3176131761
try {
3176231762
await this.compile();
31763+
await this.tags.collect();
3176331764
await this.deploy();
31765+
await this.tags.move();
3176431766
}
3176531767
catch (error) {
3176631768
core.endGroup();
@@ -31777,10 +31779,8 @@ class Artifacts {
3177731779
}
3177831780
async deploy() {
3177931781
await this.add();
31780-
await this.tags.collect();
3178131782
await this.commit();
3178231783
await this.push();
31783-
await this.tags.move();
3178431784
}
3178531785
async add() {
3178631786
const result = await exec.exec(`git add -f ${this.configuration.targetDir}/*`);
@@ -31797,7 +31797,9 @@ class Artifacts {
3179731797
async push() {
3179831798
const pushingResult = await this.git.push();
3179931799
const messages = pushingResult.remoteMessages.all.join('\n');
31800-
messages && core.info(`Pushed artifacts with messages: ${messages}`);
31800+
if (messages) {
31801+
core.info(`Pushed artifacts with messages: ${messages}`);
31802+
}
3180131803
}
3180231804
}
3180331805
exports.Artifacts = Artifacts;
@@ -31835,17 +31837,22 @@ var __importStar = (this && this.__importStar) || function (mod) {
3183531837
};
3183631838
Object.defineProperty(exports, "__esModule", ({ value: true }));
3183731839
exports.Tags = void 0;
31838-
const create_git_1 = __nccwpck_require__(6704);
3183931840
const core = __importStar(__nccwpck_require__(2186));
3184031841
class Tags {
31842+
git;
3184131843
tags = [];
31842-
git = (0, create_git_1.createGit)();
31844+
constructor(git) {
31845+
this.git = git;
31846+
}
3184331847
async collect() {
3184431848
this.tags = (await this.git.tags(['--contains'])).all;
3184531849
core.info(`Collecting tags: ${this.toString()}`);
3184631850
}
3184731851
async move() {
3184831852
core.info(`Moving tags: ${this.toString()}`);
31853+
if (!this.tags.length) {
31854+
return;
31855+
}
3184931856
await this.remove();
3185031857
await this.create();
3185131858
}
@@ -31868,7 +31875,9 @@ class Tags {
3186831875
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
3186931876
pushInfo(result, message) {
3187031877
const messages = result.remoteMessages.all.join('\n');
31871-
messages && core.info(`${message}: ${messages}`);
31878+
if (messages) {
31879+
core.info(`${message}: ${messages}`);
31880+
}
3187231881
}
3187331882
}
3187431883
exports.Tags = Tags;
@@ -31911,7 +31920,6 @@ class TemporaryBranch {
3191131920
git;
3191231921
constructor(git) {
3191331922
this.git = git;
31914-
this.git = git;
3191531923
}
3191631924
async create() {
3191731925
const _isDetached = await this.isDetached();

jest.config.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/** @type {import('ts-jest').JestConfigWithTsJest} */
2+
module.exports = {
3+
clearMocks: true,
4+
preset: 'ts-jest',
5+
moduleDirectories: ['node_modules'],
6+
moduleNameMapper: {
7+
'^@/(.*)$': '<rootDir>/src/$1',
8+
'^@model/(.*)$': '<rootDir>/src/model/$1',
9+
},
10+
setupFilesAfterEnv: ['<rootDir>/tests/setup-tests.ts'],
11+
maxWorkers: 8,
12+
};

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,18 @@
1010
"simple-git": "~3.22.0"
1111
},
1212
"devDependencies": {
13+
"@jest/globals": "^29.7.0",
14+
"@total-typescript/shoehorn": "^0.1.0",
1315
"@types/node": "~20.11.25",
1416
"@typescript-eslint/eslint-plugin": "~7.1.1",
1517
"@typescript-eslint/parser": "~7.1.1",
1618
"@vercel/ncc": "~0.38.1",
1719
"eslint": "~8.57.0",
1820
"husky": "~9.0.11",
21+
"jest": "^29.7.0",
1922
"prettier": "~3.2.5",
23+
"ts-jest": "^29.1.2",
24+
"ts-node": "^10.9.2",
2025
"typescript": "~5.3.0"
2126
},
2227
"scripts": {

src/main.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import * as core from '@actions/core';
2-
import { Artifacts } from './model/artifacts';
3-
import { Tags } from './model/tags';
2+
import { Artifacts } from '@model/artifacts';
3+
import { Tags } from '@model/tags';
4+
import { TemporaryBranch } from '@model/temporary-branch';
45
import { createGit } from './create-git';
5-
import { TemporaryBranch } from './model/temporary-branch';
66
import { Configuration } from './configuration';
77

88
async function main(): Promise<void> {
99
const configuration = new Configuration(core.getInput.bind(core));
1010

1111
const git = createGit();
12-
const tags = new Tags();
12+
const tags = new Tags(git);
1313
const artifacts = new Artifacts(git, tags, configuration);
1414
const temporaryBranch = new TemporaryBranch(git);
1515

src/model/artifacts.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { SimpleGit } from 'simple-git';
22
import * as core from '@actions/core';
33
import * as exec from '@actions/exec';
44
import type { Tags } from './tags';
5-
import type { Configuration } from '../configuration';
5+
import type { Configuration } from '@/configuration';
66

77
export class Artifacts {
88
constructor(
@@ -16,7 +16,9 @@ export class Artifacts {
1616

1717
try {
1818
await this.compile();
19+
await this.tags.collect();
1920
await this.deploy();
21+
await this.tags.move();
2022
} catch (error: Error | unknown) {
2123
core.endGroup();
2224
const message = String(error instanceof Error ? error.message : error);
@@ -35,11 +37,8 @@ export class Artifacts {
3537

3638
private async deploy(): Promise<void> {
3739
await this.add();
38-
39-
await this.tags.collect();
4040
await this.commit();
4141
await this.push();
42-
await this.tags.move();
4342
}
4443

4544
private async add(): Promise<void> {
@@ -59,6 +58,9 @@ export class Artifacts {
5958
private async push(): Promise<void> {
6059
const pushingResult = await this.git.push();
6160
const messages = pushingResult.remoteMessages.all.join('\n');
62-
messages && core.info(`Pushed artifacts with messages: ${messages}`);
61+
62+
if (messages) {
63+
core.info(`Pushed artifacts with messages: ${messages}`);
64+
}
6365
}
6466
}

src/model/tags.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import type { PushResult, SimpleGit } from 'simple-git';
2-
import { createGit } from '../create-git';
32
import * as core from '@actions/core';
43

54
export class Tags {
65
private tags: Array<string> = [];
7-
private readonly git: SimpleGit = createGit();
6+
7+
constructor(private readonly git: Readonly<SimpleGit>) {}
88

99
public async collect(): Promise<void> {
1010
this.tags = (await this.git.tags(['--contains'])).all;
@@ -13,6 +13,11 @@ export class Tags {
1313

1414
public async move(): Promise<void> {
1515
core.info(`Moving tags: ${this.toString()}`);
16+
17+
if (!this.tags.length) {
18+
return;
19+
}
20+
1621
await this.remove();
1722
await this.create();
1823
}
@@ -40,6 +45,9 @@ export class Tags {
4045
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
4146
private pushInfo(result: Readonly<PushResult>, message: string): void {
4247
const messages = result.remoteMessages.all.join('\n');
43-
messages && core.info(`${message}: ${messages}`);
48+
49+
if (messages) {
50+
core.info(`${message}: ${messages}`);
51+
}
4452
}
4553
}

src/model/temporary-branch.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ import type { PushResult, SimpleGit } from 'simple-git';
22
import * as core from '@actions/core';
33

44
export class TemporaryBranch {
5-
constructor(private readonly git: Readonly<SimpleGit>) {
6-
this.git = git;
7-
}
5+
constructor(private readonly git: Readonly<SimpleGit>) {}
86

97
async create(): Promise<void> {
108
const _isDetached = await this.isDetached();

tests/setup-tests.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { jest } from '@jest/globals';
2+
3+
jest.mock('@actions/core', () => ({
4+
startGroup: jest.fn(),
5+
endGroup: jest.fn(),
6+
info: jest.fn(),
7+
setFailed: jest.fn(),
8+
}));

tests/unit/model/artifacts.test.ts

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import type { SimpleGit } from 'simple-git';
2+
import type { Tags } from '@model/tags';
3+
import type { getInput } from '@actions/core';
4+
5+
import { it, jest, describe, expect } from '@jest/globals';
6+
import { exec } from '@actions/exec';
7+
import { fromPartial } from '@total-typescript/shoehorn';
8+
import { Configuration } from '@/configuration';
9+
import { Artifacts } from '@model/artifacts';
10+
11+
jest.mock('@actions/exec', () => ({
12+
__esModule: true,
13+
exec: jest.fn(),
14+
}));
15+
16+
jest.mock('@model/tags', () => ({
17+
__esModule: true,
18+
Tags: {
19+
collect: jest.fn(),
20+
move: jest.fn(),
21+
},
22+
}));
23+
24+
describe('Artifacts', () => {
25+
it('Compile the assets and Deploy when finished', async () => {
26+
const git = fromPartial<SimpleGit>({
27+
commit: jest.fn(() =>
28+
Promise.resolve({ summary: { changes: 0, insertions: 0, deletions: 0 } })
29+
),
30+
push: jest.fn(() =>
31+
Promise.resolve({
32+
remoteMessages: {
33+
all: [''],
34+
},
35+
})
36+
),
37+
});
38+
const tags = fromPartial<Tags>({ collect: jest.fn(), move: jest.fn() });
39+
const artifacts = new Artifacts(git, tags, configuration());
40+
41+
jest.mocked(exec).mockImplementation(async () => Promise.resolve(0));
42+
43+
await artifacts.update();
44+
45+
expect(jest.mocked(exec)).toHaveBeenNthCalledWith(1, 'yarn build');
46+
expect(jest.mocked(exec)).toHaveBeenNthCalledWith(2, 'git add -f ./build/*');
47+
});
48+
49+
it('Throw an error when failing to compile', async () => {
50+
const tags = fromPartial<Tags>({});
51+
const git = fromPartial<SimpleGit>({});
52+
const artifacts = new Artifacts(git, tags, configuration());
53+
54+
jest.mocked(exec).mockImplementation(async () => Promise.resolve(1));
55+
56+
await expect(artifacts.update()).rejects.toThrow(
57+
'Failed creating artifacts: Failing to compile artifacts. Process exited with non-zero code.'
58+
);
59+
});
60+
61+
it('Throw an error when artifacts commit fails', async () => {
62+
const git = fromPartial<SimpleGit>({
63+
commit: jest.fn(() => Promise.reject(new Error('Failed to commit'))),
64+
});
65+
const tags = fromPartial<Tags>({ collect: jest.fn() });
66+
const artifacts = new Artifacts(git, tags, configuration());
67+
68+
jest.mocked(exec).mockImplementation(async () => Promise.resolve(0));
69+
70+
await expect(artifacts.update()).rejects.toThrow('Failed creating artifacts: Failed to commit');
71+
});
72+
73+
it('Throw an error when artifacts push fails', async () => {
74+
const git = fromPartial<SimpleGit>({
75+
commit: jest.fn(() =>
76+
Promise.resolve({ summary: { changes: 0, insertions: 0, deletions: 0 } })
77+
),
78+
push: jest.fn(() => Promise.reject(new Error('Failed to push'))),
79+
});
80+
const tags = fromPartial<Tags>({ collect: jest.fn() });
81+
const artifacts = new Artifacts(git, tags, configuration());
82+
83+
jest.mocked(exec).mockImplementation(async () => Promise.resolve(0));
84+
85+
await expect(artifacts.update()).rejects.toThrow('Failed creating artifacts: Failed to push');
86+
});
87+
88+
it('Throw an error when failing to git-add', async () => {
89+
const git = fromPartial<SimpleGit>({});
90+
const tags = fromPartial<Tags>({ collect: jest.fn() });
91+
const artifacts = new Artifacts(git, tags, configuration());
92+
93+
jest.mocked(exec).mockImplementation(async (command) => (command === 'yarn build' ? 0 : 1));
94+
95+
await expect(artifacts.update()).rejects.toThrow(
96+
'Failed creating artifacts: Failing to git-add the artifacts build. Process exited with non-zero code.'
97+
);
98+
});
99+
100+
it('Throw an error when collecting tags fails', () => {
101+
const git = fromPartial<SimpleGit>({});
102+
const tags = fromPartial<Tags>({
103+
collect: jest.fn(() => Promise.reject(new Error('Failed to collect tags'))),
104+
});
105+
const artifacts = new Artifacts(git, tags, configuration());
106+
107+
jest.mocked(exec).mockImplementation(async () => Promise.resolve(0));
108+
109+
expect(artifacts.update()).rejects.toThrow('Failed creating artifacts: Failed to collect tags');
110+
});
111+
112+
it('Collect tags before moving them', async () => {
113+
const git = fromPartial<SimpleGit>({
114+
commit: jest.fn(() =>
115+
Promise.resolve({ summary: { changes: 0, insertions: 0, deletions: 0 } })
116+
),
117+
push: jest.fn(() =>
118+
Promise.resolve({
119+
remoteMessages: {
120+
all: [''],
121+
},
122+
})
123+
),
124+
});
125+
126+
const collect = jest.fn();
127+
const move = jest.fn();
128+
const tags = fromPartial<Tags>({ collect, move });
129+
130+
const artifacts = new Artifacts(git, tags, configuration());
131+
132+
jest.mocked(exec).mockImplementation(async () => Promise.resolve(0));
133+
134+
await artifacts.update();
135+
136+
expect(collect.mock.invocationCallOrder[0]).toBeLessThan(move.mock.invocationCallOrder[0] ?? 0);
137+
});
138+
});
139+
140+
function configuration(): Configuration {
141+
return new Configuration(stubGetInput());
142+
}
143+
144+
function stubGetInput(): typeof getInput {
145+
return jest.fn((name: string): string => {
146+
return name === 'command' ? 'yarn build' : './build';
147+
});
148+
}

0 commit comments

Comments
 (0)