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
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:
@@ -0,0 +1,627 @@
|
||||
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { HUMAN_ASSERTION_TYPE } from '../../src/constants';
|
||||
import { getDb } from '../../src/database/index';
|
||||
import { runDbMigrations } from '../../src/migrate';
|
||||
import { queryTestIndicesOptimized } from '../../src/models/evalPerformance';
|
||||
import { ResultFailureReason } from '../../src/types/index';
|
||||
import EvalFactory from '../factories/evalFactory';
|
||||
|
||||
import type { EvaluateResult, GradingResult } from '../../src/types/index';
|
||||
|
||||
/**
|
||||
* Helper to create a GradingResult with a human rating assertion.
|
||||
*/
|
||||
function createHumanRatedGradingResult(
|
||||
pass: boolean,
|
||||
score: number,
|
||||
reason: string,
|
||||
): GradingResult {
|
||||
return {
|
||||
pass,
|
||||
score,
|
||||
reason,
|
||||
namedScores: {},
|
||||
tokensUsed: { total: 10, prompt: 5, completion: 5 },
|
||||
componentResults: [
|
||||
{
|
||||
assertion: {
|
||||
type: HUMAN_ASSERTION_TYPE,
|
||||
},
|
||||
pass,
|
||||
score,
|
||||
reason,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create a regular (non-human-rated) GradingResult.
|
||||
*/
|
||||
function createRegularGradingResult(pass: boolean, score: number): GradingResult {
|
||||
return {
|
||||
pass,
|
||||
score,
|
||||
reason: pass ? 'Test passed' : 'Test failed',
|
||||
namedScores: {},
|
||||
tokensUsed: { total: 10, prompt: 5, completion: 5 },
|
||||
componentResults: [
|
||||
{
|
||||
assertion: {
|
||||
type: 'equals',
|
||||
},
|
||||
pass,
|
||||
score,
|
||||
reason: pass ? 'Values match' : 'Values differ',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
describe('User-Rated Filter Feature', () => {
|
||||
beforeAll(async () => {
|
||||
await runDbMigrations();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
// Clear all tables before each test
|
||||
const db = await getDb();
|
||||
await db.run('DELETE FROM eval_results');
|
||||
await db.run('DELETE FROM evals_to_datasets');
|
||||
await db.run('DELETE FROM evals_to_prompts');
|
||||
await db.run('DELETE FROM evals_to_tags');
|
||||
await db.run('DELETE FROM evals');
|
||||
});
|
||||
|
||||
describe('Eval.queryTestIndices user-rated filter', () => {
|
||||
it('should return only user-rated results when filterMode is user-rated', async () => {
|
||||
const eval_ = await EvalFactory.create({ numResults: 0 });
|
||||
|
||||
// Add results with various user rating scenarios
|
||||
const results = [
|
||||
{ testIdx: 0, hasHumanRating: true, pass: true },
|
||||
{ testIdx: 1, hasHumanRating: false, pass: true },
|
||||
{ testIdx: 2, hasHumanRating: true, pass: false },
|
||||
{ testIdx: 3, hasHumanRating: false, pass: false },
|
||||
{ testIdx: 4, hasHumanRating: true, pass: true },
|
||||
];
|
||||
|
||||
for (const result of results) {
|
||||
await eval_.addResult({
|
||||
description: `test-${result.testIdx}`,
|
||||
promptIdx: 0,
|
||||
testIdx: result.testIdx,
|
||||
testCase: { vars: { test: `value${result.testIdx}` } },
|
||||
promptId: 'test-prompt',
|
||||
provider: { id: 'test-provider', label: 'test-label' },
|
||||
prompt: { raw: 'Test prompt', label: 'Test prompt' },
|
||||
vars: { test: `value${result.testIdx}` },
|
||||
response: { output: `Response ${result.testIdx}` },
|
||||
error: null,
|
||||
failureReason: result.pass ? ResultFailureReason.NONE : ResultFailureReason.ASSERT,
|
||||
success: result.pass,
|
||||
score: result.pass ? 1 : 0,
|
||||
latencyMs: 100,
|
||||
gradingResult: result.hasHumanRating
|
||||
? createHumanRatedGradingResult(result.pass, result.pass ? 1 : 0, 'User rated')
|
||||
: createRegularGradingResult(result.pass, result.pass ? 1 : 0),
|
||||
namedScores: {},
|
||||
cost: 0.007,
|
||||
metadata: {},
|
||||
} as EvaluateResult);
|
||||
}
|
||||
|
||||
// Test user-rated filter
|
||||
const { testIndices, filteredCount } = await (eval_ as any).queryTestIndices({
|
||||
filterMode: 'user-rated',
|
||||
});
|
||||
|
||||
// Should return only test indices 0, 2, and 4 (those with human ratings)
|
||||
expect(filteredCount).toBe(3);
|
||||
expect(testIndices).toEqual([0, 2, 4]);
|
||||
});
|
||||
|
||||
it('should handle empty dataset with user-rated filter', async () => {
|
||||
const eval_ = await EvalFactory.create({ numResults: 0 });
|
||||
|
||||
const { testIndices, filteredCount } = await (eval_ as any).queryTestIndices({
|
||||
filterMode: 'user-rated',
|
||||
});
|
||||
|
||||
expect(filteredCount).toBe(0);
|
||||
expect(testIndices).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle dataset with no user-rated results', async () => {
|
||||
const eval_ = await EvalFactory.create({
|
||||
numResults: 5,
|
||||
resultTypes: ['success', 'failure'],
|
||||
});
|
||||
|
||||
const { testIndices, filteredCount } = await (eval_ as any).queryTestIndices({
|
||||
filterMode: 'user-rated',
|
||||
});
|
||||
|
||||
expect(filteredCount).toBe(0);
|
||||
expect(testIndices).toEqual([]);
|
||||
});
|
||||
|
||||
it('should work with pagination when filtering user-rated', async () => {
|
||||
const eval_ = await EvalFactory.create({ numResults: 0 });
|
||||
|
||||
// Add 10 user-rated results
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await eval_.addResult({
|
||||
description: `test-${i}`,
|
||||
promptIdx: 0,
|
||||
testIdx: i,
|
||||
testCase: { vars: { test: `value${i}` } },
|
||||
promptId: 'test-prompt',
|
||||
provider: { id: 'test-provider', label: 'test-label' },
|
||||
prompt: { raw: 'Test prompt', label: 'Test prompt' },
|
||||
vars: { test: `value${i}` },
|
||||
response: { output: `Response ${i}` },
|
||||
error: null,
|
||||
failureReason: ResultFailureReason.NONE,
|
||||
success: true,
|
||||
score: 1,
|
||||
latencyMs: 100,
|
||||
gradingResult: createHumanRatedGradingResult(true, 1, `User rated item ${i}`),
|
||||
namedScores: {},
|
||||
cost: 0.007,
|
||||
metadata: {},
|
||||
} as EvaluateResult);
|
||||
}
|
||||
|
||||
// Test pagination
|
||||
const page1 = await (eval_ as any).queryTestIndices({
|
||||
filterMode: 'user-rated',
|
||||
offset: 0,
|
||||
limit: 5,
|
||||
});
|
||||
|
||||
const page2 = await (eval_ as any).queryTestIndices({
|
||||
filterMode: 'user-rated',
|
||||
offset: 5,
|
||||
limit: 5,
|
||||
});
|
||||
|
||||
expect(page1.filteredCount).toBe(10);
|
||||
expect(page1.testIndices).toEqual([0, 1, 2, 3, 4]);
|
||||
|
||||
expect(page2.filteredCount).toBe(10);
|
||||
expect(page2.testIndices).toEqual([5, 6, 7, 8, 9]);
|
||||
});
|
||||
|
||||
it('should combine user-rated filter with search query', async () => {
|
||||
const eval_ = await EvalFactory.create({ numResults: 0 });
|
||||
|
||||
const testData = [
|
||||
{ testIdx: 0, hasHumanRating: true, output: 'Response with needle' },
|
||||
{ testIdx: 1, hasHumanRating: true, output: 'Different response' },
|
||||
{ testIdx: 2, hasHumanRating: false, output: 'Contains needle' },
|
||||
{ testIdx: 3, hasHumanRating: true, output: 'Another needle' },
|
||||
];
|
||||
|
||||
for (const data of testData) {
|
||||
await eval_.addResult({
|
||||
description: `test-${data.testIdx}`,
|
||||
promptIdx: 0,
|
||||
testIdx: data.testIdx,
|
||||
testCase: { vars: { test: `value${data.testIdx}` } },
|
||||
promptId: 'test-prompt',
|
||||
provider: { id: 'test-provider', label: 'test-label' },
|
||||
prompt: { raw: 'Test prompt', label: 'Test prompt' },
|
||||
vars: { test: `value${data.testIdx}` },
|
||||
response: { output: data.output },
|
||||
error: null,
|
||||
failureReason: ResultFailureReason.NONE,
|
||||
success: true,
|
||||
score: 1,
|
||||
latencyMs: 100,
|
||||
gradingResult: data.hasHumanRating
|
||||
? createHumanRatedGradingResult(true, 1, 'User rated')
|
||||
: createRegularGradingResult(true, 1),
|
||||
namedScores: {},
|
||||
cost: 0.007,
|
||||
metadata: {},
|
||||
} as EvaluateResult);
|
||||
}
|
||||
|
||||
// Search for "needle" with user-rated filter
|
||||
const { testIndices, filteredCount } = await (eval_ as any).queryTestIndices({
|
||||
filterMode: 'user-rated',
|
||||
searchQuery: 'needle',
|
||||
});
|
||||
|
||||
// Should return only user-rated results that also contain "needle"
|
||||
expect(filteredCount).toBe(2);
|
||||
expect(testIndices).toEqual([0, 3]);
|
||||
});
|
||||
|
||||
it('should handle malformed grading results gracefully', async () => {
|
||||
const eval_ = await EvalFactory.create({ numResults: 0 });
|
||||
const db = await getDb();
|
||||
|
||||
// Insert a result with null grading_result
|
||||
await db.run(`
|
||||
INSERT INTO eval_results (
|
||||
id, eval_id, prompt_idx, test_idx, test_case, prompt, provider,
|
||||
success, score, grading_result
|
||||
) VALUES (
|
||||
'test1', '${eval_.id}', 0, 0, '{}', '{}', '{}', 1, 1.0, NULL
|
||||
)
|
||||
`);
|
||||
|
||||
// Insert a result with empty componentResults
|
||||
await db.run(`
|
||||
INSERT INTO eval_results (
|
||||
id, eval_id, prompt_idx, test_idx, test_case, prompt, provider,
|
||||
success, score, grading_result
|
||||
) VALUES (
|
||||
'test2', '${eval_.id}', 0, 1, '{}', '{}', '{}', 1, 1.0,
|
||||
'{"pass": true, "score": 1, "componentResults": []}'
|
||||
)
|
||||
`);
|
||||
|
||||
// Insert a valid user-rated result
|
||||
await db.run(`
|
||||
INSERT INTO eval_results (
|
||||
id, eval_id, prompt_idx, test_idx, test_case, prompt, provider,
|
||||
success, score, grading_result
|
||||
) VALUES (
|
||||
'test3', '${eval_.id}', 0, 2, '{}', '{}', '{}', 1, 1.0,
|
||||
'{"pass": true, "score": 1, "componentResults": [{"assertion": {"type": "human"}, "pass": true, "score": 1, "reason": "Good"}]}'
|
||||
)
|
||||
`);
|
||||
|
||||
const { testIndices, filteredCount } = await (eval_ as any).queryTestIndices({
|
||||
filterMode: 'user-rated',
|
||||
});
|
||||
|
||||
// Should only return the valid user-rated result
|
||||
expect(filteredCount).toBe(1);
|
||||
expect(testIndices).toEqual([2]);
|
||||
});
|
||||
|
||||
it('should distinguish human assertions from other assertion types', async () => {
|
||||
const eval_ = await EvalFactory.create({ numResults: 0 });
|
||||
const db = await getDb();
|
||||
|
||||
// Result with 'human' assertion type (should match)
|
||||
await db.run(`
|
||||
INSERT INTO eval_results (
|
||||
id, eval_id, prompt_idx, test_idx, test_case, prompt, provider,
|
||||
success, score, grading_result
|
||||
) VALUES (
|
||||
'test1', '${eval_.id}', 0, 0, '{}', '{}', '{}', 1, 1.0,
|
||||
'{"pass": true, "score": 1, "componentResults": [{"assertion": {"type": "human"}, "pass": true, "score": 1}]}'
|
||||
)
|
||||
`);
|
||||
|
||||
// Result with 'equals' assertion type (should NOT match)
|
||||
await db.run(`
|
||||
INSERT INTO eval_results (
|
||||
id, eval_id, prompt_idx, test_idx, test_case, prompt, provider,
|
||||
success, score, grading_result
|
||||
) VALUES (
|
||||
'test2', '${eval_.id}', 0, 1, '{}', '{}', '{}', 1, 1.0,
|
||||
'{"pass": true, "score": 1, "componentResults": [{"assertion": {"type": "equals"}, "pass": true, "score": 1}]}'
|
||||
)
|
||||
`);
|
||||
|
||||
// Result with 'humanoid' assertion type (should NOT match - avoid false positives)
|
||||
await db.run(`
|
||||
INSERT INTO eval_results (
|
||||
id, eval_id, prompt_idx, test_idx, test_case, prompt, provider,
|
||||
success, score, grading_result
|
||||
) VALUES (
|
||||
'test3', '${eval_.id}', 0, 2, '{}', '{}', '{}', 1, 1.0,
|
||||
'{"pass": true, "score": 1, "componentResults": [{"assertion": {"type": "humanoid"}, "pass": true, "score": 1}]}'
|
||||
)
|
||||
`);
|
||||
|
||||
// Result with 'is-human' assertion type (should NOT match)
|
||||
await db.run(`
|
||||
INSERT INTO eval_results (
|
||||
id, eval_id, prompt_idx, test_idx, test_case, prompt, provider,
|
||||
success, score, grading_result
|
||||
) VALUES (
|
||||
'test4', '${eval_.id}', 0, 3, '{}', '{}', '{}', 1, 1.0,
|
||||
'{"pass": true, "score": 1, "componentResults": [{"assertion": {"type": "is-human"}, "pass": true, "score": 1}]}'
|
||||
)
|
||||
`);
|
||||
|
||||
const { testIndices, filteredCount } = await (eval_ as any).queryTestIndices({
|
||||
filterMode: 'user-rated',
|
||||
});
|
||||
|
||||
// Should only return test index 0 (the one with exactly 'human' assertion type)
|
||||
expect(filteredCount).toBe(1);
|
||||
expect(testIndices).toEqual([0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('evalPerformance.queryTestIndicesOptimized user-rated filter', () => {
|
||||
it('should return only user-rated results with optimized query', async () => {
|
||||
const eval_ = await EvalFactory.create({ numResults: 0 });
|
||||
|
||||
// Add test data
|
||||
const results = [
|
||||
{ testIdx: 0, hasHumanRating: true },
|
||||
{ testIdx: 1, hasHumanRating: false },
|
||||
{ testIdx: 2, hasHumanRating: true },
|
||||
];
|
||||
|
||||
for (const result of results) {
|
||||
await eval_.addResult({
|
||||
description: `test-${result.testIdx}`,
|
||||
promptIdx: 0,
|
||||
testIdx: result.testIdx,
|
||||
testCase: { vars: { test: `value${result.testIdx}` } },
|
||||
promptId: 'test-prompt',
|
||||
provider: { id: 'test-provider', label: 'test-label' },
|
||||
prompt: { raw: 'Test prompt', label: 'Test prompt' },
|
||||
vars: { test: `value${result.testIdx}` },
|
||||
response: { output: `Response ${result.testIdx}` },
|
||||
error: null,
|
||||
failureReason: ResultFailureReason.NONE,
|
||||
success: true,
|
||||
score: 1,
|
||||
latencyMs: 100,
|
||||
gradingResult: result.hasHumanRating
|
||||
? createHumanRatedGradingResult(true, 1, 'User rated')
|
||||
: createRegularGradingResult(true, 1),
|
||||
namedScores: {},
|
||||
cost: 0.007,
|
||||
metadata: {},
|
||||
} as EvaluateResult);
|
||||
}
|
||||
|
||||
const { testIndices, filteredCount } = await queryTestIndicesOptimized(eval_.id, {
|
||||
filterMode: 'user-rated',
|
||||
});
|
||||
|
||||
expect(filteredCount).toBe(2);
|
||||
expect(testIndices).toEqual([0, 2]);
|
||||
});
|
||||
|
||||
it('should handle large datasets efficiently', async () => {
|
||||
const eval_ = await EvalFactory.create({ numResults: 0 });
|
||||
|
||||
// Add 100 results, every 5th one is user-rated
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const hasHumanRating = i % 5 === 0;
|
||||
await eval_.addResult({
|
||||
description: `test-${i}`,
|
||||
promptIdx: 0,
|
||||
testIdx: i,
|
||||
testCase: { vars: { test: `value${i}` } },
|
||||
promptId: 'test-prompt',
|
||||
provider: { id: 'test-provider', label: 'test-label' },
|
||||
prompt: { raw: 'Test prompt', label: 'Test prompt' },
|
||||
vars: { test: `value${i}` },
|
||||
response: { output: `Response ${i}` },
|
||||
error: null,
|
||||
failureReason: ResultFailureReason.NONE,
|
||||
success: true,
|
||||
score: 1,
|
||||
latencyMs: 100,
|
||||
gradingResult: hasHumanRating
|
||||
? createHumanRatedGradingResult(true, 1, `User rated ${i}`)
|
||||
: createRegularGradingResult(true, 1),
|
||||
namedScores: {},
|
||||
cost: 0.007,
|
||||
metadata: {},
|
||||
} as EvaluateResult);
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
const { testIndices, filteredCount } = await queryTestIndicesOptimized(eval_.id, {
|
||||
filterMode: 'user-rated',
|
||||
limit: 10,
|
||||
});
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
// Should have 20 user-rated items (0, 5, 10, 15, ...)
|
||||
expect(filteredCount).toBe(20);
|
||||
expect(testIndices).toEqual([0, 5, 10, 15, 20, 25, 30, 35, 40, 45]);
|
||||
|
||||
// Query should be fast (typically under 100ms for 100 items)
|
||||
expect(duration).toBeLessThan(500);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Integration with getTablePage', () => {
|
||||
it('should filter user-rated correctly through getTablePage', async () => {
|
||||
const eval_ = await EvalFactory.create({ numResults: 0 });
|
||||
|
||||
// Add mixed results
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const hasHumanRating = i % 2 === 0;
|
||||
await eval_.addResult({
|
||||
description: `test-${i}`,
|
||||
promptIdx: 0,
|
||||
testIdx: i,
|
||||
testCase: { vars: { test: `value${i}` } },
|
||||
promptId: 'test-prompt',
|
||||
provider: { id: 'test-provider', label: 'test-label' },
|
||||
prompt: { raw: 'Test prompt', label: 'Test prompt' },
|
||||
vars: { test: `value${i}` },
|
||||
response: { output: `Response ${i}` },
|
||||
error: null,
|
||||
failureReason: ResultFailureReason.NONE,
|
||||
success: true,
|
||||
score: 1,
|
||||
latencyMs: 100,
|
||||
gradingResult: hasHumanRating
|
||||
? createHumanRatedGradingResult(true, 1, `User rated ${i}`)
|
||||
: createRegularGradingResult(true, 1),
|
||||
namedScores: {},
|
||||
cost: 0.007,
|
||||
metadata: {},
|
||||
} as EvaluateResult);
|
||||
}
|
||||
|
||||
const result = await eval_.getTablePage({
|
||||
filterMode: 'user-rated',
|
||||
filters: [],
|
||||
});
|
||||
|
||||
// Should have 3 user-rated results (0, 2, 4)
|
||||
expect(result.filteredCount).toBe(3);
|
||||
expect(result.body.length).toBe(3);
|
||||
|
||||
// Verify the test indices in the body
|
||||
const testIndices = result.body.map((row) => row.testIdx);
|
||||
expect(testIndices).toEqual([0, 2, 4]);
|
||||
|
||||
// Verify each result has a human assertion in componentResults
|
||||
for (const row of result.body) {
|
||||
const hasHumanRating = row.outputs.some((output) =>
|
||||
output.gradingResult?.componentResults?.some(
|
||||
(cr) => cr.assertion?.type === HUMAN_ASSERTION_TYPE,
|
||||
),
|
||||
);
|
||||
expect(hasHumanRating).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge cases and error handling', () => {
|
||||
it('should handle results with null componentResults', async () => {
|
||||
const eval_ = await EvalFactory.create({ numResults: 0 });
|
||||
const db = await getDb();
|
||||
|
||||
// Insert result with grading_result but null componentResults
|
||||
await db.run(`
|
||||
INSERT INTO eval_results (
|
||||
id, eval_id, prompt_idx, test_idx, test_case, prompt, provider,
|
||||
success, score, grading_result
|
||||
) VALUES (
|
||||
'test1', '${eval_.id}', 0, 0, '{}', '{}', '{}', 1, 1.0,
|
||||
'{"pass": true, "score": 1, "reason": "Test", "componentResults": null}'
|
||||
)
|
||||
`);
|
||||
|
||||
const { testIndices, filteredCount } = await (eval_ as any).queryTestIndices({
|
||||
filterMode: 'user-rated',
|
||||
});
|
||||
|
||||
expect(filteredCount).toBe(0);
|
||||
expect(testIndices).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle results with multiple componentResults including human', async () => {
|
||||
const eval_ = await EvalFactory.create({ numResults: 0 });
|
||||
const db = await getDb();
|
||||
|
||||
// Result with multiple componentResults, one of which is human
|
||||
await db.run(`
|
||||
INSERT INTO eval_results (
|
||||
id, eval_id, prompt_idx, test_idx, test_case, prompt, provider,
|
||||
success, score, grading_result
|
||||
) VALUES (
|
||||
'test1', '${eval_.id}', 0, 0, '{}', '{}', '{}', 1, 1.0,
|
||||
'{"pass": true, "score": 1, "componentResults": [
|
||||
{"assertion": {"type": "equals"}, "pass": true, "score": 1},
|
||||
{"assertion": {"type": "human"}, "pass": true, "score": 1},
|
||||
{"assertion": {"type": "contains"}, "pass": true, "score": 1}
|
||||
]}'
|
||||
)
|
||||
`);
|
||||
|
||||
const { testIndices, filteredCount } = await (eval_ as any).queryTestIndices({
|
||||
filterMode: 'user-rated',
|
||||
});
|
||||
|
||||
expect(filteredCount).toBe(1);
|
||||
expect(testIndices).toEqual([0]);
|
||||
});
|
||||
|
||||
it('should handle concurrent access to user-rated filter', async () => {
|
||||
const eval_ = await EvalFactory.create({ numResults: 0 });
|
||||
|
||||
// Add some user-rated results
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await eval_.addResult({
|
||||
description: `test-${i}`,
|
||||
promptIdx: 0,
|
||||
testIdx: i,
|
||||
testCase: { vars: { test: `value${i}` } },
|
||||
promptId: 'test-prompt',
|
||||
provider: { id: 'test-provider', label: 'test-label' },
|
||||
prompt: { raw: 'Test prompt', label: 'Test prompt' },
|
||||
vars: { test: `value${i}` },
|
||||
response: { output: `Response ${i}` },
|
||||
error: null,
|
||||
failureReason: ResultFailureReason.NONE,
|
||||
success: true,
|
||||
score: 1,
|
||||
latencyMs: 100,
|
||||
gradingResult: createHumanRatedGradingResult(true, 1, `User rated ${i}`),
|
||||
namedScores: {},
|
||||
cost: 0.007,
|
||||
metadata: {},
|
||||
} as EvaluateResult);
|
||||
}
|
||||
|
||||
// Run multiple queries concurrently
|
||||
const promises = Array.from({ length: 5 }, () =>
|
||||
(eval_ as any).queryTestIndices({ filterMode: 'user-rated' }),
|
||||
);
|
||||
|
||||
const results = await Promise.all(promises);
|
||||
|
||||
// All concurrent queries should return the same results
|
||||
const firstResult = results[0];
|
||||
for (const result of results) {
|
||||
expect(result.filteredCount).toBe(firstResult.filteredCount);
|
||||
expect(result.testIndices).toEqual(firstResult.testIndices);
|
||||
}
|
||||
});
|
||||
|
||||
it('should verify SQL injection safety', async () => {
|
||||
const eval_ = await EvalFactory.create({ numResults: 0 });
|
||||
|
||||
// Add a user-rated result
|
||||
await eval_.addResult({
|
||||
description: 'test-safe',
|
||||
promptIdx: 0,
|
||||
testIdx: 0,
|
||||
testCase: { vars: { test: 'value' } },
|
||||
promptId: 'test-prompt',
|
||||
provider: { id: 'test-provider', label: 'test-label' },
|
||||
prompt: { raw: 'Test prompt', label: 'Test prompt' },
|
||||
vars: { test: 'value' },
|
||||
response: { output: 'Safe response' },
|
||||
error: null,
|
||||
failureReason: ResultFailureReason.NONE,
|
||||
success: true,
|
||||
score: 1,
|
||||
latencyMs: 100,
|
||||
gradingResult: createHumanRatedGradingResult(true, 1, 'User rated'),
|
||||
namedScores: {},
|
||||
cost: 0.007,
|
||||
metadata: {},
|
||||
} as EvaluateResult);
|
||||
|
||||
// Try SQL injection in search query
|
||||
const maliciousSearch = "'; DROP TABLE eval_results; --";
|
||||
const { filteredCount } = await queryTestIndicesOptimized(eval_.id, {
|
||||
filterMode: 'user-rated',
|
||||
searchQuery: maliciousSearch,
|
||||
});
|
||||
|
||||
// Should not match the malicious search string
|
||||
expect(filteredCount).toBe(0);
|
||||
|
||||
// Test that user-rated filter still works after attempted injection
|
||||
const justUserRated = await queryTestIndicesOptimized(eval_.id, {
|
||||
filterMode: 'user-rated',
|
||||
});
|
||||
expect(justUserRated.filteredCount).toBe(1);
|
||||
expect(justUserRated.testIndices).toEqual([0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user