Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
175 lines
5.3 KiB
TypeScript
175 lines
5.3 KiB
TypeScript
/**
|
|
* Tests for CSV prompt loader.
|
|
*/
|
|
|
|
import * as fs from 'node:fs';
|
|
import * as os from 'node:os';
|
|
import * as path from 'node:path';
|
|
|
|
import { loadTestCasesFromCsv } from '../cli/csv-prompt-loader';
|
|
|
|
describe('csv-prompt-loader', () => {
|
|
const tempDirs: string[] = [];
|
|
|
|
function writeTempCsv(filename: string, content: string): string {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'eval-csv-'));
|
|
tempDirs.push(dir);
|
|
const csvPath = path.join(dir, filename);
|
|
fs.writeFileSync(csvPath, content, 'utf8');
|
|
return csvPath;
|
|
}
|
|
|
|
afterEach(() => {
|
|
while (tempDirs.length > 0) {
|
|
const dir = tempDirs.pop();
|
|
if (dir && fs.existsSync(dir)) {
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
}
|
|
});
|
|
|
|
it('should load dos/donts into context when present', () => {
|
|
const csvPath = writeTempCsv(
|
|
'pairwise.csv',
|
|
'id,prompt,dos,donts\npw-1,"Create a workflow","Must use Notion","No HTTP Request"\n',
|
|
);
|
|
|
|
const testCases = loadTestCasesFromCsv(csvPath);
|
|
expect(testCases).toEqual([
|
|
{
|
|
id: 'pw-1',
|
|
prompt: 'Create a workflow',
|
|
context: { dos: 'Must use Notion', donts: 'No HTTP Request' },
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('should load prompts without context when dos/donts are absent', () => {
|
|
const csvPath = writeTempCsv('llm.csv', 'id,prompt\nllm-1,"Create a workflow"\n');
|
|
|
|
const testCases = loadTestCasesFromCsv(csvPath);
|
|
expect(testCases).toEqual([{ id: 'llm-1', prompt: 'Create a workflow' }]);
|
|
});
|
|
|
|
it('should support header column re-ordering', () => {
|
|
const csvPath = writeTempCsv(
|
|
'reorder.csv',
|
|
'prompt,id,donts,dos\n"Create a workflow","pw-1","No HTTP Request","Must use Notion"\n',
|
|
);
|
|
|
|
const testCases = loadTestCasesFromCsv(csvPath);
|
|
expect(testCases).toEqual([
|
|
{
|
|
id: 'pw-1',
|
|
prompt: 'Create a workflow',
|
|
context: { dos: 'Must use Notion', donts: 'No HTTP Request' },
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('should support do/dont header aliases', () => {
|
|
const csvPath = writeTempCsv(
|
|
'aliases.csv',
|
|
'id,prompt,do,dont\npw-1,"Create a workflow","Must use Notion","No HTTP Request"\n',
|
|
);
|
|
|
|
const testCases = loadTestCasesFromCsv(csvPath);
|
|
expect(testCases).toEqual([
|
|
{
|
|
id: 'pw-1',
|
|
prompt: 'Create a workflow',
|
|
context: { dos: 'Must use Notion', donts: 'No HTTP Request' },
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('should not create context when dos/donts columns are present but empty', () => {
|
|
const csvPath = writeTempCsv(
|
|
'empty-context.csv',
|
|
'id,prompt,dos,donts\npw-1,"Create a workflow",,\n',
|
|
);
|
|
|
|
const testCases = loadTestCasesFromCsv(csvPath);
|
|
expect(testCases).toEqual([{ id: 'pw-1', prompt: 'Create a workflow' }]);
|
|
});
|
|
|
|
it('should allow headerless CSV (treat first column as prompt)', () => {
|
|
const csvPath = writeTempCsv('no-header.csv', '"Create a workflow"\n"Second prompt"\n');
|
|
|
|
const testCases = loadTestCasesFromCsv(csvPath);
|
|
expect(testCases).toEqual([
|
|
{ id: 'csv-case-1', prompt: 'Create a workflow' },
|
|
{ id: 'csv-case-2', prompt: 'Second prompt' },
|
|
]);
|
|
});
|
|
|
|
it('should ignore rows with empty prompts', () => {
|
|
const csvPath = writeTempCsv('empty-rows.csv', 'id,prompt\nrow-1,\nrow-2,"Valid prompt"\n');
|
|
|
|
const testCases = loadTestCasesFromCsv(csvPath);
|
|
expect(testCases).toEqual([{ id: 'row-2', prompt: 'Valid prompt' }]);
|
|
});
|
|
|
|
it('should handle UTF-8 BOM', () => {
|
|
const csvPath = writeTempCsv('bom.csv', '\ufeffid,prompt\nllm-1,"Create a workflow"\n');
|
|
|
|
const testCases = loadTestCasesFromCsv(csvPath);
|
|
expect(testCases).toEqual([{ id: 'llm-1', prompt: 'Create a workflow' }]);
|
|
});
|
|
|
|
it('should resolve relative paths from process.cwd()', () => {
|
|
const csvPath = writeTempCsv('relative.csv', 'id,prompt\nllm-1,"Create a workflow"\n');
|
|
const relativePath = path.relative(process.cwd(), csvPath);
|
|
|
|
const testCases = loadTestCasesFromCsv(relativePath);
|
|
expect(testCases).toEqual([{ id: 'llm-1', prompt: 'Create a workflow' }]);
|
|
});
|
|
|
|
it('should throw when file does not exist', () => {
|
|
expect(() => loadTestCasesFromCsv('/definitely-not-a-real-path.csv')).toThrow(
|
|
/CSV file not found/,
|
|
);
|
|
});
|
|
|
|
it('should throw when CSV is empty', () => {
|
|
const csvPath = writeTempCsv('empty.csv', '');
|
|
expect(() => loadTestCasesFromCsv(csvPath)).toThrow('The provided CSV file is empty');
|
|
});
|
|
|
|
it('should throw when no valid prompts exist', () => {
|
|
const csvPath = writeTempCsv('no-prompts.csv', 'id,prompt\nrow-1,\nrow-2," "\n');
|
|
expect(() => loadTestCasesFromCsv(csvPath)).toThrow(
|
|
'No valid prompts found in the provided CSV file',
|
|
);
|
|
});
|
|
|
|
it('should parse annotations column as JSON into context', () => {
|
|
const csvPath = writeTempCsv(
|
|
'annotations.csv',
|
|
'id,prompt,annotations\nann-1,"Create a workflow","{""code_necessary"":true}"\n',
|
|
);
|
|
|
|
const testCases = loadTestCasesFromCsv(csvPath);
|
|
expect(testCases).toEqual([
|
|
{
|
|
id: 'ann-1',
|
|
prompt: 'Create a workflow',
|
|
context: { annotations: { code_necessary: true } },
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('should skip invalid JSON in annotations column with warning', () => {
|
|
const warnSpy = jest.spyOn(console, 'warn').mockImplementation();
|
|
const csvPath = writeTempCsv(
|
|
'bad-annotations.csv',
|
|
'id,prompt,annotations\nbad-1,"Create a workflow","not-json"\n',
|
|
);
|
|
|
|
const testCases = loadTestCasesFromCsv(csvPath);
|
|
expect(testCases).toEqual([{ id: 'bad-1', prompt: 'Create a workflow', context: {} }]);
|
|
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('invalid JSON'));
|
|
warnSpy.mockRestore();
|
|
});
|
|
});
|