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
134 lines
4.6 KiB
TypeScript
134 lines
4.6 KiB
TypeScript
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"',
|
|
);
|
|
});
|
|
});
|
|
});
|