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

892 lines
26 KiB
TypeScript

import chalk from 'chalk';
import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from 'vitest';
import { generateEvalSummary } from '../../../src/util/eval/summary';
import { stripAnsi } from '../../util/utils';
import type { EvalSummaryParams } from '../../../src/util/eval/summary';
import type { TokenUsageTracker } from '../../../src/util/tokenUsage';
type MockTracker = {
getProviderIds: Mock;
getProviderUsage: Mock;
trackUsage: Mock;
resetAllUsage: Mock;
resetProviderUsage: Mock;
getTotalUsage: Mock;
cleanup: Mock;
};
function createMockTracker(): TokenUsageTracker {
return {
getProviderIds: vi.fn().mockReturnValue([]),
getProviderUsage: vi.fn(),
trackUsage: vi.fn(),
resetAllUsage: vi.fn(),
resetProviderUsage: vi.fn(),
getTotalUsage: vi.fn(),
cleanup: vi.fn(),
} as unknown as TokenUsageTracker;
}
describe('generateEvalSummary', () => {
let mockTracker: MockTracker & TokenUsageTracker;
beforeEach(() => {
vi.clearAllMocks();
mockTracker = createMockTracker() as MockTracker & TokenUsageTracker;
});
afterEach(() => {
vi.clearAllMocks();
});
describe('completion message', () => {
it('should show basic completion message when not writing to database', () => {
const params: EvalSummaryParams = {
evalId: 'eval-123',
isRedteam: false,
writeToDatabase: false,
shareableUrl: null,
wantsToShare: false,
hasExplicitDisable: false,
cloudEnabled: false,
tokenUsage: { total: 0 },
successes: 5,
failures: 0,
errors: 0,
duration: 5000,
maxConcurrency: 4,
tracker: mockTracker,
};
const lines = generateEvalSummary(params);
const output = stripAnsi(lines.join('\n'));
expect(output).toContain('✓ Eval complete');
expect(output).not.toContain('eval-123');
});
it('should show eval ID when writing to database without shareable URL', () => {
const params: EvalSummaryParams = {
evalId: 'eval-456',
isRedteam: false,
writeToDatabase: true,
shareableUrl: null,
wantsToShare: false,
hasExplicitDisable: false,
cloudEnabled: false,
tokenUsage: { total: 0 },
successes: 5,
failures: 0,
errors: 0,
duration: 5000,
maxConcurrency: 4,
tracker: mockTracker,
};
const lines = generateEvalSummary(params);
const output = stripAnsi(lines.join('\n'));
expect(output).toContain('✓ Eval complete (ID: eval-456)');
});
it('should show shareable URL when available', () => {
const params: EvalSummaryParams = {
evalId: 'eval-789',
isRedteam: false,
writeToDatabase: true,
shareableUrl: 'https://promptfoo.app/eval/abc123',
wantsToShare: true,
hasExplicitDisable: false,
cloudEnabled: false,
tokenUsage: { total: 0 },
successes: 5,
failures: 0,
errors: 0,
duration: 5000,
maxConcurrency: 4,
tracker: mockTracker,
};
const lines = generateEvalSummary(params);
const output = stripAnsi(lines.join('\n'));
expect(output).toContain('✓ Eval complete: https://promptfoo.app/eval/abc123');
expect(output).not.toContain('eval-789');
});
it('should say "Red team complete" for red team evals', () => {
const params: EvalSummaryParams = {
evalId: 'eval-rt-1',
isRedteam: true,
writeToDatabase: false,
shareableUrl: null,
wantsToShare: false,
hasExplicitDisable: false,
cloudEnabled: false,
tokenUsage: { total: 0 },
successes: 10,
failures: 2,
errors: 0,
duration: 8000,
maxConcurrency: 4,
tracker: mockTracker,
};
const lines = generateEvalSummary(params);
const output = stripAnsi(lines.join('\n'));
expect(output).toContain('✓ Red team complete');
expect(output).not.toContain('Eval complete');
});
it('should explain non-retryable target errors when an eval is aborted', () => {
const params: EvalSummaryParams = {
evalId: 'eval-aborted',
isRedteam: false,
writeToDatabase: true,
shareableUrl: null,
wantsToShare: false,
hasExplicitDisable: false,
cloudEnabled: false,
tokenUsage: { total: 0 },
successes: 0,
failures: 0,
errors: 1,
duration: 1000,
maxConcurrency: 4,
tracker: mockTracker,
targetErrorStatus: 401,
};
const lines = generateEvalSummary(params);
const output = stripAnsi(lines.join('\n'));
expect(output).toContain('✗ Eval aborted (ID: eval-aborted)');
expect(output).toContain(
'Scan stopped: Target is unavailable and will not recover on retry.',
);
expect(output).toContain('Target returned HTTP 401');
expect(output).not.toContain('Server error (500)');
expect(output).toContain('To fix: Check your target configuration and credentials.');
});
});
describe('token usage', () => {
it('should display eval tokens correctly', () => {
const params: EvalSummaryParams = {
evalId: 'eval-123',
isRedteam: false,
writeToDatabase: false,
shareableUrl: null,
wantsToShare: false,
hasExplicitDisable: false,
cloudEnabled: false,
tokenUsage: {
total: 1000,
prompt: 400,
completion: 600,
cached: 0,
},
successes: 5,
failures: 0,
errors: 0,
duration: 5000,
maxConcurrency: 4,
tracker: mockTracker,
};
const lines = generateEvalSummary(params);
const output = stripAnsi(lines.join('\n'));
expect(output).toContain('Tokens:');
expect(output).toContain('Eval: 1,000 (400 prompt, 600 completion)');
});
it('should display grading tokens only when no eval tokens (critical bug fix)', () => {
const params: EvalSummaryParams = {
evalId: 'eval-grading-only',
isRedteam: false,
writeToDatabase: false,
shareableUrl: null,
wantsToShare: false,
hasExplicitDisable: false,
cloudEnabled: false,
tokenUsage: {
total: 0,
assertions: {
total: 500,
prompt: 200,
completion: 300,
cached: 0,
},
},
successes: 5,
failures: 0,
errors: 0,
duration: 5000,
maxConcurrency: 4,
tracker: mockTracker,
};
const lines = generateEvalSummary(params);
const output = stripAnsi(lines.join('\n'));
expect(output).toContain('Tokens:');
expect(output).toContain('Grading: 500 (200 prompt, 300 completion)');
expect(output).not.toContain('Eval:');
});
it('should display both eval and grading tokens', () => {
const params: EvalSummaryParams = {
evalId: 'eval-both',
isRedteam: false,
writeToDatabase: false,
shareableUrl: null,
wantsToShare: false,
hasExplicitDisable: false,
cloudEnabled: false,
tokenUsage: {
total: 1000,
prompt: 400,
completion: 600,
assertions: {
total: 500,
prompt: 200,
completion: 300,
},
},
successes: 5,
failures: 0,
errors: 0,
duration: 5000,
maxConcurrency: 4,
tracker: mockTracker,
};
const lines = generateEvalSummary(params);
const output = stripAnsi(lines.join('\n'));
expect(output).toContain('Tokens:');
expect(output).toContain('Eval: 1,000 (400 prompt, 600 completion)');
expect(output).toContain('Grading: 500 (200 prompt, 300 completion)');
});
it('should show 100% cached correctly', () => {
const params: EvalSummaryParams = {
evalId: 'eval-cached',
isRedteam: false,
writeToDatabase: false,
shareableUrl: null,
wantsToShare: false,
hasExplicitDisable: false,
cloudEnabled: false,
tokenUsage: {
total: 1000,
cached: 1000,
},
successes: 5,
failures: 0,
errors: 0,
duration: 5000,
maxConcurrency: 4,
tracker: mockTracker,
};
const lines = generateEvalSummary(params);
const output = stripAnsi(lines.join('\n'));
expect(output).toContain('Tokens:');
expect(output).toContain('Eval: 1,000 (cached)');
});
it('should show partial cached tokens', () => {
const params: EvalSummaryParams = {
evalId: 'eval-partial-cache',
isRedteam: false,
writeToDatabase: false,
shareableUrl: null,
wantsToShare: false,
hasExplicitDisable: false,
cloudEnabled: false,
tokenUsage: {
total: 1000,
prompt: 400,
completion: 600,
cached: 200,
},
successes: 5,
failures: 0,
errors: 0,
duration: 5000,
maxConcurrency: 4,
tracker: mockTracker,
};
const lines = generateEvalSummary(params);
const output = stripAnsi(lines.join('\n'));
expect(output).toContain('Eval: 1,000 (400 prompt, 600 completion, 200 cached)');
});
it('should not show token section when no tokens', () => {
const params: EvalSummaryParams = {
evalId: 'eval-no-tokens',
isRedteam: false,
writeToDatabase: false,
shareableUrl: null,
wantsToShare: false,
hasExplicitDisable: false,
cloudEnabled: false,
tokenUsage: { total: 0 },
successes: 5,
failures: 0,
errors: 0,
duration: 5000,
maxConcurrency: 4,
tracker: mockTracker,
};
const lines = generateEvalSummary(params);
const output = stripAnsi(lines.join('\n'));
expect(output).not.toContain('Tokens:');
});
});
describe('provider breakdown', () => {
it('should show provider breakdown with request counts', () => {
mockTracker.getProviderIds.mockReturnValue(['openai:gpt-4', 'anthropic:claude-3']);
mockTracker.getProviderUsage.mockImplementation((id: string) => {
if (id === 'openai:gpt-4') {
return {
total: 1500,
prompt: 600,
completion: 900,
cached: 0,
numRequests: 5,
};
}
if (id === 'anthropic:claude-3') {
return {
total: 800,
prompt: 300,
completion: 500,
cached: 0,
numRequests: 3,
};
}
return undefined;
});
const params: EvalSummaryParams = {
evalId: 'eval-providers',
isRedteam: false,
writeToDatabase: false,
shareableUrl: null,
wantsToShare: false,
hasExplicitDisable: false,
cloudEnabled: false,
tokenUsage: { total: 2300 },
successes: 8,
failures: 0,
errors: 0,
duration: 5000,
maxConcurrency: 4,
tracker: mockTracker,
};
const lines = generateEvalSummary(params);
const output = stripAnsi(lines.join('\n'));
expect(output).toContain('Providers:');
expect(output).toContain('openai:gpt-4');
expect(output).toContain('1,500 (5 requests; 600 prompt, 900 completion)');
expect(output).toContain('anthropic:claude-3');
expect(output).toContain('800 (3 requests; 300 prompt, 500 completion)');
});
it('should always show request count even when 0', () => {
mockTracker.getProviderIds.mockReturnValue(['openai:gpt-4', 'anthropic:claude-3']);
mockTracker.getProviderUsage.mockImplementation((id: string) => {
if (id === 'openai:gpt-4') {
return {
total: 1000,
cached: 1000,
numRequests: 0,
};
}
if (id === 'anthropic:claude-3') {
return {
total: 500,
prompt: 200,
completion: 300,
numRequests: 2,
};
}
return undefined;
});
const params: EvalSummaryParams = {
evalId: 'eval-zero-requests',
isRedteam: false,
writeToDatabase: false,
shareableUrl: null,
wantsToShare: false,
hasExplicitDisable: false,
cloudEnabled: false,
tokenUsage: { total: 1500 },
successes: 5,
failures: 0,
errors: 0,
duration: 5000,
maxConcurrency: 4,
tracker: mockTracker,
};
const lines = generateEvalSummary(params);
const output = stripAnsi(lines.join('\n'));
expect(output).toContain('Providers:');
expect(output).toContain('openai:gpt-4: 1,000 (0 requests; cached)');
expect(output).toContain('anthropic:claude-3: 500 (2 requests; 200 prompt, 300 completion)');
});
});
describe('pass rate and results', () => {
it('should show percentages for each result line at 100% pass rate', () => {
const params: EvalSummaryParams = {
evalId: 'eval-100',
isRedteam: false,
writeToDatabase: false,
shareableUrl: null,
wantsToShare: false,
hasExplicitDisable: false,
cloudEnabled: false,
tokenUsage: { total: 0 },
successes: 10,
failures: 0,
errors: 0,
duration: 5000,
maxConcurrency: 4,
tracker: mockTracker,
};
const lines = generateEvalSummary(params);
const plainOutput = stripAnsi(lines.join('\n'));
expect(plainOutput).toContain('Results:');
expect(plainOutput).toContain('10 passed (100%)');
expect(plainOutput).toContain('0 failed (0%)');
expect(plainOutput).toContain('0 errors (0%)');
});
it('should show percentages for each result line when some tests fail', () => {
const params: EvalSummaryParams = {
evalId: 'eval-85',
isRedteam: false,
writeToDatabase: false,
shareableUrl: null,
wantsToShare: false,
hasExplicitDisable: false,
cloudEnabled: false,
tokenUsage: { total: 0 },
successes: 17,
failures: 3,
errors: 0,
duration: 5000,
maxConcurrency: 4,
tracker: mockTracker,
};
const lines = generateEvalSummary(params);
const plainOutput = stripAnsi(lines.join('\n'));
expect(plainOutput).toContain('Results:');
expect(plainOutput).toContain('17 passed (85.00%)');
expect(plainOutput).toContain('3 failed (15.00%)');
expect(plainOutput).toContain('0 errors (0%)');
});
it('should show percentages for each result line when passed and failed are split evenly', () => {
const params: EvalSummaryParams = {
evalId: 'eval-50',
isRedteam: false,
writeToDatabase: false,
shareableUrl: null,
wantsToShare: false,
hasExplicitDisable: false,
cloudEnabled: false,
tokenUsage: { total: 0 },
successes: 5,
failures: 5,
errors: 0,
duration: 5000,
maxConcurrency: 4,
tracker: mockTracker,
};
const lines = generateEvalSummary(params);
const plainOutput = stripAnsi(lines.join('\n'));
expect(plainOutput).toContain('Results:');
expect(plainOutput).toContain('5 passed (50.00%)');
expect(plainOutput).toContain('5 failed (50.00%)');
expect(plainOutput).toContain('0 errors (0%)');
});
it('should include errors in results', () => {
const params: EvalSummaryParams = {
evalId: 'eval-errors',
isRedteam: false,
writeToDatabase: false,
shareableUrl: null,
wantsToShare: false,
hasExplicitDisable: false,
cloudEnabled: false,
tokenUsage: { total: 0 },
successes: 8,
failures: 1,
errors: 1,
duration: 5000,
maxConcurrency: 4,
tracker: mockTracker,
};
const lines = generateEvalSummary(params);
const plainOutput = stripAnsi(lines.join('\n'));
const outputLines = plainOutput.split('\n');
const hasLineMatching = (pattern: RegExp) => outputLines.some((line) => pattern.test(line));
expect(plainOutput).toContain('Results:');
expect(hasLineMatching(/^\s*(✓\s+)?8 passed \(80\.00%\)$/)).toBe(true);
expect(hasLineMatching(/^\s*(✗\s+)?1 failed \(10\.00%\)$/)).toBe(true);
expect(hasLineMatching(/^\s*(✗\s+)?1 error \(10\.00%\)$/)).toBe(true);
expect(plainOutput).not.toContain('1 errors');
});
it('should render colored icons with muted percentages', () => {
const params: EvalSummaryParams = {
evalId: 'eval-styling',
isRedteam: false,
writeToDatabase: false,
shareableUrl: null,
wantsToShare: false,
hasExplicitDisable: false,
cloudEnabled: false,
tokenUsage: { total: 0 },
successes: 8,
failures: 1,
errors: 1,
duration: 5000,
maxConcurrency: 4,
tracker: mockTracker,
};
const lines = generateEvalSummary(params);
expect(lines).toContain(
` ${chalk.green('✓')} ${chalk.white.bold('8')} ${chalk.white('passed')} ${chalk.gray('(80.00%)')}`,
);
expect(lines).toContain(
` ${chalk.red('✗')} ${chalk.white.bold('1')} ${chalk.white('failed')} ${chalk.gray('(10.00%)')}`,
);
expect(lines).toContain(
` ${chalk.red('✗')} ${chalk.white.bold('1')} ${chalk.white('error')} ${chalk.gray('(10.00%)')}`,
);
});
});
describe('guidance messages', () => {
it('should show guidance when writing to database without shareable URL', () => {
const params: EvalSummaryParams = {
evalId: 'eval-view',
isRedteam: false,
writeToDatabase: true,
shareableUrl: null,
wantsToShare: false,
hasExplicitDisable: false,
cloudEnabled: false,
tokenUsage: { total: 0 },
successes: 5,
failures: 0,
errors: 0,
duration: 5000,
maxConcurrency: 4,
tracker: mockTracker,
};
const lines = generateEvalSummary(params);
const output = stripAnsi(lines.join('\n'));
expect(output).toContain('» View results: promptfoo view');
expect(output).toContain('» Share with your team: https://promptfoo.app');
expect(output).toContain('» Feedback: https://promptfoo.dev/feedback');
});
it('should show share guidance with cloud enabled', () => {
const params: EvalSummaryParams = {
evalId: 'eval-share-cloud',
isRedteam: false,
writeToDatabase: true,
shareableUrl: null,
wantsToShare: false,
hasExplicitDisable: false,
cloudEnabled: true,
tokenUsage: { total: 0 },
successes: 5,
failures: 0,
errors: 0,
duration: 5000,
maxConcurrency: 4,
tracker: mockTracker,
};
const lines = generateEvalSummary(params);
const output = stripAnsi(lines.join('\n'));
expect(output).toContain('» View results: promptfoo view');
expect(output).toContain('» Create shareable URL: promptfoo share');
expect(output).not.toContain('https://promptfoo.app');
});
it('should NOT show share guidance when explicitly disabled (--no-share)', () => {
const params: EvalSummaryParams = {
evalId: 'eval-no-share',
isRedteam: false,
writeToDatabase: true,
shareableUrl: null,
wantsToShare: false,
hasExplicitDisable: true,
cloudEnabled: false,
tokenUsage: { total: 0 },
successes: 5,
failures: 0,
errors: 0,
duration: 5000,
maxConcurrency: 4,
tracker: mockTracker,
};
const lines = generateEvalSummary(params);
const output = stripAnsi(lines.join('\n'));
expect(output).toContain('» View results: promptfoo view');
expect(output).not.toContain('» Share with your team');
expect(output).not.toContain('» Create shareable URL');
});
it('should NOT show guidance when not writing to database', () => {
const params: EvalSummaryParams = {
evalId: 'eval-no-write',
isRedteam: false,
writeToDatabase: false,
shareableUrl: null,
wantsToShare: false,
hasExplicitDisable: false,
cloudEnabled: false,
tokenUsage: { total: 0 },
successes: 5,
failures: 0,
errors: 0,
duration: 5000,
maxConcurrency: 4,
tracker: mockTracker,
};
const lines = generateEvalSummary(params);
const output = stripAnsi(lines.join('\n'));
expect(output).not.toContain('» View results:');
expect(output).not.toContain('» Share');
expect(output).not.toContain('» Feedback:');
});
it('should NOT show guidance when shareable URL is present', () => {
const params: EvalSummaryParams = {
evalId: 'eval-with-url',
isRedteam: false,
writeToDatabase: true,
shareableUrl: 'https://promptfoo.app/eval/abc123',
wantsToShare: true,
hasExplicitDisable: false,
cloudEnabled: true,
tokenUsage: { total: 0 },
successes: 5,
failures: 0,
errors: 0,
duration: 5000,
maxConcurrency: 4,
tracker: mockTracker,
};
const lines = generateEvalSummary(params);
const output = stripAnsi(lines.join('\n'));
expect(output).not.toContain('» View results:');
expect(output).not.toContain('» Share');
expect(output).not.toContain('» Feedback:');
});
});
describe('performance metrics', () => {
it('should show duration and concurrency', () => {
const params: EvalSummaryParams = {
evalId: 'eval-perf',
isRedteam: false,
writeToDatabase: false,
shareableUrl: null,
wantsToShare: false,
hasExplicitDisable: false,
cloudEnabled: false,
tokenUsage: { total: 0 },
successes: 5,
failures: 0,
errors: 0,
duration: 125, // 125 seconds = 2m 5s
maxConcurrency: 8,
tracker: mockTracker,
};
const lines = generateEvalSummary(params);
const output = stripAnsi(lines.join('\n'));
expect(output).toContain('Duration:');
expect(output).toContain('(concurrency: 8)');
});
});
describe('edge cases', () => {
it('should use singular "error" when there is exactly 1 error', () => {
const params: EvalSummaryParams = {
evalId: 'eval-singular-error',
isRedteam: false,
writeToDatabase: false,
shareableUrl: null,
wantsToShare: false,
hasExplicitDisable: false,
cloudEnabled: false,
tokenUsage: { total: 0 },
successes: 5,
failures: 0,
errors: 1,
duration: 5000,
maxConcurrency: 4,
tracker: mockTracker,
};
const lines = generateEvalSummary(params);
const plainOutput = stripAnsi(lines.join('\n'));
expect(plainOutput).toContain('1 error');
expect(plainOutput).not.toContain('1 errors');
});
it('should use plural "errors" when there are multiple errors', () => {
const params: EvalSummaryParams = {
evalId: 'eval-plural-errors',
isRedteam: false,
writeToDatabase: false,
shareableUrl: null,
wantsToShare: false,
hasExplicitDisable: false,
cloudEnabled: false,
tokenUsage: { total: 0 },
successes: 5,
failures: 0,
errors: 3,
duration: 5000,
maxConcurrency: 4,
tracker: mockTracker,
};
const lines = generateEvalSummary(params);
const plainOutput = stripAnsi(lines.join('\n'));
expect(plainOutput).toContain('3 errors');
});
it('should use plural "errors" when there are 0 errors', () => {
const params: EvalSummaryParams = {
evalId: 'eval-zero-errors',
isRedteam: false,
writeToDatabase: false,
shareableUrl: null,
wantsToShare: false,
hasExplicitDisable: false,
cloudEnabled: false,
tokenUsage: { total: 0 },
successes: 5,
failures: 0,
errors: 0,
duration: 5000,
maxConcurrency: 4,
tracker: mockTracker,
};
const lines = generateEvalSummary(params);
const plainOutput = stripAnsi(lines.join('\n'));
expect(plainOutput).toContain('0 errors');
});
it('should handle provider returning undefined usage gracefully', () => {
mockTracker.getProviderIds.mockReturnValue([
'openai:gpt-4',
'missing-provider',
'anthropic:claude-3',
]);
mockTracker.getProviderUsage.mockImplementation((id: string) => {
if (id === 'openai:gpt-4') {
return {
total: 1000,
prompt: 400,
completion: 600,
numRequests: 5,
};
}
if (id === 'missing-provider') {
return undefined; // Simulates a provider that returns undefined
}
if (id === 'anthropic:claude-3') {
return {
total: 500,
prompt: 200,
completion: 300,
numRequests: 3,
};
}
return undefined;
});
const params: EvalSummaryParams = {
evalId: 'eval-undefined-provider',
isRedteam: false,
writeToDatabase: false,
shareableUrl: null,
wantsToShare: false,
hasExplicitDisable: false,
cloudEnabled: false,
tokenUsage: { total: 1500 },
successes: 8,
failures: 0,
errors: 0,
duration: 5000,
maxConcurrency: 4,
tracker: mockTracker,
};
// Should not throw
const lines = generateEvalSummary(params);
const output = stripAnsi(lines.join('\n'));
// Should show the providers that have valid usage
expect(output).toContain('Providers:');
expect(output).toContain('openai:gpt-4');
expect(output).toContain('anthropic:claude-3');
// Should NOT show the missing provider
expect(output).not.toContain('missing-provider');
});
});
});