Files
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

186 lines
5.1 KiB
TypeScript

import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { vi } from 'vitest';
import type { MockInstance } from 'vitest';
import type { ApiProvider, ProviderResponse } from '../../src/types/index';
/**
* Creates a deferred promise that can be resolved or rejected externally.
* Useful for controlling async flow in tests.
*/
export function createDeferred<T>(): {
promise: Promise<T>;
resolve: (value: T) => void;
reject: (reason?: unknown) => void;
} {
let resolve!: (value: T) => void;
let reject!: (reason?: unknown) => void;
const promise = new Promise<T>((promiseResolve, promiseReject) => {
resolve = promiseResolve;
reject = promiseReject;
});
return { promise, resolve, reject };
}
export class TestGrader implements ApiProvider {
async callApi(): Promise<ProviderResponse> {
return {
output: JSON.stringify({ pass: true, reason: 'Test grading output' }),
tokenUsage: { total: 10, prompt: 5, completion: 5 },
};
}
id(): string {
return 'TestGradingProvider';
}
}
export function createMockResponse(
options: {
ok?: boolean;
body?: any;
statusText?: string;
status?: number;
headers?: Headers;
text?: () => Promise<string>;
json?: () => Promise<any>;
} = { ok: true },
): Response {
const isOk = options.ok ?? (options.status ? options.status < 400 : true);
const mockResponse: Response = {
ok: isOk,
status: options.status || (isOk ? 200 : 400),
statusText: options.statusText || (isOk ? 'OK' : 'Bad Request'),
headers: options.headers || new Headers(),
redirected: false,
type: 'basic',
url: 'https://example.com',
json: options.json || (() => Promise.resolve(options.body || {})),
text: options.text || (() => Promise.resolve('')),
blob: () => Promise.resolve(new Blob([])),
arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)),
formData: () => Promise.resolve(new FormData()),
bodyUsed: false,
body: null,
clone() {
return createMockResponse(options);
},
} as Response;
return mockResponse;
}
export function stripAnsi(value: string): string {
return value.replace(/\u001b\[[0-9;]*m/g, '');
}
export type ConsoleMethod = 'debug' | 'error' | 'info' | 'log' | 'warn';
export function mockConsole(
method: ConsoleMethod,
implementation: (...args: unknown[]) => void = () => {},
): MockInstance {
return vi.spyOn(console, method).mockImplementation(implementation);
}
function replaceProcessEnv(nextEnv: Record<string, string | undefined>): void {
for (const key of Object.keys(process.env)) {
Reflect.deleteProperty(process.env, key);
}
Object.assign(process.env, nextEnv);
}
export function mockProcessEnv(
overrides: Record<string, string | undefined> = {},
options: { clear?: boolean } = {},
): () => void {
const originalEnv = { ...process.env };
if (options.clear) {
replaceProcessEnv({});
}
for (const [key, value] of Object.entries(overrides)) {
if (value === undefined) {
Reflect.deleteProperty(process.env, key);
} else {
Object.assign(process.env, { [key]: value });
}
}
return () => {
replaceProcessEnv(originalEnv);
};
}
export const PROXY_ENV_KEYS = [
'HTTP_PROXY',
'http_proxy',
'HTTPS_PROXY',
'https_proxy',
'ALL_PROXY',
'all_proxy',
'NO_PROXY',
'no_proxy',
'NPM_CONFIG_PROXY',
'NPM_CONFIG_HTTP_PROXY',
'NPM_CONFIG_HTTPS_PROXY',
'npm_config_proxy',
'npm_config_http_proxy',
'npm_config_https_proxy',
] as const;
export function clearProxyEnv(): void {
for (const key of PROXY_ENV_KEYS) {
Reflect.deleteProperty(process.env, key);
}
}
export function mockGlobal<T>(name: string, value: T): () => void {
const descriptor = Object.getOwnPropertyDescriptor(globalThis, name);
Object.defineProperty(globalThis, name, {
configurable: true,
value,
writable: true,
});
return () => {
if (descriptor) {
Object.defineProperty(globalThis, name, descriptor);
} else {
Reflect.deleteProperty(globalThis, name);
}
};
}
/**
* Creates a unique temporary directory in the operating system temp location.
*
* @param prefix - Optional directory name prefix. Defaults to `promptfoo-test-`.
* @returns The absolute path to the newly created temporary directory.
*/
export function createTempDir(prefix = 'promptfoo-test-'): string {
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
}
/**
* Removes a temporary directory created during tests.
*
* If `tempDir` is undefined, this function is a no-op. Otherwise, it retries recursive removal
* briefly to tolerate transient file-handle contention on Windows.
*
* @param tempDir - Absolute or relative path to the temporary directory to remove.
*/
export function removeTempDir(tempDir: string | undefined): void {
if (!tempDir) {
return;
}
// On Windows, file handles can linger briefly after a stream is closed, so an
// immediate recursive delete may throw ENOTEMPTY/EBUSY/EPERM. Retry with a short
// backoff (a no-op on POSIX, where these errors don't occur).
fs.rmSync(tempDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
}