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
239 lines
7.3 KiB
TypeScript
239 lines
7.3 KiB
TypeScript
import { vi } from 'vitest';
|
|
|
|
const exactTransformHandlers = new Map<string, (input: any) => any>([
|
|
['output + " postprocessed"', (input) => input + ' postprocessed'],
|
|
['JSON.parse(output).value', parseJsonValueTransform],
|
|
['`Transformed: ${output}`', (input) => `Transformed: ${input}`],
|
|
['`ProviderTransformed: ${output}`', (input) => `ProviderTransformed: ${input}`],
|
|
['`Provider: ${output}`', (input) => `Provider: ${input}`],
|
|
['`Test: ${output}`', (input) => `Test: ${input}`],
|
|
['"testTransformed " + output', (input) => 'testTransformed ' + input],
|
|
['"defaultTestTransformed " + output', (input) => 'defaultTestTransformed ' + input],
|
|
['"Test: " + output', (input) => 'Test: ' + input],
|
|
['"Provider: " + output', (input) => 'Provider: ' + input],
|
|
['"Transform: " + output', (input) => 'Transform: ' + input],
|
|
['output + "-provider-test"', (input) => input + '-provider-test'],
|
|
['output + "-provider"', (input) => input + '-provider'],
|
|
['output + "-test"', (input) => input + '-test'],
|
|
['`Transform: ${output}`', (input) => `Transform: ${input}`],
|
|
['`Postprocess: ${output}`', (input) => `Postprocess: ${input}`],
|
|
]);
|
|
|
|
function parseJsonValueTransform(input: any) {
|
|
try {
|
|
return JSON.parse(input).value;
|
|
} catch {
|
|
return input;
|
|
}
|
|
}
|
|
|
|
function mockTransformVars(code: string, input: any, context?: any) {
|
|
if (code.includes('vars.transformed = true')) {
|
|
return { ...input, transformed: true };
|
|
}
|
|
if (code.includes('vars.defaultTransform = true')) {
|
|
return { ...input, defaultTransform: true };
|
|
}
|
|
if (code.includes('{ ...vars') && code.includes('toUpperCase()')) {
|
|
return { ...input, name: input.name?.toUpperCase() };
|
|
}
|
|
if (code.includes('{ ...vars') && code.includes('vars.age + 5')) {
|
|
return { ...input, age: (input.age ?? 0) + 5 };
|
|
}
|
|
if (code.includes('return {') && code.includes('context.uuid')) {
|
|
return {
|
|
...input,
|
|
id: context?.uuid || 'mock-uuid',
|
|
hasPrompt: Boolean(context?.prompt),
|
|
};
|
|
}
|
|
if (code.includes('test2UpperCase: vars.test2.toUpperCase()')) {
|
|
return { ...input, test2UpperCase: input.test2?.toUpperCase() };
|
|
}
|
|
}
|
|
|
|
function mockMetadataTransform(code: string, input: any, context?: any) {
|
|
if (!code.includes('context?.metadata')) {
|
|
return undefined;
|
|
}
|
|
if (code.includes('Output:') && context?.metadata) {
|
|
return `Output: ${input}, Metadata: ${JSON.stringify(context.metadata)}`;
|
|
}
|
|
if (code.includes('Has metadata') && context?.metadata) {
|
|
return `Has metadata: ${input}`;
|
|
}
|
|
if (code.includes('No metadata') && !context?.metadata) {
|
|
return `No metadata: ${input}`;
|
|
}
|
|
if (
|
|
code.includes('Empty metadata') &&
|
|
context?.metadata &&
|
|
Object.keys(context.metadata).length === 0
|
|
) {
|
|
return `Empty metadata: ${input}`;
|
|
}
|
|
if (code.includes('All context') && context?.vars && context?.prompt && context?.metadata) {
|
|
return `All context: ${input}`;
|
|
}
|
|
if (
|
|
code.includes('Missing context') &&
|
|
!(context?.vars && context?.prompt && context?.metadata)
|
|
) {
|
|
return `Missing context: ${input}`;
|
|
}
|
|
}
|
|
|
|
async function mockTransform(code: unknown, input: any, context?: any) {
|
|
if (typeof code !== 'string') {
|
|
return input;
|
|
}
|
|
|
|
const exactHandler = exactTransformHandlers.get(code);
|
|
if (exactHandler) {
|
|
return exactHandler(input);
|
|
}
|
|
|
|
return (
|
|
mockTransformVars(code, input, context) ?? mockMetadataTransform(code, input, context) ?? input
|
|
);
|
|
}
|
|
|
|
vi.mock('../../src/util/transform', () => ({
|
|
TransformInputType: {
|
|
OUTPUT: 'output',
|
|
VARS: 'vars',
|
|
},
|
|
// Provide a process shim for ESM compatibility in inline JavaScript code
|
|
getProcessShim: vi.fn().mockReturnValue(process),
|
|
transform: vi.fn().mockImplementation(mockTransform),
|
|
}));
|
|
|
|
vi.mock('../../src/util/fileReference', async () => {
|
|
const actual = await vi.importActual<typeof import('../../src/util/fileReference')>(
|
|
'../../src/util/fileReference',
|
|
);
|
|
return {
|
|
...actual,
|
|
processConfigFileReferences: vi.fn().mockImplementation(async (config) => {
|
|
if (
|
|
typeof config === 'object' &&
|
|
config !== null &&
|
|
config.tests &&
|
|
Array.isArray(config.tests)
|
|
) {
|
|
const result = {
|
|
...config,
|
|
tests: config.tests.map((test: any) => {
|
|
return {
|
|
...test,
|
|
vars:
|
|
test.vars.var1 === 'file://test/fixtures/test_file.txt'
|
|
? {
|
|
var1: '<h1>Sample Report</h1><p>This is a test report with some data for the year 2023.</p>',
|
|
}
|
|
: test.vars,
|
|
};
|
|
}),
|
|
};
|
|
return result;
|
|
}
|
|
return config;
|
|
}),
|
|
};
|
|
});
|
|
|
|
vi.mock('proxy-agent', () => ({
|
|
ProxyAgent: vi.fn().mockImplementation(() => ({})),
|
|
}));
|
|
vi.mock('glob', () => {
|
|
const globSync = vi.fn().mockImplementation((pattern) => {
|
|
if (pattern.includes('test/fixtures/test_file.txt')) {
|
|
return [pattern];
|
|
}
|
|
return [];
|
|
});
|
|
const hasMagic = vi.fn((pattern: string | string[]) => {
|
|
const p = Array.isArray(pattern) ? pattern.join('') : pattern;
|
|
return p.includes('*') || p.includes('?') || p.includes('[') || p.includes('{');
|
|
});
|
|
const glob = Object.assign(vi.fn(), { globSync, hasMagic, sync: globSync });
|
|
return {
|
|
default: { globSync, hasMagic },
|
|
glob,
|
|
globSync,
|
|
hasMagic,
|
|
};
|
|
});
|
|
|
|
vi.mock('../../src/esm', async (importOriginal) => {
|
|
return {
|
|
...(await importOriginal()),
|
|
importModule: vi.fn(),
|
|
};
|
|
});
|
|
|
|
vi.mock('../../src/evaluatorHelpers', async () => {
|
|
const actual = await vi.importActual<typeof import('../../src/evaluatorHelpers')>(
|
|
'../../src/evaluatorHelpers',
|
|
);
|
|
return {
|
|
...actual,
|
|
runExtensionHook: vi.fn().mockImplementation((_extensions, _hookName, context) => context),
|
|
};
|
|
});
|
|
|
|
vi.mock('../../src/cliState', () => ({
|
|
__esModule: true,
|
|
default: {
|
|
resume: false,
|
|
basePath: '',
|
|
webUI: false,
|
|
},
|
|
}));
|
|
|
|
vi.mock('../../src/models/prompt', () => ({
|
|
generateIdFromPrompt: vi.fn((prompt) => `prompt-${prompt.label || 'default'}`),
|
|
}));
|
|
|
|
vi.mock('../../src/util/time', async () => {
|
|
const actual = await vi.importActual<typeof import('../../src/util/time')>('../../src/util/time');
|
|
return {
|
|
...actual,
|
|
sleep: vi.fn(),
|
|
};
|
|
});
|
|
|
|
vi.mock('../../src/util/fileExtensions', async () => {
|
|
const actual = await vi.importActual<typeof import('../../src/util/fileExtensions')>(
|
|
'../../src/util/fileExtensions',
|
|
);
|
|
return {
|
|
...actual,
|
|
isImageFile: vi
|
|
.fn()
|
|
.mockImplementation((filePath) => filePath.endsWith('.jpg') || filePath.endsWith('.png')),
|
|
isVideoFile: vi.fn().mockImplementation((filePath) => filePath.endsWith('.mp4')),
|
|
isAudioFile: vi.fn().mockImplementation((filePath) => filePath.endsWith('.mp3')),
|
|
isJavascriptFile: vi.fn().mockReturnValue(false),
|
|
};
|
|
});
|
|
|
|
vi.mock('../../src/util/functions/loadFunction', async () => {
|
|
const actual = await vi.importActual<typeof import('../../src/util/functions/loadFunction')>(
|
|
'../../src/util/functions/loadFunction',
|
|
);
|
|
return {
|
|
...actual,
|
|
loadFunction: vi.fn().mockImplementation((options) => {
|
|
if (options.filePath.includes('scoring')) {
|
|
return Promise.resolve((_metrics: Record<string, number>) => ({
|
|
pass: true,
|
|
score: 0.75,
|
|
reason: 'Custom scoring reason',
|
|
}));
|
|
}
|
|
return Promise.resolve(() => {});
|
|
}),
|
|
};
|
|
});
|