chore: import upstream snapshot with attribution
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
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Has been cancelled
Test and Publish Multi-arch Docker Image / test (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Has been cancelled
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) Has been cancelled
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Has been cancelled
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Has been cancelled
Validate Renovate Config / Validate Renovate Configuration (push) Has been cancelled
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
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Has been cancelled
Test and Publish Multi-arch Docker Image / test (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Has been cancelled
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) Has been cancelled
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Has been cancelled
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Has been cancelled
Validate Renovate Config / Validate Renovate Configuration (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* GitHub OIDC Authentication Tests
|
||||
*/
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { getGitHubOIDCToken } from '../../code-scan-action/src/auth';
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
core: {
|
||||
getIDToken: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock both the root specifier and the nested package entry resolved from code-scan-action.
|
||||
vi.mock('@actions/core', () => mocks.core);
|
||||
vi.mock('../../code-scan-action/node_modules/@actions/core/lib/core.js', () => mocks.core);
|
||||
|
||||
describe('getGitHubOIDCToken', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should return OIDC token for given audience', async () => {
|
||||
mocks.core.getIDToken.mockResolvedValue('mock-oidc-token');
|
||||
|
||||
const token = await getGitHubOIDCToken('https://scanner.example.com');
|
||||
|
||||
expect(mocks.core.getIDToken).toHaveBeenCalledWith('https://scanner.example.com');
|
||||
expect(token).toBe('mock-oidc-token');
|
||||
});
|
||||
|
||||
it('should throw error when getIDToken fails', async () => {
|
||||
mocks.core.getIDToken.mockRejectedValue(new Error('OIDC not configured'));
|
||||
|
||||
await expect(getGitHubOIDCToken('https://scanner.example.com')).rejects.toThrow(
|
||||
'Failed to get GitHub OIDC token: OIDC not configured',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import * as fs from 'fs';
|
||||
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
import { ZodError } from 'zod';
|
||||
import { generateConfigFile } from '../../code-scan-action/src/config';
|
||||
|
||||
describe('generateConfigFile', () => {
|
||||
const generatedPaths: string[] = [];
|
||||
|
||||
afterEach(() => {
|
||||
for (const configPath of generatedPaths.splice(0)) {
|
||||
if (fs.existsSync(configPath)) {
|
||||
fs.unlinkSync(configPath);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function readGeneratedConfig(minimumSeverity: string, guidance?: string): string {
|
||||
const configPath = generateConfigFile(minimumSeverity, guidance);
|
||||
generatedPaths.push(configPath);
|
||||
return fs.readFileSync(configPath, 'utf8');
|
||||
}
|
||||
|
||||
it('writes a normalized config for full-repository scans', () => {
|
||||
expect(readGeneratedConfig(' HIGH ')).toBe('minimumSeverity: high\ndiffsOnly: false\n');
|
||||
});
|
||||
|
||||
it('escapes single-line guidance as a YAML string', () => {
|
||||
expect(readGeneratedConfig('medium', 'Review "auth" carefully')).toBe(
|
||||
'minimumSeverity: medium\ndiffsOnly: false\nguidance: "Review \\"auth\\" carefully"\n',
|
||||
);
|
||||
});
|
||||
|
||||
it('writes multiline guidance as a YAML literal block', () => {
|
||||
expect(readGeneratedConfig('critical', 'Line one\nLine two')).toBe(
|
||||
'minimumSeverity: critical\ndiffsOnly: false\nguidance: |\n Line one\n Line two\n',
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects invalid severities', () => {
|
||||
let thrown: unknown;
|
||||
|
||||
try {
|
||||
generateConfigFile('high!');
|
||||
} catch (error) {
|
||||
thrown = error;
|
||||
}
|
||||
|
||||
expect(thrown).toBeInstanceOf(ZodError);
|
||||
|
||||
const issue = (thrown as ZodError).issues[0];
|
||||
expect(issue).toMatchObject({
|
||||
code: 'invalid_value',
|
||||
message: expect.stringContaining('Invalid option'),
|
||||
});
|
||||
expect(issue).toHaveProperty('values', ['critical', 'high', 'medium', 'low', 'none']);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,180 @@
|
||||
/**
|
||||
* GitHub API Client Tests
|
||||
*/
|
||||
|
||||
import * as github from '@actions/github';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { getGitHubContext, partitionReviewCommentsByDiff } from '../../code-scan-action/src/github';
|
||||
import type { Octokit } from '@octokit/rest';
|
||||
|
||||
const mocks = vi.hoisted(() => {
|
||||
// Mock diff that includes src/auth.ts with lines 40-100 in scope
|
||||
const mockDiff = `diff --git a/src/auth.ts b/src/auth.ts
|
||||
index abc123..def456 100644
|
||||
--- a/src/auth.ts
|
||||
+++ b/src/auth.ts
|
||||
@@ -40,60 +40,60 @@
|
||||
context line 40
|
||||
context line 41
|
||||
context line 42
|
||||
+added line 43
|
||||
context line 44
|
||||
context line 45
|
||||
context line 46
|
||||
context line 47
|
||||
context line 48
|
||||
context line 49
|
||||
context line 50
|
||||
context line 51
|
||||
context line 52
|
||||
context line 53
|
||||
context line 54
|
||||
context line 55
|
||||
context line 56
|
||||
context line 57
|
||||
context line 58
|
||||
context line 59
|
||||
context line 60
|
||||
`;
|
||||
|
||||
return {
|
||||
core: {
|
||||
info: vi.fn(),
|
||||
warning: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
github: {
|
||||
context: {
|
||||
repo: {
|
||||
owner: 'test-owner',
|
||||
repo: 'test-repo',
|
||||
},
|
||||
payload: {
|
||||
pull_request: {
|
||||
number: 123,
|
||||
head: {
|
||||
sha: 'abc123',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mockDiff,
|
||||
Octokit: vi.fn().mockImplementation(() => ({
|
||||
pulls: {
|
||||
createReview: vi.fn().mockResolvedValue({}),
|
||||
get: vi.fn().mockResolvedValue({ data: mockDiff }),
|
||||
},
|
||||
issues: {
|
||||
createComment: vi.fn().mockResolvedValue({}),
|
||||
},
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
// Mock @actions/core
|
||||
vi.mock('@actions/core', () => mocks.core);
|
||||
vi.mock('../../code-scan-action/node_modules/@actions/core/lib/core.js', () => mocks.core);
|
||||
|
||||
// Mock both the root specifiers and the nested package entries resolved from code-scan-action.
|
||||
vi.mock('@actions/github', () => mocks.github);
|
||||
vi.mock('../../code-scan-action/node_modules/@actions/github/lib/github.js', () => mocks.github);
|
||||
|
||||
// Mock Octokit
|
||||
vi.mock('@octokit/rest', () => ({ Octokit: mocks.Octokit }));
|
||||
vi.mock('../../code-scan-action/node_modules/@octokit/rest/dist-src/index.js', () => ({
|
||||
Octokit: mocks.Octokit,
|
||||
}));
|
||||
|
||||
const mockDiff = mocks.mockDiff;
|
||||
|
||||
describe('GitHub API Client', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
mocks.github.context.repo = {
|
||||
owner: 'test-owner',
|
||||
repo: 'test-repo',
|
||||
};
|
||||
mocks.github.context.payload = {
|
||||
pull_request: {
|
||||
number: 123,
|
||||
head: {
|
||||
sha: 'abc123',
|
||||
},
|
||||
},
|
||||
};
|
||||
mocks.Octokit.mockImplementation(function () {
|
||||
return {
|
||||
pulls: {
|
||||
createReview: vi.fn().mockResolvedValue({}),
|
||||
get: vi.fn().mockResolvedValue({ data: mockDiff }),
|
||||
},
|
||||
issues: {
|
||||
createComment: vi.fn().mockResolvedValue({}),
|
||||
},
|
||||
} as unknown as Octokit;
|
||||
});
|
||||
});
|
||||
|
||||
describe('getGitHubContext', () => {
|
||||
it('should extract context from github.context', async () => {
|
||||
const context = await getGitHubContext('test-token');
|
||||
|
||||
expect(context).toEqual({
|
||||
owner: 'test-owner',
|
||||
repo: 'test-repo',
|
||||
number: 123,
|
||||
sha: 'abc123',
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw error when not in PR context', async () => {
|
||||
const originalPayload = github.context.payload;
|
||||
github.context.payload = {};
|
||||
|
||||
await expect(getGitHubContext('test-token')).rejects.toThrow(
|
||||
'This action requires a pull_request event or workflow_dispatch with pr_number input',
|
||||
);
|
||||
|
||||
github.context.payload = originalPayload;
|
||||
});
|
||||
});
|
||||
|
||||
describe('partitionReviewCommentsByDiff', () => {
|
||||
const mockContext = {
|
||||
owner: 'test-owner',
|
||||
repo: 'test-repo',
|
||||
number: 123,
|
||||
sha: 'abc123',
|
||||
};
|
||||
|
||||
it('clamps comments to visible diff lines and routes unmapped files to general comments', async () => {
|
||||
const result = await partitionReviewCommentsByDiff('fake-token', mockContext, [
|
||||
{
|
||||
file: 'src/auth.ts',
|
||||
line: 500,
|
||||
finding: 'Finding in a changed file',
|
||||
},
|
||||
{
|
||||
file: 'src/outside-diff.ts',
|
||||
line: 12,
|
||||
finding: 'Finding outside the diff',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(result.lineComments).toEqual([
|
||||
expect.objectContaining({
|
||||
file: 'src/auth.ts',
|
||||
line: 61,
|
||||
}),
|
||||
]);
|
||||
expect(result.generalComments).toEqual([]);
|
||||
expect(result.invalidLineComments).toEqual([
|
||||
expect.objectContaining({
|
||||
file: 'src/outside-diff.ts',
|
||||
line: 12,
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user