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
143 lines
4.4 KiB
TypeScript
143 lines
4.4 KiB
TypeScript
import { vi } from 'vitest';
|
|
|
|
import type { ApiProvider, ProviderResponse, TokenUsage } from '../../src/types/index';
|
|
|
|
export type MockApiProvider = ApiProvider & {
|
|
id: ReturnType<typeof vi.fn<() => string>>;
|
|
callApi: ReturnType<typeof vi.fn<ApiProvider['callApi']>>;
|
|
cleanup?: ReturnType<typeof vi.fn<NonNullable<ApiProvider['cleanup']>>>;
|
|
};
|
|
|
|
export type MockProviderOptions = Omit<Partial<ApiProvider>, 'callApi' | 'cleanup' | 'id'> & {
|
|
callApi?: ApiProvider['callApi'];
|
|
cleanup?: ApiProvider['cleanup'] | boolean;
|
|
id?: string | (() => string);
|
|
response?: ProviderResponse;
|
|
};
|
|
|
|
/**
|
|
* Shallow-replace the default scaffolding with the caller's overrides. Unlike
|
|
* createRequiredTokenUsage below, this helper does NOT deep-merge nested fields
|
|
* because TokenUsage leaves completionDetails/assertions optional — if a caller
|
|
* passes either, they are declaring the whole shape they want to assert against.
|
|
* Tests in test/redteam/providers/iterativeTree.test.ts rely on this behavior
|
|
* to check exact tokenUsage propagation, so preserve it.
|
|
*/
|
|
export function createTokenUsage(overrides: TokenUsage = {}): TokenUsage {
|
|
return {
|
|
total: 10,
|
|
prompt: 5,
|
|
completion: 5,
|
|
cached: 0,
|
|
numRequests: 1,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Returns a Required<TokenUsage> with every nested field populated. Deep-merges
|
|
* completionDetails and assertions because Required<> forces every field to be
|
|
* present — partial overrides for those nested bags would otherwise drop
|
|
* defaults the caller didn't mention. This is the right behavior for fixtures
|
|
* flowing into type-strict slots (e.g. EvaluateResult.tokenUsage).
|
|
*/
|
|
export function createRequiredTokenUsage(overrides: TokenUsage = {}): Required<TokenUsage> {
|
|
return {
|
|
total: 10,
|
|
prompt: 5,
|
|
completion: 5,
|
|
cached: 0,
|
|
numRequests: 1,
|
|
...overrides,
|
|
completionDetails: {
|
|
reasoning: 0,
|
|
acceptedPrediction: 0,
|
|
rejectedPrediction: 0,
|
|
cacheReadInputTokens: 0,
|
|
cacheCreationInputTokens: 0,
|
|
...overrides.completionDetails,
|
|
},
|
|
assertions: {
|
|
total: 0,
|
|
prompt: 0,
|
|
completion: 0,
|
|
cached: 0,
|
|
numRequests: 0,
|
|
...overrides.assertions,
|
|
completionDetails: {
|
|
reasoning: 0,
|
|
acceptedPrediction: 0,
|
|
rejectedPrediction: 0,
|
|
cacheReadInputTokens: 0,
|
|
cacheCreationInputTokens: 0,
|
|
...overrides.assertions?.completionDetails,
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Returns a default ProviderResponse. tokenUsage follows the shallow-replace
|
|
* convention from createTokenUsage: callers passing { tokenUsage: {...} } are
|
|
* declaring the exact shape they want to assert on, so we do not deep-merge
|
|
* the default scaffolding into it. If you need defaults preserved, wrap the
|
|
* partial in createTokenUsage(...) at the call site.
|
|
*/
|
|
export function createProviderResponse(
|
|
overrides: Partial<ProviderResponse> = {},
|
|
): ProviderResponse {
|
|
return {
|
|
output: 'Test output',
|
|
tokenUsage: createTokenUsage(),
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function resolveIdImpl(id: string | (() => string) = 'test-provider'): () => string {
|
|
return typeof id === 'function' ? id : () => id;
|
|
}
|
|
|
|
export function createMockProvider(options: MockProviderOptions = {}): MockApiProvider {
|
|
const {
|
|
callApi,
|
|
cleanup,
|
|
id,
|
|
response = createProviderResponse(),
|
|
...providerOverrides
|
|
} = options;
|
|
|
|
const provider: MockApiProvider = {
|
|
id: vi.fn<() => string>().mockImplementation(resolveIdImpl(id)),
|
|
callApi: vi.fn<ApiProvider['callApi']>().mockImplementation(callApi ?? (async () => response)),
|
|
...providerOverrides,
|
|
};
|
|
|
|
if (cleanup) {
|
|
provider.cleanup = vi
|
|
.fn<NonNullable<ApiProvider['cleanup']>>()
|
|
.mockImplementation(cleanup === true ? async () => undefined : cleanup);
|
|
}
|
|
|
|
return provider;
|
|
}
|
|
|
|
export type ResetMockProviderOptions = {
|
|
id?: string | (() => string);
|
|
response?: ProviderResponse;
|
|
cleanup?: ApiProvider['cleanup'] | boolean;
|
|
};
|
|
|
|
export function resetMockProvider(
|
|
provider: MockApiProvider,
|
|
options: ResetMockProviderOptions = {},
|
|
): void {
|
|
const { id, response = createProviderResponse(), cleanup } = options;
|
|
provider.id.mockReset().mockImplementation(resolveIdImpl(id));
|
|
provider.callApi.mockReset().mockResolvedValue(response);
|
|
if (provider.cleanup) {
|
|
provider.cleanup
|
|
.mockReset()
|
|
.mockImplementation(!cleanup || cleanup === true ? async () => undefined : cleanup);
|
|
}
|
|
}
|