mirror of
https://github.com/imjasonh/govulncheck-action
synced 2026-07-08 08:45:48 +00:00
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>
247 lines
7.6 KiB
JavaScript
247 lines
7.6 KiB
JavaScript
import SummaryGenerator from '../lib/summary.js';
|
|
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
|
|
|
|
describe('SummaryGenerator', () => {
|
|
let summaryGenerator;
|
|
let mockCore;
|
|
let mockParser;
|
|
let mockSummary;
|
|
|
|
beforeEach(() => {
|
|
mockSummary = {
|
|
addHeading: jest.fn().mockReturnThis(),
|
|
addEOL: jest.fn().mockReturnThis(),
|
|
addRaw: jest.fn().mockReturnThis(),
|
|
addList: jest.fn().mockReturnThis(),
|
|
addTable: jest.fn().mockReturnThis(),
|
|
addLink: jest.fn().mockReturnThis(),
|
|
addSeparator: jest.fn().mockReturnThis(),
|
|
write: jest.fn().mockResolvedValue(),
|
|
};
|
|
|
|
mockCore = {
|
|
summary: mockSummary,
|
|
};
|
|
|
|
mockParser = {
|
|
extractUniqueModules: jest.fn(),
|
|
extractCallSites: jest.fn(),
|
|
};
|
|
|
|
summaryGenerator = new SummaryGenerator(mockCore);
|
|
});
|
|
|
|
describe('generateSummary', () => {
|
|
it('should generate summary with no vulnerabilities', async () => {
|
|
mockParser.extractUniqueModules.mockReturnValue([]);
|
|
mockParser.extractCallSites.mockReturnValue([]);
|
|
|
|
await summaryGenerator.generateSummary([], mockParser, '.');
|
|
|
|
expect(mockSummary.addHeading).toHaveBeenCalledWith('🔍 Govulncheck Security Report', 1);
|
|
expect(mockSummary.addRaw).toHaveBeenCalledWith('✅ No vulnerabilities found!');
|
|
expect(mockSummary.write).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should generate summary with vulnerabilities sorted by OSV ID', async () => {
|
|
const vulnerabilities = [
|
|
{
|
|
finding: {
|
|
osv: 'GO-2023-5678',
|
|
trace: [{ module: 'example.com/vulnerable', version: 'v1.0.0' }],
|
|
fixed_version: 'v1.2.3',
|
|
},
|
|
osvDetails: {
|
|
summary: 'Second vulnerability',
|
|
},
|
|
},
|
|
{
|
|
finding: {
|
|
osv: 'GO-2023-1234',
|
|
trace: [{ module: 'example.com/vulnerable', version: 'v1.0.0' }],
|
|
fixed_version: 'v1.2.3',
|
|
},
|
|
osvDetails: {
|
|
summary: 'First vulnerability',
|
|
},
|
|
},
|
|
];
|
|
|
|
mockParser.extractUniqueModules.mockReturnValue(['example.com/vulnerable']);
|
|
mockParser.extractCallSites.mockReturnValue([]);
|
|
|
|
await summaryGenerator.generateSummary(vulnerabilities, mockParser, '.');
|
|
|
|
// Check that table was called
|
|
const tableCall = mockSummary.addTable.mock.calls[0][0];
|
|
expect(tableCall[1][0]).toBe('GO-2023-1234'); // First row should be sorted first
|
|
expect(tableCall[2][0]).toBe('GO-2023-5678'); // Second row should be sorted second
|
|
});
|
|
|
|
it('should include CVE aliases in vulnerability table', async () => {
|
|
const vulnerabilities = [
|
|
{
|
|
finding: {
|
|
osv: 'GO-2023-1234',
|
|
trace: [{ module: 'example.com/vulnerable', version: 'v1.0.0' }],
|
|
fixed_version: 'v1.2.3',
|
|
},
|
|
osvDetails: {
|
|
summary: 'Critical vulnerability',
|
|
aliases: ['CVE-2023-1234', 'GHSA-xxxx-yyyy', 'CVE-2023-5678'],
|
|
},
|
|
},
|
|
];
|
|
|
|
mockParser.extractUniqueModules.mockReturnValue(['example.com/vulnerable']);
|
|
mockParser.extractCallSites.mockReturnValue([]);
|
|
|
|
await summaryGenerator.generateSummary(vulnerabilities, mockParser, '.');
|
|
|
|
const tableCall = mockSummary.addTable.mock.calls[0][0];
|
|
expect(tableCall[1][3]).toBe('CVE: CVE-2023-1234, CVE-2023-5678');
|
|
});
|
|
|
|
it('should generate vulnerable code locations section', async () => {
|
|
const vulnerabilities = [
|
|
{
|
|
finding: {
|
|
osv: 'GO-2023-1234',
|
|
trace: [{ module: 'example.com/vulnerable', version: 'v1.0.0' }],
|
|
},
|
|
},
|
|
];
|
|
|
|
const callSites = [
|
|
{
|
|
filename: 'main.go',
|
|
line: 42,
|
|
vulnerableFunction: 'vulnerable.Function',
|
|
osv: 'GO-2023-1234',
|
|
},
|
|
{
|
|
filename: 'utils.go',
|
|
line: 10,
|
|
vulnerableFunction: 'helper.Process',
|
|
osv: 'GO-2023-5678',
|
|
},
|
|
];
|
|
|
|
mockParser.extractUniqueModules.mockReturnValue(['example.com/vulnerable']);
|
|
mockParser.extractCallSites.mockReturnValue(callSites);
|
|
|
|
await summaryGenerator.generateSummary(vulnerabilities, mockParser, '.');
|
|
|
|
expect(mockSummary.addHeading).toHaveBeenCalledWith('🚨 Vulnerable Code Locations', 2);
|
|
expect(mockSummary.addHeading).toHaveBeenCalledWith('📄 main.go', 3);
|
|
expect(mockSummary.addRaw).toHaveBeenCalledWith('• Line 42: calls vulnerable.Function');
|
|
expect(mockSummary.addLink).toHaveBeenCalledWith(
|
|
'GO-2023-1234',
|
|
'https://pkg.go.dev/vuln/GO-2023-1234'
|
|
);
|
|
});
|
|
|
|
it('should handle call sites in subdirectory', async () => {
|
|
const vulnerabilities = [
|
|
{
|
|
finding: {
|
|
osv: 'GO-2023-1234',
|
|
trace: [{ module: 'example.com/vulnerable', version: 'v1.0.0' }],
|
|
},
|
|
},
|
|
];
|
|
|
|
const callSites = [
|
|
{
|
|
filename: 'main.go',
|
|
line: 42,
|
|
vulnerableFunction: 'vulnerable.Function',
|
|
osv: 'GO-2023-1234',
|
|
},
|
|
];
|
|
|
|
mockParser.extractUniqueModules.mockReturnValue(['example.com/vulnerable']);
|
|
mockParser.extractCallSites.mockReturnValue(callSites);
|
|
|
|
await summaryGenerator.generateSummary(vulnerabilities, mockParser, 'subdir');
|
|
|
|
expect(mockSummary.addHeading).toHaveBeenCalledWith('📄 subdir/main.go', 3);
|
|
});
|
|
|
|
it('should generate recommendations with latest fixed versions', async () => {
|
|
const vulnerabilities = [
|
|
{
|
|
finding: {
|
|
osv: 'GO-2023-1234',
|
|
trace: [{ module: 'example.com/vulnerable', version: 'v1.0.0' }],
|
|
fixed_version: 'v1.2.0',
|
|
},
|
|
},
|
|
{
|
|
finding: {
|
|
osv: 'GO-2023-5678',
|
|
trace: [{ module: 'example.com/vulnerable', version: 'v1.0.0' }],
|
|
fixed_version: 'v1.2.3',
|
|
},
|
|
},
|
|
{
|
|
finding: {
|
|
osv: 'GO-2023-9999',
|
|
trace: [{ module: 'another.com/package', version: 'v2.0.0' }],
|
|
fixed_version: 'v2.1.0',
|
|
},
|
|
},
|
|
];
|
|
|
|
mockParser.extractUniqueModules.mockReturnValue([
|
|
'example.com/vulnerable',
|
|
'another.com/package',
|
|
]);
|
|
mockParser.extractCallSites.mockReturnValue([]);
|
|
|
|
await summaryGenerator.generateSummary(vulnerabilities, mockParser, '.');
|
|
|
|
expect(mockSummary.addList).toHaveBeenCalledWith([
|
|
'Update another.com/package to version v2.1.0 or later',
|
|
'Update example.com/vulnerable to version v1.2.3 or later',
|
|
]);
|
|
});
|
|
|
|
it('should handle vulnerabilities without fixed versions', async () => {
|
|
const vulnerabilities = [
|
|
{
|
|
finding: {
|
|
osv: 'GO-2023-1234',
|
|
trace: [{ module: 'example.com/vulnerable', version: 'v1.0.0' }],
|
|
},
|
|
},
|
|
];
|
|
|
|
mockParser.extractUniqueModules.mockReturnValue(['example.com/vulnerable']);
|
|
mockParser.extractCallSites.mockReturnValue([]);
|
|
|
|
await summaryGenerator.generateSummary(vulnerabilities, mockParser, '.');
|
|
|
|
expect(mockSummary.addRaw).toHaveBeenCalledWith(
|
|
'No specific version recommendations available.'
|
|
);
|
|
});
|
|
|
|
it('should handle vulnerabilities without trace data', async () => {
|
|
const vulnerabilities = [
|
|
{
|
|
finding: {
|
|
osv: 'GO-2023-1234',
|
|
},
|
|
},
|
|
];
|
|
|
|
mockParser.extractUniqueModules.mockReturnValue([]);
|
|
mockParser.extractCallSites.mockReturnValue([]);
|
|
|
|
await summaryGenerator.generateSummary(vulnerabilities, mockParser, '.');
|
|
|
|
expect(mockSummary.write).toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|