1
0
Fork 0
mirror of https://github.com/imjasonh/govulncheck-action synced 2026-07-09 09:07:50 +00:00
govulncheck-action/test/index.test.js
Jason Hall c4a3cd53b0 build: migrate to ESM and esbuild
Updates the project to use ESM modules and replaces ncc with esbuild. This fixes build failures caused by newer @actions/core versions which use strict ESM exports that ncc cannot resolve.

Also updates tests to use ESM and dependency injection.

Signed-off-by: Jason Hall <imjasonh@gmail.com>
2026-02-21 02:54:49 -05:00

283 lines
7.8 KiB
JavaScript

import { run } from '../index.js';
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
describe('GitHub Action Integration', () => {
let mockGovulncheck;
let mockParser;
let mockAnnotator;
let mockCore;
let originalChdir;
beforeEach(() => {
// Save original process.chdir
originalChdir = process.chdir;
process.chdir = jest.fn();
// Reset all mocks
jest.clearAllMocks();
// Create mock core
const mockSummary = {
addHeading: jest.fn().mockReturnThis(),
addEOL: jest.fn().mockReturnThis(),
addRaw: jest.fn().mockReturnThis(),
addList: jest.fn().mockReturnThis(),
addTable: jest.fn().mockReturnThis(),
addCodeBlock: jest.fn().mockReturnThis(),
addDetails: jest.fn().mockReturnThis(),
addLink: jest.fn().mockReturnThis(),
addSeparator: jest.fn().mockReturnThis(),
write: jest.fn().mockResolvedValue(),
};
mockCore = {
getInput: jest.fn().mockReturnValue('.'),
info: jest.fn(),
warning: jest.fn(),
setOutput: jest.fn(),
setFailed: jest.fn(),
summary: mockSummary,
};
// Create mock dependencies
mockGovulncheck = {
install: jest.fn().mockResolvedValue(),
run: jest.fn().mockResolvedValue({
output: '',
errorOutput: '',
exitCode: 0,
}),
};
mockParser = {
parse: jest.fn().mockReturnValue([]),
extractUniqueModules: jest.fn().mockReturnValue([]),
extractCallSites: jest.fn().mockReturnValue([]),
};
mockAnnotator = {
createAnnotations: jest.fn().mockResolvedValue(),
};
});
afterEach(() => {
// Restore original process.chdir
process.chdir = originalChdir;
});
it('should complete successfully with no vulnerabilities', async () => {
const result = await run({
core: mockCore,
govulncheck: mockGovulncheck,
parser: mockParser,
annotator: mockAnnotator,
});
expect(mockCore.info).toHaveBeenCalledWith('Running govulncheck...');
expect(mockCore.info).toHaveBeenCalledWith('Raw govulncheck output length: 0 characters');
expect(mockCore.info).toHaveBeenCalledWith('Raw output: ');
expect(mockGovulncheck.install).toHaveBeenCalled();
expect(mockGovulncheck.run).toHaveBeenCalled();
expect(mockCore.setOutput).toHaveBeenCalledWith('vulnerabilities-found', 'false');
expect(mockCore.setOutput).toHaveBeenCalledWith('vulnerability-count', '0');
expect(result).toEqual({
vulnerabilities: [],
hasVulnerabilities: false,
});
});
it('should handle vulnerabilities found', async () => {
const mockVulnerabilities = [
{
finding: {
osv: 'GO-2023-1234',
trace: [{ module: 'example.com/vulnerable' }],
},
},
{
finding: {
osv: 'GO-2023-5678',
trace: [{ module: 'another.com/package' }],
},
},
];
mockParser.parse.mockReturnValue(mockVulnerabilities);
const result = await run({
core: mockCore,
govulncheck: mockGovulncheck,
parser: mockParser,
annotator: mockAnnotator,
});
expect(mockCore.warning).toHaveBeenCalledWith('Found 2 vulnerabilities');
expect(mockCore.setOutput).toHaveBeenCalledWith('vulnerabilities-found', 'true');
expect(mockCore.setOutput).toHaveBeenCalledWith('vulnerability-count', '2');
expect(mockAnnotator.createAnnotations).toHaveBeenCalledWith(
mockVulnerabilities,
mockParser,
'.'
);
expect(result).toEqual({
vulnerabilities: mockVulnerabilities,
hasVulnerabilities: true,
});
});
it('should change to working directory if specified', async () => {
mockCore.getInput.mockReturnValue('./subfolder');
await run({
core: mockCore,
govulncheck: mockGovulncheck,
parser: mockParser,
annotator: mockAnnotator,
});
expect(process.chdir).toHaveBeenCalledWith('./subfolder');
});
it('should not change directory for default value', async () => {
mockCore.getInput.mockReturnValue('.');
await run({
core: mockCore,
govulncheck: mockGovulncheck,
parser: mockParser,
annotator: mockAnnotator,
});
expect(process.chdir).not.toHaveBeenCalled();
});
it('should handle stderr warnings', async () => {
mockGovulncheck.run.mockResolvedValue({
output: '',
errorOutput: 'warning: using database from 2023-01-01',
exitCode: 0,
});
await run({
core: mockCore,
govulncheck: mockGovulncheck,
parser: mockParser,
annotator: mockAnnotator,
});
expect(mockCore.warning).toHaveBeenCalledWith(
'govulncheck stderr: warning: using database from 2023-01-01'
);
});
it('should fail when go.sum is missing', async () => {
mockGovulncheck.run.mockResolvedValue({
output: '{"config": {}}',
errorOutput: 'missing go.sum entry for module providing package golang.org/x/net/html',
exitCode: 1,
});
await expect(
run({
core: mockCore,
govulncheck: mockGovulncheck,
parser: mockParser,
annotator: mockAnnotator,
})
).rejects.toThrow('govulncheck failed due to missing dependencies');
});
it('should fail when imports cannot be resolved', async () => {
mockGovulncheck.run.mockResolvedValue({
output: '{"config": {}}',
errorOutput: 'could not import golang.org/x/net/html (invalid package name: "")',
exitCode: 1,
});
await expect(
run({
core: mockCore,
govulncheck: mockGovulncheck,
parser: mockParser,
annotator: mockAnnotator,
})
).rejects.toThrow('govulncheck failed due to missing dependencies');
});
it('should handle errors and set action as failed', async () => {
const error = new Error('Failed to install govulncheck');
mockGovulncheck.install.mockRejectedValue(error);
await expect(
run({
core: mockCore,
govulncheck: mockGovulncheck,
parser: mockParser,
annotator: mockAnnotator,
})
).rejects.toThrow('Failed to install govulncheck');
expect(mockCore.setFailed).toHaveBeenCalledWith('Failed to install govulncheck');
});
it('should handle parsing errors', async () => {
const error = new Error('Invalid JSON output');
mockParser.parse.mockImplementation(() => {
throw error;
});
await expect(
run({
core: mockCore,
govulncheck: mockGovulncheck,
parser: mockParser,
annotator: mockAnnotator,
})
).rejects.toThrow('Invalid JSON output');
expect(mockCore.setFailed).toHaveBeenCalledWith('Invalid JSON output');
});
it('should handle annotation errors gracefully', async () => {
const mockVulnerabilities = [{ finding: { osv: 'GO-2023-1234' } }];
mockParser.parse.mockReturnValue(mockVulnerabilities);
mockAnnotator.createAnnotations.mockRejectedValue(new Error('Annotation failed'));
await expect(
run({
core: mockCore,
govulncheck: mockGovulncheck,
parser: mockParser,
annotator: mockAnnotator,
})
).rejects.toThrow('Annotation failed');
expect(mockCore.setFailed).toHaveBeenCalledWith('Annotation failed');
});
it('should truncate output logging when output is large', async () => {
const largeOutput = 'x'.repeat(6000);
mockGovulncheck.run.mockResolvedValue({
output: largeOutput,
errorOutput: '',
exitCode: 0,
});
await run({
core: mockCore,
govulncheck: mockGovulncheck,
parser: mockParser,
annotator: mockAnnotator,
});
expect(mockCore.info).toHaveBeenCalledWith('Raw govulncheck output length: 6000 characters');
expect(mockCore.info).toHaveBeenCalledWith(
`Raw output (first 1000 chars): ${'x'.repeat(1000)}...`
);
});
});