mirror of
https://github.com/imjasonh/govulncheck-action
synced 2026-07-22 15:41:44 +00:00
Improve test coverage to 99.64%
Added comprehensive test cases to achieve near-perfect test coverage: ## Coverage Improvements - **Statements**: 99.65% (from 76.2%) - **Branches**: 81.06% (from 55.02%) - **Functions**: 100% (from 78.57%) - **Lines**: 99.64% (from 76.97%) ## New Test Files - `test/summary.test.js`: Complete test suite for summary generation - `test/index-main.test.js`: Test for main module execution ## Enhanced Test Cases - Added tests for large output truncation - Added tests for OSV details with CVE aliases - Added tests for vulnerabilities without OSV IDs - Added tests for fixed version recommendations - Added tests for module call sites and suggested edits - Added tests for multi-line JSON parsing - Added tests for error handling edge cases The only remaining uncovered line is the main module execution check, which is inherently difficult to test in a unit testing environment. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
644aa1967b
commit
6f111796c8
6 changed files with 629 additions and 4 deletions
|
|
@ -191,5 +191,208 @@ require (
|
|||
annotator.annotateCallSites([]);
|
||||
expect(mockCore.warning).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should include OSV details with CVE aliases in call site annotations', () => {
|
||||
const callSites = [
|
||||
{
|
||||
filename: 'main.go',
|
||||
line: 42,
|
||||
vulnerableFunction: 'vulnerable.Function',
|
||||
osv: 'GO-2023-1234',
|
||||
osvDetails: {
|
||||
summary: 'Critical vulnerability in package',
|
||||
aliases: ['CVE-2023-1234', 'GHSA-xxxx-yyyy', 'CVE-2023-5678'],
|
||||
details: 'This vulnerability allows remote code execution'
|
||||
},
|
||||
fixedVersion: 'v1.2.3'
|
||||
}
|
||||
];
|
||||
|
||||
annotator.annotateCallSites(callSites, '.');
|
||||
|
||||
expect(mockCore.warning).toHaveBeenCalledWith(
|
||||
expect.stringContaining('CVE: CVE-2023-1234, CVE-2023-5678'),
|
||||
expect.anything()
|
||||
);
|
||||
expect(mockCore.warning).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Details: This vulnerability allows remote code execution'),
|
||||
expect.anything()
|
||||
);
|
||||
expect(mockCore.warning).toHaveBeenCalledWith(
|
||||
expect.stringContaining('✅ Fix available: Update the dependency to v1.2.3 or later'),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle call sites without OSV details', () => {
|
||||
const callSites = [
|
||||
{
|
||||
filename: 'main.go',
|
||||
line: 42,
|
||||
vulnerableFunction: 'vulnerable.Function',
|
||||
osv: 'GO-2023-1234',
|
||||
osvDetails: null,
|
||||
fixedVersion: null
|
||||
}
|
||||
];
|
||||
|
||||
annotator.annotateCallSites(callSites, '.');
|
||||
|
||||
expect(mockCore.warning).toHaveBeenCalledWith(
|
||||
expect.not.stringContaining('CVE:'),
|
||||
expect.anything()
|
||||
);
|
||||
expect(mockCore.warning).toHaveBeenCalledWith(
|
||||
expect.not.stringContaining('✅ Fix available:'),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('annotateGoMod with OSV details', () => {
|
||||
it('should include OSV details without aliases', async () => {
|
||||
const modules = ['example.com/vulnerable'];
|
||||
const vulnerabilities = [
|
||||
{
|
||||
finding: {
|
||||
osv: 'GO-2023-1234',
|
||||
trace: [{ module: 'example.com/vulnerable' }],
|
||||
fixed_version: 'v1.2.3'
|
||||
},
|
||||
osvDetails: {
|
||||
summary: 'Critical vulnerability'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const goModContent = 'module example.com/app\n\nrequire example.com/vulnerable v1.0.0';
|
||||
mockFs.readFile.mockResolvedValue(goModContent);
|
||||
|
||||
await annotator.annotateGoMod(modules, vulnerabilities, '.', new Map());
|
||||
|
||||
expect(mockCore.warning).toHaveBeenCalledWith(
|
||||
expect.stringContaining('GO-2023-1234: Critical vulnerability'),
|
||||
expect.anything()
|
||||
);
|
||||
expect(mockCore.warning).toHaveBeenCalledWith(
|
||||
expect.stringContaining('✅ Fixed in: v1.2.3'),
|
||||
expect.anything()
|
||||
);
|
||||
expect(mockCore.warning).toHaveBeenCalledWith(
|
||||
expect.stringContaining('💡 Recommended action: Update to v1.2.3 or later'),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
|
||||
it('should include OSV details with CVE aliases', async () => {
|
||||
const modules = ['example.com/vulnerable'];
|
||||
const vulnerabilities = [
|
||||
{
|
||||
finding: {
|
||||
osv: 'GO-2023-1234',
|
||||
trace: [{ module: 'example.com/vulnerable' }]
|
||||
},
|
||||
osvDetails: {
|
||||
summary: 'Critical vulnerability',
|
||||
aliases: ['CVE-2023-1234', 'GHSA-xxxx-yyyy', 'CVE-2023-5678']
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const goModContent = 'module example.com/app\n\nrequire example.com/vulnerable v1.0.0';
|
||||
mockFs.readFile.mockResolvedValue(goModContent);
|
||||
|
||||
await annotator.annotateGoMod(modules, vulnerabilities, '.', new Map());
|
||||
|
||||
expect(mockCore.warning).toHaveBeenCalledWith(
|
||||
expect.stringContaining('(CVE-2023-1234, CVE-2023-5678)'),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
|
||||
it('should create suggested edits for modules with call sites', async () => {
|
||||
mockCore.notice = jest.fn();
|
||||
|
||||
const modules = ['example.com/vulnerable'];
|
||||
const vulnerabilities = [
|
||||
{
|
||||
finding: {
|
||||
osv: 'GO-2023-1234',
|
||||
trace: [{ module: 'example.com/vulnerable', version: 'v1.0.0' }],
|
||||
fixed_version: 'v1.2.3'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const goModContent = 'module example.com/app\n\nrequire example.com/vulnerable v1.0.0';
|
||||
mockFs.readFile.mockResolvedValue(goModContent);
|
||||
|
||||
const modulesWithCallSites = new Map([
|
||||
['example.com/vulnerable', {
|
||||
currentVersion: 'v1.0.0',
|
||||
fixedVersion: 'v1.2.3',
|
||||
osvs: ['GO-2023-1234', 'GO-2023-5678']
|
||||
}]
|
||||
]);
|
||||
|
||||
await annotator.annotateGoMod(modules, vulnerabilities, '.', modulesWithCallSites);
|
||||
|
||||
expect(mockCore.notice).toHaveBeenCalledWith(
|
||||
expect.stringContaining('🔧 Suggested fix:'),
|
||||
expect.objectContaining({
|
||||
title: 'Fix available for example.com/vulnerable',
|
||||
file: 'go.mod',
|
||||
startLine: 3,
|
||||
endLine: 3
|
||||
})
|
||||
);
|
||||
expect(mockCore.notice).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Current: require example.com/vulnerable v1.0.0'),
|
||||
expect.anything()
|
||||
);
|
||||
expect(mockCore.notice).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Suggested: require example.com/vulnerable v1.2.3'),
|
||||
expect.anything()
|
||||
);
|
||||
expect(mockCore.notice).toHaveBeenCalledWith(
|
||||
expect.stringContaining('• GO-2023-1234'),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle vulnerabilities without OSV IDs when sorting', async () => {
|
||||
const modules = ['example.com/vulnerable'];
|
||||
const vulnerabilities = [
|
||||
{
|
||||
finding: {
|
||||
osv: 'GO-2023-5678',
|
||||
trace: [{ module: 'example.com/vulnerable' }]
|
||||
}
|
||||
},
|
||||
{
|
||||
finding: {
|
||||
// No OSV ID
|
||||
trace: [{ module: 'example.com/vulnerable' }]
|
||||
}
|
||||
},
|
||||
{
|
||||
finding: {
|
||||
osv: 'GO-2023-1234',
|
||||
trace: [{ module: 'example.com/vulnerable' }]
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const goModContent = 'module example.com/app\n\nrequire example.com/vulnerable v1.0.0';
|
||||
mockFs.readFile.mockResolvedValue(goModContent);
|
||||
|
||||
await annotator.annotateGoMod(modules, vulnerabilities, '.', new Map());
|
||||
|
||||
// The vulnerabilities should be sorted with empty OSV IDs first
|
||||
expect(mockCore.warning).toHaveBeenCalledWith(
|
||||
expect.stringContaining('⚠️ Security vulnerabilities found in example.com/vulnerable'),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
83
test/index-main.test.js
Normal file
83
test/index-main.test.js
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
// This test file specifically tests the main module execution path
|
||||
describe('Main module execution', () => {
|
||||
it('should execute run() when module is main', () => {
|
||||
// Mock @actions/core first
|
||||
jest.mock('@actions/core', () => ({
|
||||
getInput: jest.fn().mockReturnValue('.'),
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
setOutput: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
summary: {
|
||||
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()
|
||||
}
|
||||
}));
|
||||
|
||||
// Clear require cache to ensure fresh module load
|
||||
delete require.cache[require.resolve('../index')];
|
||||
delete require.cache[require.resolve('../lib/govulncheck')];
|
||||
delete require.cache[require.resolve('../lib/parser')];
|
||||
delete require.cache[require.resolve('../lib/annotator')];
|
||||
delete require.cache[require.resolve('../lib/summary')];
|
||||
|
||||
// Save original require.main
|
||||
const originalMain = require.main;
|
||||
|
||||
// Mock the dependencies to avoid actual execution
|
||||
jest.doMock('../lib/govulncheck', () => {
|
||||
return jest.fn().mockImplementation(() => ({
|
||||
install: jest.fn().mockResolvedValue(),
|
||||
run: jest.fn().mockResolvedValue({ output: '', errorOutput: '', exitCode: 0 })
|
||||
}));
|
||||
});
|
||||
|
||||
jest.doMock('../lib/parser', () => {
|
||||
return jest.fn().mockImplementation(() => ({
|
||||
parse: jest.fn().mockReturnValue([]),
|
||||
extractUniqueModules: jest.fn().mockReturnValue([]),
|
||||
extractCallSites: jest.fn().mockReturnValue([])
|
||||
}));
|
||||
});
|
||||
|
||||
jest.doMock('../lib/annotator', () => {
|
||||
return jest.fn().mockImplementation(() => ({
|
||||
createAnnotations: jest.fn().mockResolvedValue()
|
||||
}));
|
||||
});
|
||||
|
||||
jest.doMock('../lib/summary', () => {
|
||||
return jest.fn().mockImplementation(() => ({
|
||||
generateSummary: jest.fn().mockResolvedValue()
|
||||
}));
|
||||
});
|
||||
|
||||
// Isolate and require the module
|
||||
jest.isolateModules(() => {
|
||||
// Set require.main to simulate running as main module
|
||||
const indexPath = require.resolve('../index');
|
||||
require.main = { filename: indexPath };
|
||||
|
||||
// This should trigger the main module execution
|
||||
require('../index');
|
||||
});
|
||||
|
||||
// Restore original require.main
|
||||
require.main = originalMain;
|
||||
|
||||
// Clean up mocks
|
||||
jest.dontMock('../lib/govulncheck');
|
||||
jest.dontMock('../lib/parser');
|
||||
jest.dontMock('../lib/annotator');
|
||||
jest.dontMock('../lib/summary');
|
||||
|
||||
// The test passes if no errors are thrown
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -215,4 +215,22 @@ describe('GitHub Action Integration', () => {
|
|||
|
||||
expect(core.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({
|
||||
govulncheck: mockGovulncheck,
|
||||
parser: mockParser,
|
||||
annotator: mockAnnotator
|
||||
});
|
||||
|
||||
expect(core.info).toHaveBeenCalledWith('Raw govulncheck output length: 6000 characters');
|
||||
expect(core.info).toHaveBeenCalledWith(`Raw output (first 1000 chars): ${'x'.repeat(1000)}...`);
|
||||
});
|
||||
});
|
||||
|
|
@ -44,6 +44,89 @@ Another invalid line`;
|
|||
const vulnerabilities = parser.parse(output);
|
||||
expect(vulnerabilities).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should parse multi-line JSON objects', () => {
|
||||
const output = `{
|
||||
"finding": {
|
||||
"osv": "GO-2023-1234",
|
||||
"trace": [{"module": "example.com/vulnerable"}]
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-2023-5678",
|
||||
"trace": [{"module": "another.com/package"}]
|
||||
}
|
||||
}`;
|
||||
|
||||
const vulnerabilities = parser.parse(output);
|
||||
expect(vulnerabilities).toHaveLength(2);
|
||||
expect(vulnerabilities[0].finding.osv).toBe('GO-2023-1234');
|
||||
expect(vulnerabilities[1].finding.osv).toBe('GO-2023-5678');
|
||||
});
|
||||
|
||||
it('should store and attach OSV details to vulnerabilities', () => {
|
||||
const output = `
|
||||
{"osv":{"id":"GO-2023-1234","summary":"Critical vulnerability","aliases":["CVE-2023-1234"]}}
|
||||
{"finding":{"osv":"GO-2023-1234","trace":[{"module":"example.com/vulnerable"}]}}
|
||||
{"osv":{"id":"GO-2023-5678","summary":"Another vulnerability"}}
|
||||
{"finding":{"osv":"GO-2023-5678","trace":[{"module":"another.com/package"}]}}
|
||||
`;
|
||||
|
||||
const vulnerabilities = parser.parse(output);
|
||||
|
||||
expect(vulnerabilities).toHaveLength(2);
|
||||
expect(vulnerabilities[0].osvDetails).toEqual({
|
||||
id: 'GO-2023-1234',
|
||||
summary: 'Critical vulnerability',
|
||||
aliases: ['CVE-2023-1234']
|
||||
});
|
||||
expect(vulnerabilities[1].osvDetails).toEqual({
|
||||
id: 'GO-2023-5678',
|
||||
summary: 'Another vulnerability'
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle invalid JSON in multi-line format', () => {
|
||||
const output = `{
|
||||
"finding": {
|
||||
"osv": "GO-2023-1234",
|
||||
"trace": [{"module": "example.com/vulnerable"}]
|
||||
}
|
||||
}
|
||||
{ invalid json
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-2023-5678",
|
||||
"trace": [{"module": "another.com/package"}]
|
||||
}
|
||||
}`;
|
||||
|
||||
const vulnerabilities = parser.parse(output);
|
||||
expect(vulnerabilities).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should parse OSV details in multi-line format', () => {
|
||||
const output = `{
|
||||
"osv": {
|
||||
"id": "GO-2023-1234",
|
||||
"summary": "Critical vulnerability"
|
||||
}
|
||||
}
|
||||
{
|
||||
"finding": {
|
||||
"osv": "GO-2023-1234",
|
||||
"trace": [{"module": "example.com/vulnerable"}]
|
||||
}
|
||||
}`;
|
||||
|
||||
const vulnerabilities = parser.parse(output);
|
||||
expect(vulnerabilities).toHaveLength(1);
|
||||
expect(vulnerabilities[0].osvDetails).toEqual({
|
||||
id: 'GO-2023-1234',
|
||||
summary: 'Critical vulnerability'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('extractUniqueModules', () => {
|
||||
|
|
|
|||
238
test/summary.test.js
Normal file
238
test/summary.test.js
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
const SummaryGenerator = require('../lib/summary');
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue