Files
promptfoo--promptfoo/test/codeScans/git/diff.test.ts
T
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:24:08 +08:00

181 lines
5.1 KiB
TypeScript

/**
* Git Diff Tests
*/
import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest';
import { extractDiff, validateOnBranch } from '../../../src/codeScan/git/diff';
import { GitError } from '../../../src/types/codeScan';
import type { SimpleGit, StatusResult } from 'simple-git';
// Define a partial mock type for the SimpleGit methods we use
type MockSimpleGit = Pick<SimpleGit, 'status' | 'branch' | 'diff' | 'log'> & {
status: Mock;
branch: Mock;
diff: Mock;
log: Mock;
};
// Mock simple-git with a factory that returns a mock instance
const mockGit: MockSimpleGit = {
status: vi.fn(),
branch: vi.fn(),
diff: vi.fn(),
log: vi.fn(),
};
vi.mock('simple-git', () => ({
default: vi.fn(() => mockGit),
}));
describe('Git Diff', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('validateOnBranch', () => {
it('should return current branch name when on a branch', async () => {
mockGit.status.mockResolvedValue({
current: 'feature/test-branch',
detached: false,
} as StatusResult);
const branchName = await validateOnBranch(mockGit as unknown as SimpleGit);
expect(branchName).toBe('feature/test-branch');
});
it('should throw GitError when in detached HEAD state', async () => {
mockGit.status.mockResolvedValue({
current: null,
detached: true,
} as StatusResult);
await expect(validateOnBranch(mockGit as unknown as SimpleGit)).rejects.toThrow(GitError);
await expect(validateOnBranch(mockGit as unknown as SimpleGit)).rejects.toThrow(
'Not on a branch',
);
});
});
describe('extractDiff', () => {
it('should extract diff comparing against main branch', async () => {
const mockDiff = `diff --git a/file.ts b/file.ts
index 123..456 100644
--- a/file.ts
+++ b/file.ts
@@ -1,3 +1,4 @@
+const newLine = 'test';
const existingLine = 'existing';`;
// Mock status (for validateOnBranch)
mockGit.status.mockResolvedValue({
current: 'feature/test',
detached: false,
} as StatusResult);
// Mock main branch exists
mockGit.branch.mockResolvedValue({
all: ['main', 'feature/test'],
branches: {},
current: 'feature/test',
detached: false,
} as any);
mockGit.diff.mockResolvedValue(mockDiff);
const result = await extractDiff('/fake/repo');
expect(result).toEqual({
diff: mockDiff,
baseBranch: 'main',
});
expect(mockGit.diff).toHaveBeenCalledWith(['main...HEAD']);
});
it('should fall back to master branch if main does not exist', async () => {
const mockDiff = 'diff content';
// Mock status (for validateOnBranch)
mockGit.status.mockResolvedValue({
current: 'feature/test',
detached: false,
} as StatusResult);
// Mock main branch does not exist, master does
mockGit.branch.mockResolvedValue({
all: ['master', 'feature/test'],
branches: {},
current: 'feature/test',
detached: false,
} as any);
mockGit.diff.mockResolvedValue(mockDiff);
const result = await extractDiff('/fake/repo');
expect(result).toEqual({
diff: mockDiff,
baseBranch: 'master',
});
expect(mockGit.diff).toHaveBeenCalledWith(['master...HEAD']);
});
it('should throw GitError when neither main nor master exists', async () => {
// Mock status (for validateOnBranch)
mockGit.status.mockResolvedValue({
current: 'feature/test',
detached: false,
} as StatusResult);
// Mock neither branch exists - should throw error
mockGit.branch.mockResolvedValue({
all: ['feature/test'],
branches: {},
current: 'feature/test',
detached: false,
} as any);
await expect(extractDiff('/fake/repo')).rejects.toThrow(GitError);
});
it('should throw GitError if no changes detected', async () => {
// Mock status (for validateOnBranch)
mockGit.status.mockResolvedValue({
current: 'feature/test',
detached: false,
} as StatusResult);
mockGit.branch.mockResolvedValue({
all: ['main', 'feature/test'],
branches: {},
current: 'feature/test',
detached: false,
} as any);
// Mock empty diff
mockGit.diff.mockResolvedValue('');
await expect(extractDiff('/fake/repo')).rejects.toThrow(GitError);
await expect(extractDiff('/fake/repo')).rejects.toThrow('No changes detected');
});
it('should throw GitError if diff extraction fails', async () => {
// Mock status (for validateOnBranch)
mockGit.status.mockResolvedValue({
current: 'feature/test',
detached: false,
} as StatusResult);
mockGit.branch.mockResolvedValue({
all: ['main', 'feature/test'],
branches: {},
current: 'feature/test',
detached: false,
} as any);
mockGit.diff.mockRejectedValue(new Error('git diff failed'));
await expect(extractDiff('/fake/repo')).rejects.toThrow(GitError);
await expect(extractDiff('/fake/repo')).rejects.toThrow('Failed to extract diff');
});
});
});