chore: import upstream snapshot with attribution
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

This commit is contained in:
wehub-resource-sync
2026-07-13 13:24:08 +08:00
commit 0d3cb498a3
5438 changed files with 1316560 additions and 0 deletions
@@ -0,0 +1,332 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { evaluate } from '../src/evaluator';
import Eval from '../src/models/eval';
import type { ApiProvider, TestSuite } from '../src/types/index';
// Create hoisted mock for transform
const mockTransform = vi.hoisted(() => vi.fn());
const mockRunAssertions = vi.hoisted(() => vi.fn());
// Mock the transform function to track calls
vi.mock('../src/util/transform', () => ({
transform: mockTransform,
TransformInputType: {
OUTPUT: 'output',
VARS: 'vars',
},
}));
// Mock assertions to prevent timeouts
vi.mock('../src/assertions', async () => {
const actual = await vi.importActual('../src/assertions');
return {
...(actual as any),
runAssertions: mockRunAssertions,
};
});
// Mock cache to prevent file system operations
vi.mock('../src/cache', () => ({
getCache: vi.fn(() => ({
get: vi.fn(),
set: vi.fn(),
wrap: vi.fn((_key: any, fn: any) => fn()),
})),
withCacheNamespace: vi.fn(async (_namespace: string | undefined, fn: () => Promise<unknown>) =>
fn(),
),
}));
// Mock logger to prevent console output during tests
vi.mock('../src/logger', () => ({
default: {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
},
}));
// Mock file operations
vi.mock('../src/util/file', () => ({
readFileCached: vi.fn(() => Promise.resolve('')),
}));
// Mock evaluator helpers
vi.mock('../src/evaluatorHelpers', async () => {
const actual = await vi.importActual('../src/evaluatorHelpers');
return {
...(actual as any),
runExtensionHook: vi.fn((...args: any[]) => args[2]),
};
});
// Mock time utilities
vi.mock('../src/util/time', async () => {
const actual = await vi.importActual('../src/util/time');
return {
...(actual as any),
sleep: vi.fn(() => Promise.resolve()),
};
});
// Mock ESM
vi.mock('../src/esm', () => ({}));
describe('Transformation integration', () => {
beforeEach(() => {
vi.clearAllMocks();
mockTransform.mockReset();
mockRunAssertions.mockReset();
mockRunAssertions.mockResolvedValue({
pass: true,
score: 1,
namedScores: {},
});
});
afterEach(() => {
vi.restoreAllMocks();
});
it('passes provider-transformed output to test transforms and context assertions', async () => {
// Track the transformation sequence
const transformCalls: { expression: string; input: any }[] = [];
mockTransform.mockImplementation(async (expression, input) => {
transformCalls.push({ expression, input });
if (expression === 'output.toUpperCase()') {
// Provider transform
return 'PROVIDER TRANSFORMED';
} else if (expression === 'output + " - test transformed"') {
// Test transform - should receive provider output
return input + ' - test transformed';
}
return input;
});
const testSuite: TestSuite = {
prompts: [{ raw: 'Test prompt', label: 'Test' }],
providers: [
{
id: () => 'mock-provider',
callApi: async () => ({
output: 'original output',
tokenUsage: { total: 10, prompt: 5, completion: 5, numRequests: 1 },
}),
transform: 'output.toUpperCase()',
} as ApiProvider,
],
tests: [
{
vars: {
query: 'test query',
context: 'test context',
},
assert: [
{
type: 'equals',
value: 'PROVIDER TRANSFORMED - test transformed',
},
{
type: 'context-faithfulness',
contextTransform: 'output.split(" ")[0]',
},
],
options: {
transform: 'output + " - test transformed"',
},
},
],
};
const evalRecord = new Eval({});
const results = await evaluate(testSuite, evalRecord, { maxConcurrency: 1 });
// Check that the test transform received provider output.
const testTransformCall = transformCalls.find(
(c) => c.expression === 'output + " - test transformed"',
);
expect(testTransformCall?.input).toBe('PROVIDER TRANSFORMED');
expect(results.results[0].response?.output).toBe('PROVIDER TRANSFORMED - test transformed');
expect(mockRunAssertions).toHaveBeenCalledWith(
expect.objectContaining({
providerResponse: expect.objectContaining({
output: 'PROVIDER TRANSFORMED - test transformed',
providerTransformedOutput: 'PROVIDER TRANSFORMED',
}),
}),
);
});
it('passes provider-transformed output through with multiple context assertions', async () => {
const transformCalls: { expression: string; input: any; timestamp: number }[] = [];
mockTransform.mockImplementation(async (expression, input) => {
transformCalls.push({ expression, input, timestamp: Date.now() });
if (expression === 'JSON.stringify({provider: output})') {
// Provider transform
return JSON.stringify({ provider: input });
} else if (expression === 'JSON.parse(output).provider.toLowerCase()') {
// Test transform
const parsed = typeof input === 'string' ? JSON.parse(input) : input;
return parsed.provider.toLowerCase();
}
return input;
});
const testSuite: TestSuite = {
prompts: [{ raw: 'Test prompt', label: 'Test' }],
providers: [
{
id: () => 'mock-provider',
callApi: async () => ({
output: 'ORIGINAL',
tokenUsage: { total: 10, prompt: 5, completion: 5, numRequests: 1 },
}),
transform: 'JSON.stringify({provider: output})',
} as ApiProvider,
],
tests: [
{
vars: {
query: 'test query',
},
assert: [
{
type: 'context-faithfulness',
contextTransform: 'JSON.parse(output).context1',
},
{
type: 'context-recall',
contextTransform: 'JSON.parse(output).context2',
},
{
type: 'context-relevance',
contextTransform: 'JSON.parse(output).context3',
},
],
options: {
transform: 'JSON.parse(output).provider.toLowerCase()',
},
},
],
};
const evalRecord = new Eval({});
await evaluate(testSuite, evalRecord, { maxConcurrency: 1 });
const testTransform = transformCalls.find((c) => c.expression.includes('toLowerCase'));
expect(testTransform?.input).toBe('{"provider":"ORIGINAL"}');
expect(mockRunAssertions).toHaveBeenCalledWith(
expect.objectContaining({
providerResponse: expect.objectContaining({
output: 'original',
providerTransformedOutput: '{"provider":"ORIGINAL"}',
}),
test: expect.objectContaining({
assert: [
expect.objectContaining({ contextTransform: 'JSON.parse(output).context1' }),
expect.objectContaining({ contextTransform: 'JSON.parse(output).context2' }),
expect.objectContaining({ contextTransform: 'JSON.parse(output).context3' }),
],
}),
}),
);
});
// Note: function-transform wiring tests used to live here but were removed
// in favor of `test/evaluator.integration.realTransforms.test.ts`, which
// exercises the real `transform()` pipeline end-to-end instead of asserting
// that a mock was called with a function reference.
it('should work correctly with only provider transform', async () => {
mockTransform.mockImplementation(async (expression, input) => {
if (expression === 'output.trim().toUpperCase()') {
return String(input).trim().toUpperCase();
}
return input;
});
const testSuite: TestSuite = {
prompts: [{ raw: 'Test prompt', label: 'Test' }],
providers: [
{
id: () => 'mock-provider',
callApi: async () => ({
output: ' spaced output ',
tokenUsage: { total: 10, prompt: 5, completion: 5, numRequests: 1 },
}),
transform: 'output.trim().toUpperCase()',
} as ApiProvider,
],
tests: [
{
assert: [
{
type: 'equals',
value: 'SPACED OUTPUT',
},
],
},
],
};
const evalRecord = new Eval({});
const results = await evaluate(testSuite, evalRecord, { maxConcurrency: 1 });
expect(results.results[0].response?.output).toBe('SPACED OUTPUT');
});
it('passes raw provider output to context assertions when no transforms are configured', async () => {
const testSuite: TestSuite = {
prompts: [{ raw: 'Test prompt', label: 'Test' }],
providers: [
{
id: () => 'mock-provider',
callApi: async () => ({
output: { data: 'test', metadata: { extracted: 'metadata' } },
tokenUsage: { total: 10, prompt: 5, completion: 5, numRequests: 1 },
}),
} as ApiProvider,
],
tests: [
{
vars: { query: 'test' },
assert: [
{
type: 'context-faithfulness',
contextTransform: 'output.metadata',
},
],
},
],
};
const evalRecord = new Eval({});
await evaluate(testSuite, evalRecord, { maxConcurrency: 1 });
expect(mockRunAssertions).toHaveBeenCalledWith(
expect.objectContaining({
providerResponse: expect.objectContaining({
output: {
data: 'test',
metadata: { extracted: 'metadata' },
},
providerTransformedOutput: {
data: 'test',
metadata: { extracted: 'metadata' },
},
}),
test: expect.objectContaining({
assert: [expect.objectContaining({ contextTransform: 'output.metadata' })],
}),
}),
);
});
});