Skip to content

Commit d64c501

Browse files
authored
test: add tests for utils (#4)
* test: add tests for utils * chore: add build script
1 parent ce39354 commit d64c501

File tree

9 files changed

+355
-655
lines changed

9 files changed

+355
-655
lines changed

main.js

Lines changed: 83 additions & 75 deletions
Large diffs are not rendered by default.

package-lock.json

Lines changed: 105 additions & 571 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"test": "test"
2626
},
2727
"scripts": {
28-
"build": "esbuild src/main.ts --bundle --outfile=main.js --format=esm --platform=node --target=node24",
28+
"build": "node scripts/build.mjs",
2929
"format": "prettier --write src test",
3030
"lint": "npm run lint:format && npm run lint:js && npm run lint:types",
3131
"lint:format": "prettier --check src test",
@@ -38,7 +38,7 @@
3838
"@actions/github": "^6.0.1",
3939
"@eslint/js": "^9.36.0",
4040
"@types/node": "^24.5.2",
41-
"esbuild": "^0.24.2",
41+
"esbuild": "^0.25.10",
4242
"eslint": "^9.36.0",
4343
"prettier": "^3.6.2",
4444
"typescript": "^5.9.2",

scripts/build.mjs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as esbuild from 'esbuild';
2+
3+
await esbuild.build({
4+
entryPoints: ['src/main.ts'],
5+
bundle: true,
6+
outfile: 'main.js',
7+
format: 'esm',
8+
target: 'node24',
9+
platform: 'node',
10+
banner: {
11+
js: `
12+
import { fileURLToPath } from 'node:url';
13+
import { createRequire as topLevelCreateRequire } from 'node:module';
14+
import { dirname as topLevelDirname } from 'path';
15+
const require = topLevelCreateRequire(import.meta.url);
16+
`.trim()
17+
}
18+
});

src/git.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ export function getFileFromRef(
1010
try {
1111
const content = execFileSync('git', ['show', `${ref}:${filePath}`], {
1212
encoding: 'utf8',
13-
cwd
13+
cwd,
14+
stdio: 'pipe'
1415
});
1516
return content;
1617
} catch (err) {
@@ -33,7 +34,3 @@ export function getBaseRef(): string {
3334

3435
return 'origin/main';
3536
}
36-
37-
export function getCurrentRef(): string {
38-
return github.context.sha ?? 'HEAD';
39-
}

src/main.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as process from 'process';
22
import * as core from '@actions/core';
33
import * as github from '@actions/github';
44
import {parseLockfile, detectLockfile} from './lockfile.js';
5-
import {getFileFromRef, getBaseRef, getCurrentRef} from './git.js';
5+
import {getFileFromRef, getBaseRef} from './git.js';
66
import {fetchPackageMetadata} from './npm.js';
77

88
function formatBytes(bytes: number): string {
@@ -19,7 +19,7 @@ async function run(): Promise<void> {
1919
try {
2020
const workspacePath = process.env.GITHUB_WORKSPACE || process.cwd();
2121
const baseRef = getBaseRef();
22-
const currentRef = getCurrentRef();
22+
const currentRef = github.context.sha;
2323
const lockfilePath = detectLockfile(workspacePath);
2424
const token = core.getInput('github-token', {required: true});
2525
const prNumber = parseInt(core.getInput('pr-number', {required: true}), 10);

test/git_test.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import {describe, it, expect, beforeEach, vi} from 'vitest';
2+
import * as git from '../src/git.js';
3+
import * as github from '@actions/github';
4+
import process from 'process';
5+
import {fileURLToPath} from 'node:url';
6+
import path from 'node:path';
7+
8+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
9+
const rootDir = path.join(currentDir, '..');
10+
11+
describe('getBaseRef', () => {
12+
it('should return input base ref if provided', () => {
13+
try {
14+
process.env['INPUT_BASE-REF'] = 'feature-branch';
15+
const baseRef = git.getBaseRef();
16+
expect(baseRef).toBe('feature-branch');
17+
} finally {
18+
delete process.env['INPUT_BASE-REF'];
19+
}
20+
});
21+
22+
it('should return pull request base ref if in PR context', () => {
23+
const originalPayload = github.context.payload;
24+
try {
25+
github.context.payload = {
26+
pull_request: {
27+
number: 303,
28+
base: {
29+
ref: 'develop'
30+
}
31+
}
32+
};
33+
const baseRef = git.getBaseRef();
34+
expect(baseRef).toBe('origin/develop');
35+
} finally {
36+
github.context.payload = originalPayload;
37+
}
38+
});
39+
40+
it('should return default base ref if no input or PR context', () => {
41+
const originalPayload = github.context.payload;
42+
try {
43+
github.context.payload = {};
44+
const baseRef = git.getBaseRef();
45+
expect(baseRef).toBe('origin/main');
46+
} finally {
47+
github.context.payload = originalPayload;
48+
}
49+
});
50+
});
51+
52+
describe('getFileFromRef', () => {
53+
beforeEach(() => {
54+
vi.mock(import('@actions/core'), async (importModule) => {
55+
const mod = await importModule();
56+
return {
57+
...mod,
58+
info: vi.fn(),
59+
error: vi.fn()
60+
};
61+
});
62+
});
63+
64+
it('should return file content from a given ref', () => {
65+
const content = git.getFileFromRef('HEAD', 'package.json', rootDir);
66+
expect(content).toBeDefined();
67+
expect(content).toContain('"name":');
68+
});
69+
70+
it('should return null if file does not exist in the given ref', () => {
71+
const content = git.getFileFromRef('HEAD', 'nonexistentfile.txt', rootDir);
72+
expect(content).toBeNull();
73+
});
74+
});

test/npm_test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import * as core from '@actions/core';
2+
import {
3+
describe,
4+
it,
5+
vi,
6+
beforeEach,
7+
afterEach,
8+
type MockInstance,
9+
expect
10+
} from 'vitest';
11+
import {fetchPackageMetadata} from '../src/npm.js';
12+
13+
describe('fetchPackageMetadata', () => {
14+
let fetchMock: MockInstance<typeof globalThis.fetch>;
15+
16+
beforeEach(() => {
17+
fetchMock = vi.spyOn(globalThis, 'fetch');
18+
vi.mock(import('@actions/core'), async (importModule) => {
19+
const mod = await importModule();
20+
return {
21+
...mod,
22+
info: vi.fn(),
23+
error: vi.fn()
24+
};
25+
});
26+
});
27+
28+
afterEach(() => {
29+
fetchMock.mockRestore();
30+
vi.clearAllMocks();
31+
});
32+
33+
it('should return null if request fails', async () => {
34+
const response = new Response(null, {status: 404});
35+
fetchMock.mockResolvedValue(response);
36+
const result = await fetchPackageMetadata('nonexistent-package', '1.0.0');
37+
expect(result).toBeNull();
38+
});
39+
40+
it('should return package metadata for valid package and version', async () => {
41+
const mockMetadata = {
42+
name: 'some-package',
43+
version: '1.0.0'
44+
};
45+
const response = new Response(JSON.stringify(mockMetadata), {status: 200});
46+
fetchMock.mockResolvedValue(response);
47+
const result = await fetchPackageMetadata('some-package', '1.0.0');
48+
expect(result).toEqual(mockMetadata);
49+
});
50+
51+
it('should return null if fetch fails', async () => {
52+
const infoSpy = vi.mocked(core.info);
53+
fetchMock.mockRejectedValue(new Error('Network error'));
54+
const result = await fetchPackageMetadata('some-package', '1.0.0');
55+
expect(result).toBeNull();
56+
expect(infoSpy).toHaveBeenCalledWith(
57+
'Failed to fetch metadata for [email protected]: Error: Network error'
58+
);
59+
});
60+
});

vitest.config.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { defineConfig } from 'vitest/config'
2+
3+
export default defineConfig({
4+
test: {
5+
include: [
6+
'test/**/*_test.ts'
7+
]
8+
}
9+
})

0 commit comments

Comments
 (0)