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

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
+133
View File
@@ -0,0 +1,133 @@
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import { setDownloadHeaders } from '../../../src/server/utils/downloadHelpers';
import type { Response } from 'express';
describe('downloadHelpers', () => {
describe('setDownloadHeaders', () => {
let mockRes: Partial<Response>;
let setHeaderMock: Mock;
beforeEach(() => {
setHeaderMock = vi.fn().mockReturnThis();
mockRes = {
setHeader: setHeaderMock,
} as Partial<Response>;
});
it('should set correct headers for CSV download', () => {
setDownloadHeaders(mockRes as Response, 'test-file.csv', 'text/csv');
expect(setHeaderMock).toHaveBeenCalledWith('Content-Type', 'text/csv');
expect(setHeaderMock).toHaveBeenCalledWith(
'Content-Disposition',
'attachment; filename="test-file.csv"',
);
expect(setHeaderMock).toHaveBeenCalledWith(
'Cache-Control',
'no-cache, no-store, must-revalidate',
);
expect(setHeaderMock).toHaveBeenCalledWith('Pragma', 'no-cache');
expect(setHeaderMock).toHaveBeenCalledWith('Expires', '0');
});
it('should set correct headers for JSON download', () => {
setDownloadHeaders(mockRes as Response, 'data.json', 'application/json');
expect(setHeaderMock).toHaveBeenCalledWith('Content-Type', 'application/json');
expect(setHeaderMock).toHaveBeenCalledWith(
'Content-Disposition',
'attachment; filename="data.json"',
);
});
it('should handle special characters in filename', () => {
setDownloadHeaders(mockRes as Response, 'file with spaces & special.csv', 'text/csv');
expect(setHeaderMock).toHaveBeenCalledWith(
'Content-Disposition',
'attachment; filename="file with spaces & special.csv"',
);
});
it('should handle very long filenames', () => {
const longFileName = 'a'.repeat(255) + '.csv';
setDownloadHeaders(mockRes as Response, longFileName, 'text/csv');
expect(setHeaderMock).toHaveBeenCalledWith(
'Content-Disposition',
`attachment; filename="${longFileName}"`,
);
});
it('should handle Unicode characters in filename', () => {
setDownloadHeaders(mockRes as Response, '测试文件.csv', 'text/csv');
expect(setHeaderMock).toHaveBeenCalledWith(
'Content-Disposition',
'attachment; filename="测试文件.csv"',
);
});
it('should handle different content types', () => {
const testCases = [
{ fileName: 'file.pdf', contentType: 'application/pdf' },
{ fileName: 'file.xml', contentType: 'application/xml' },
{ fileName: 'file.txt', contentType: 'text/plain' },
{ fileName: 'file.yaml', contentType: 'application/x-yaml' },
];
testCases.forEach(({ fileName, contentType }) => {
setHeaderMock.mockClear();
setDownloadHeaders(mockRes as Response, fileName, contentType);
expect(setHeaderMock).toHaveBeenCalledWith('Content-Type', contentType);
expect(setHeaderMock).toHaveBeenCalledWith(
'Content-Disposition',
`attachment; filename="${fileName}"`,
);
});
});
it('should set all cache control headers', () => {
setDownloadHeaders(mockRes as Response, 'test.csv', 'text/csv');
// Verify all cache-preventing headers are set
const calls = setHeaderMock.mock.calls;
const headerMap = new Map(calls.map((call) => [call[0] as string, call[1]]));
expect(headerMap.get('Cache-Control')).toBe('no-cache, no-store, must-revalidate');
expect(headerMap.get('Pragma')).toBe('no-cache');
expect(headerMap.get('Expires')).toBe('0');
});
it('should be called exactly 5 times for all headers', () => {
setDownloadHeaders(mockRes as Response, 'test.csv', 'text/csv');
expect(setHeaderMock).toHaveBeenCalledTimes(5);
});
it('should handle empty filename', () => {
setDownloadHeaders(mockRes as Response, '', 'text/csv');
expect(setHeaderMock).toHaveBeenCalledWith('Content-Disposition', 'attachment; filename=""');
});
it('should handle filename with quotes', () => {
setDownloadHeaders(mockRes as Response, 'file"with"quotes.csv', 'text/csv');
expect(setHeaderMock).toHaveBeenCalledWith(
'Content-Disposition',
'attachment; filename="file"with"quotes.csv"',
);
});
it('should handle filename with path separators', () => {
setDownloadHeaders(mockRes as Response, 'path/to/file.csv', 'text/csv');
expect(setHeaderMock).toHaveBeenCalledWith(
'Content-Disposition',
'attachment; filename="path/to/file.csv"',
);
});
});
});
+289
View File
@@ -0,0 +1,289 @@
import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from 'vitest';
import { z } from 'zod';
import logger from '../../../src/logger';
import { replyValidationError, sendError } from '../../../src/server/utils/errors';
import type { Response } from 'express';
vi.mock('../../../src/logger', () => ({
default: {
error: vi.fn(),
warn: vi.fn(),
info: vi.fn(),
debug: vi.fn(),
},
}));
function createResponseMock(): {
res: Response;
status: Mock;
json: Mock;
} {
const json = vi.fn().mockReturnThis();
const status = vi.fn(() => ({ json }));
return {
res: { status, json } as unknown as Response,
status: status as unknown as Mock,
json,
};
}
describe('server/utils/errors', () => {
afterEach(() => {
vi.clearAllMocks();
});
describe('sendError', () => {
it('returns the public message and never leaks the internal error', () => {
const { res, status, json } = createResponseMock();
sendError(res, 500, 'Failed to do thing', new Error('secret db password leaked'));
expect(status).toHaveBeenCalledWith(500);
const body = json.mock.calls[0]?.[0];
expect(body).toEqual({ error: 'Failed to do thing' });
expect(JSON.stringify(body)).not.toContain('secret db password');
});
it('skips logging when no internal error is provided', () => {
const { res } = createResponseMock();
sendError(res, 404, 'Not found');
expect(logger.error).not.toHaveBeenCalled();
});
it('logs Error name, message, and stack as a structured object', () => {
const { res } = createResponseMock();
const err = new Error('boom');
err.stack = 'Error: boom\n at someFn';
sendError(res, 500, 'Failed', err);
expect(logger.error).toHaveBeenCalledTimes(1);
const [message, context] = (logger.error as unknown as Mock).mock.calls[0];
expect(message).toBe('Failed');
expect(context).toEqual({
error: {
name: 'Error',
message: 'boom',
stack: 'Error: boom\n at someFn',
},
});
});
it('preserves Error.cause when present, serialized to retain its stack', () => {
const { res } = createResponseMock();
const cause = new Error('underlying');
cause.stack = 'Error: underlying\n at someInnerFn';
const err = new Error('wrapper', { cause });
sendError(res, 500, 'Failed', err);
const [, context] = (logger.error as unknown as Mock).mock.calls[0];
const ctx = context as { error: { cause: { name: string; message: string; stack: string } } };
expect(ctx.error.cause.name).toBe('Error');
expect(ctx.error.cause.message).toBe('underlying');
// Stack must survive — serializing the raw Error reference would have
// dropped it via JSON.stringify, which is the regression this helper exists to prevent.
expect(ctx.error.cause.stack).toContain('someInnerFn');
});
it('passes non-Error values through verbatim', () => {
const { res } = createResponseMock();
sendError(res, 500, 'Failed', 'plain string error');
const [, context] = (logger.error as unknown as Mock).mock.calls[0];
expect(context).toEqual({ error: 'plain string error' });
});
it('logs `null` as a real (no-detail) failure rather than skipping', () => {
// Pin current behavior: the guard is `internalError !== undefined`, so
// `null` reaches the logger. This is intentional — a future refactor to
// `if (internalError)` would silently swallow falsy-but-real signals
// (e.g. `0`, `false`, `''`).
const { res } = createResponseMock();
sendError(res, 500, 'Failed', null);
expect(logger.error).toHaveBeenCalledTimes(1);
const [, context] = (logger.error as unknown as Mock).mock.calls[0];
expect(context).toEqual({ error: null });
});
it('preserves enumerable diagnostic fields on Error subclasses', () => {
// Node SystemError carries `code`/`errno`/`syscall`/`path` as enumerable
// own props; AWS SDK errors carry `$metadata`/`$fault`; fetch errors
// carry `code`/`errno`. Logging the explicit four fields without
// spreading would silently drop these — exactly the production-triage
// regression flagged by the Codex P2 reviewer on this PR.
const { res } = createResponseMock();
const sysErr = Object.assign(new Error('ENOENT: no such file or directory, open ...'), {
code: 'ENOENT',
errno: -2,
syscall: 'open',
path: '/missing/file',
});
sendError(res, 500, 'Failed', sysErr);
const [, context] = (logger.error as unknown as Mock).mock.calls[0];
const ctx = context as {
error: {
name: string;
message: string;
stack: string;
code: string;
errno: number;
syscall: string;
path: string;
};
};
expect(ctx.error.name).toBe('Error');
expect(ctx.error.code).toBe('ENOENT');
expect(ctx.error.errno).toBe(-2);
expect(ctx.error.syscall).toBe('open');
expect(ctx.error.path).toBe('/missing/file');
// Standard fields still win even though the subclass also has its own.
expect(typeof ctx.error.stack).toBe('string');
});
it('preserves Error subclass name (TypeError, custom subclasses)', () => {
const { res } = createResponseMock();
class CustomError extends Error {
constructor(message: string) {
super(message);
this.name = 'CustomError';
}
}
sendError(res, 500, 'Failed', new TypeError('not a string'));
sendError(res, 500, 'Failed', new CustomError('bespoke'));
const calls = (logger.error as unknown as Mock).mock.calls;
expect((calls[0][1] as { error: { name: string } }).error.name).toBe('TypeError');
expect((calls[1][1] as { error: { name: string } }).error.name).toBe('CustomError');
});
it('recursively unwraps Error.cause chains so nested stacks survive serialization', () => {
const { res } = createResponseMock();
const root = new Error('root cause');
root.stack = 'Error: root cause\n at deep';
const middle = new Error('middle', { cause: root });
middle.stack = 'Error: middle\n at middle';
const top = new Error('top', { cause: middle });
top.stack = 'Error: top\n at top';
sendError(res, 500, 'Failed', top);
const [, context] = (logger.error as unknown as Mock).mock.calls[0];
const ctx = context as {
error: {
stack: string;
cause: { stack: string; cause: { stack: string } };
};
};
expect(ctx.error.stack).toContain('top');
expect(ctx.error.cause.stack).toContain('middle');
expect(ctx.error.cause.cause.stack).toContain('deep');
});
it('does not crash on a circular Error.cause', () => {
const { res, status } = createResponseMock();
const cyclic = new Error('cyclic') as Error & { cause?: unknown };
cyclic.cause = cyclic;
expect(() => sendError(res, 500, 'Failed', cyclic)).not.toThrow();
// The response must still ship — cycle detection in serializeError is
// the entire reason this canonical 500 funnel doesn't unhandled-throw.
expect(status).toHaveBeenCalledWith(500);
});
it('caps long Error.cause chains before serializing unbounded logger context', () => {
const { res } = createResponseMock();
const chain = Array.from({ length: 6 }, (_, index) => {
const err = new Error(`level-${index}`) as Error & { cause?: unknown };
err.stack = `Error: level-${index}\n at level${index}`;
return err;
});
for (let i = 0; i < chain.length - 1; i++) {
chain[i].cause = chain[i + 1];
}
sendError(res, 500, 'Failed', chain[0]);
const [, context] = (logger.error as unknown as Mock).mock.calls[0];
const cappedCause = (
context as {
error: { cause: { cause: { cause: { cause: unknown } } } };
}
).error.cause.cause.cause.cause;
expect(cappedCause).toEqual({ name: 'Error', message: 'level-4' });
expect(JSON.stringify(context)).not.toContain('level-5');
});
it('falls back to a hand-built envelope if ErrorResponseSchema.parse throws', async () => {
// Force the schema parse to throw, simulating a future tightening that
// rejects what `sendError` produces. Without the safeRespond fallback,
// the throw would escape the handler's try/catch and land in Express's
// default error path (empty 500 with no JSON body).
const common = await import('../../../src/types/api/common');
const parseSpy = vi.spyOn(common.ErrorResponseSchema, 'parse').mockImplementationOnce(() => {
throw new Error('forced schema rejection');
});
try {
const { res, status, json } = createResponseMock();
expect(() => sendError(res, 500, 'Public message')).not.toThrow();
expect(status).toHaveBeenCalledWith(500);
expect(json.mock.calls[0]?.[0]).toEqual({ error: 'Public message' });
expect(parseSpy).toHaveBeenCalledTimes(1);
} finally {
parseSpy.mockRestore();
}
});
});
describe('replyValidationError', () => {
let zodError: z.ZodError;
beforeEach(() => {
const schema = z.object({
name: z.string(),
age: z.number().int().nonnegative(),
});
const result = schema.safeParse({ name: 42, age: -1 });
if (result.success) {
throw new Error('expected parse to fail');
}
zodError = result.error;
});
it('returns a 400 with prettified message in `error`', () => {
const { res, status, json } = createResponseMock();
replyValidationError(res, zodError);
expect(status).toHaveBeenCalledWith(400);
const body = json.mock.calls[0]?.[0] as { error: string };
expect(typeof body.error).toBe('string');
expect(body.error.length).toBeGreaterThan(0);
});
it('exposes structured Zod issues in `details` for programmatic clients', () => {
const { res, json } = createResponseMock();
replyValidationError(res, zodError);
const body = json.mock.calls[0]?.[0] as {
details: { issues: z.core.$ZodIssue[] };
};
expect(Array.isArray(body.details.issues)).toBe(true);
expect(body.details.issues.length).toBe(zodError.issues.length);
const paths = body.details.issues.map((issue) => issue.path.join('.'));
expect(paths).toContain('name');
expect(paths).toContain('age');
});
});
});
+11
View File
@@ -0,0 +1,11 @@
import { describe, expect, it } from 'vitest';
import * as legacyEvalTableUtils from '../../../src/server/utils/evalTableUtils';
import * as evalTableUtils from '../../../src/util/eval/evalTableUtils';
describe('legacy evalTableUtils import', () => {
it('re-exports the node-layer implementation for enterprise compatibility', () => {
expect(legacyEvalTableUtils.evalTableToCsv).toBe(evalTableUtils.evalTableToCsv);
expect(legacyEvalTableUtils.streamEvalCsv).toBe(evalTableUtils.streamEvalCsv);
expect(legacyEvalTableUtils.mergeComparisonTables).toBe(evalTableUtils.mergeComparisonTables);
});
});