0d3cb498a3
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Waiting to run
Test and Publish Multi-arch Docker Image / test (push) Waiting to run
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-arm64 platform:linux/arm64 runner:ubuntu-24.04-arm]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Blocked by required conditions
Validate Renovate Config / Validate Renovate Configuration (push) Waiting to run
CI / Shell Format Check (push) Has been cancelled
CI / Check Ruby (3.4) (push) Has been cancelled
CI / CI Config (push) Has been cancelled
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Has been cancelled
CI / Build on Node ${{ matrix.node }} (push) Has been cancelled
CI / Style Check (push) Has been cancelled
CI / Generate Assets (push) Has been cancelled
CI / Check Python (3.14) (push) Has been cancelled
CI / Check Python (3.9) (push) Has been cancelled
CI / Build Docs (push) Has been cancelled
CI / Code Scan Action (push) Has been cancelled
CI / Site tests (push) Has been cancelled
CI / webui tests (push) Has been cancelled
CI / Run Integration Tests (push) Has been cancelled
CI / Run Smoke Tests (push) Has been cancelled
CI / Go Tests (push) Has been cancelled
CI / Share Test (push) Has been cancelled
CI / Redteam (Production API) (push) Has been cancelled
CI / Redteam (Staging API) (push) Has been cancelled
CI / GitHub Actions Lint (push) Has been cancelled
CI / Check Ruby (3.0) (push) Has been cancelled
release-please / release-please (push) Has been cancelled
release-please / build (push) Has been cancelled
release-please / publish-npm (push) Has been cancelled
release-please / publish-npm-backfill (push) Has been cancelled
release-please / docker (push) Has been cancelled
release-please / publish-code-scan-action (push) Has been cancelled
release-please / attest-code-scan-action (push) Has been cancelled
170 lines
4.9 KiB
TypeScript
170 lines
4.9 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
ALL_CLEAR_MESSAGE,
|
|
hasPrPostableFindings,
|
|
prepareComments,
|
|
} from '../../../src/codeScan/util/github';
|
|
import { CodeScanSeverity, type Comment } from '../../../src/types/codeScan';
|
|
|
|
describe('prepareComments', () => {
|
|
it('should return empty arrays and empty review body when no comments', () => {
|
|
const result = prepareComments([], undefined, undefined);
|
|
|
|
expect(result.lineComments).toEqual([]);
|
|
expect(result.generalComments).toEqual([]);
|
|
expect(result.reviewBody).toBe('');
|
|
});
|
|
|
|
it('should prepend "All Clear" when only severity=none comments exist', () => {
|
|
const comments: Comment[] = [
|
|
{
|
|
file: null,
|
|
line: null,
|
|
severity: CodeScanSeverity.NONE,
|
|
finding: 'No vulnerabilities found',
|
|
},
|
|
];
|
|
const review = 'Scanned 10 files. No issues detected.';
|
|
|
|
const result = prepareComments(comments, review, 'medium');
|
|
|
|
expect(result.reviewBody).toContain(ALL_CLEAR_MESSAGE);
|
|
expect(result.reviewBody).toContain('Scanned 10 files');
|
|
});
|
|
|
|
it('should separate line-specific from general comments', () => {
|
|
const comments: Comment[] = [
|
|
{
|
|
file: 'src/test.ts',
|
|
line: 42,
|
|
severity: CodeScanSeverity.HIGH,
|
|
finding: 'SQL injection vulnerability',
|
|
},
|
|
{
|
|
file: null,
|
|
line: null,
|
|
severity: CodeScanSeverity.MEDIUM,
|
|
finding: 'General security issue',
|
|
},
|
|
{
|
|
file: 'src/file-only.ts',
|
|
line: null,
|
|
severity: CodeScanSeverity.LOW,
|
|
finding: 'File-level security issue',
|
|
},
|
|
];
|
|
|
|
const result = prepareComments(comments, 'Review text', 'low');
|
|
|
|
expect(result.lineComments).toHaveLength(1);
|
|
expect(result.lineComments[0].file).toBe('src/test.ts');
|
|
expect(result.generalComments).toHaveLength(2);
|
|
expect(result.generalComments.map((comment) => comment.file)).toEqual([
|
|
null,
|
|
'src/file-only.ts',
|
|
]);
|
|
});
|
|
|
|
it.each([
|
|
null,
|
|
0,
|
|
-1,
|
|
1.5,
|
|
])('should route findings with a non-inlineable line (%s) to general comments', (line) => {
|
|
const comment: Comment = {
|
|
file: 'src/example.ts',
|
|
line,
|
|
severity: CodeScanSeverity.HIGH,
|
|
finding: 'File-scoped issue',
|
|
};
|
|
|
|
const result = prepareComments([comment], 'Review', 'low');
|
|
|
|
expect(result.lineComments).toEqual([]);
|
|
expect(result.generalComments).toEqual([comment]);
|
|
});
|
|
|
|
it('should filter out severity=none from general comments', () => {
|
|
const comments: Comment[] = [
|
|
{
|
|
file: null,
|
|
line: null,
|
|
severity: CodeScanSeverity.NONE,
|
|
finding: 'All clear message',
|
|
},
|
|
{
|
|
file: null,
|
|
line: null,
|
|
severity: CodeScanSeverity.LOW,
|
|
finding: 'Actual issue',
|
|
},
|
|
];
|
|
|
|
const result = prepareComments(comments, 'Review', 'low');
|
|
|
|
expect(result.generalComments).toHaveLength(1);
|
|
expect(result.generalComments[0].severity).toBe(CodeScanSeverity.LOW);
|
|
});
|
|
|
|
it('should recognize fileless findings as PR-postable but reject severity=none comments', () => {
|
|
expect(
|
|
hasPrPostableFindings([
|
|
{
|
|
file: null,
|
|
line: null,
|
|
severity: CodeScanSeverity.HIGH,
|
|
finding: 'General security issue',
|
|
},
|
|
]),
|
|
).toBe(true);
|
|
|
|
expect(
|
|
hasPrPostableFindings([
|
|
{
|
|
file: 'src/test.ts',
|
|
line: 42,
|
|
severity: CodeScanSeverity.NONE,
|
|
finding: 'No issue found',
|
|
},
|
|
]),
|
|
).toBe(false);
|
|
});
|
|
|
|
it('should append severity threshold', () => {
|
|
const result = prepareComments([], 'Review text', 'medium');
|
|
|
|
expect(result.reviewBody).toContain('Minimum severity threshold');
|
|
expect(result.reviewBody).toContain('Medium');
|
|
});
|
|
|
|
it('should sort comments by severity descending', () => {
|
|
const comments: Comment[] = [
|
|
{ severity: CodeScanSeverity.LOW, finding: 'Low issue', file: 'a.ts', line: 1 },
|
|
{ severity: CodeScanSeverity.CRITICAL, finding: 'Critical issue', file: 'b.ts', line: 2 },
|
|
{ severity: CodeScanSeverity.MEDIUM, finding: 'Medium issue', file: 'c.ts', line: 3 },
|
|
];
|
|
|
|
const result = prepareComments(comments, 'Review', undefined);
|
|
|
|
expect(result.lineComments[0].severity).toBe(CodeScanSeverity.CRITICAL);
|
|
expect(result.lineComments[1].severity).toBe(CodeScanSeverity.MEDIUM);
|
|
expect(result.lineComments[2].severity).toBe(CodeScanSeverity.LOW);
|
|
});
|
|
|
|
it('should not prepend "All Clear" when vulnerabilities exist', () => {
|
|
const comments: Comment[] = [
|
|
{
|
|
severity: CodeScanSeverity.HIGH,
|
|
finding: 'Real vulnerability',
|
|
file: 'test.ts',
|
|
line: 10,
|
|
},
|
|
];
|
|
|
|
const result = prepareComments(comments, 'Found issues', 'low');
|
|
|
|
expect(result.reviewBody).not.toContain(ALL_CLEAR_MESSAGE);
|
|
expect(result.reviewBody).toContain('Found issues');
|
|
});
|
|
});
|