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

437 lines
13 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { handleWordCount } from '../../src/assertions/wordCount';
import { createMockProvider, createProviderResponse } from '../factories/provider';
import type { AssertionParams, AssertionValue, AtomicTestCase } from '../../src/types/index';
const mockProvider = createMockProvider({
id: 'mock',
response: createProviderResponse({ output: 'mock' }),
});
const defaultParams = {
baseType: 'word-count' as const,
assertionValueContext: {
vars: {},
test: {} as AtomicTestCase,
prompt: 'test prompt',
logProbs: undefined,
provider: mockProvider,
providerResponse: { output: 'test output' },
},
output: 'test output',
providerResponse: { output: 'test output' },
test: {} as AtomicTestCase,
};
describe('handleWordCount', () => {
describe('exact count', () => {
it('should pass when word count matches exactly (number value)', () => {
const params: AssertionParams = {
...defaultParams,
assertion: { type: 'word-count', value: 5 },
renderedValue: 5 as AssertionValue,
outputString: 'This is a test sentence',
inverse: false,
};
const result = handleWordCount(params);
expect(result).toEqual({
pass: true,
score: 1,
reason: 'Assertion passed',
assertion: params.assertion,
});
});
it('should pass when word count matches exactly (string value)', () => {
const params: AssertionParams = {
...defaultParams,
assertion: { type: 'word-count', value: '3' },
renderedValue: '3' as AssertionValue,
outputString: 'Hello world test',
inverse: false,
};
const result = handleWordCount(params);
expect(result).toEqual({
pass: true,
score: 1,
reason: 'Assertion passed',
assertion: params.assertion,
});
});
it('should fail when word count does not match', () => {
const params: AssertionParams = {
...defaultParams,
assertion: { type: 'word-count', value: 10 },
renderedValue: 10 as AssertionValue,
outputString: 'Short text',
inverse: false,
};
const result = handleWordCount(params);
expect(result).toEqual({
pass: false,
score: 0,
reason: 'Word count 2 does not equal expected 10',
assertion: params.assertion,
});
});
it('should count words correctly with multiple spaces', () => {
const params: AssertionParams = {
...defaultParams,
assertion: { type: 'word-count', value: 4 },
renderedValue: 4 as AssertionValue,
outputString: 'Word with multiple spaces',
inverse: false,
};
const result = handleWordCount(params);
expect(result.pass).toBe(true);
});
it('should handle single word', () => {
const params: AssertionParams = {
...defaultParams,
assertion: { type: 'word-count', value: 1 },
renderedValue: 1 as AssertionValue,
outputString: 'Hello',
inverse: false,
};
const result = handleWordCount(params);
expect(result.pass).toBe(true);
});
it('should handle empty string as 0 words', () => {
const params: AssertionParams = {
...defaultParams,
assertion: { type: 'word-count', value: 0 },
renderedValue: 0 as AssertionValue,
outputString: '',
inverse: false,
};
const result = handleWordCount(params);
expect(result.pass).toBe(true);
});
});
describe('range (min and max)', () => {
it('should pass when word count is within range', () => {
const params: AssertionParams = {
...defaultParams,
assertion: { type: 'word-count', value: { min: 3, max: 7 } },
renderedValue: { min: 3, max: 7 } as AssertionValue,
outputString: 'This is a test sentence',
inverse: false,
};
const result = handleWordCount(params);
expect(result).toEqual({
pass: true,
score: 1,
reason: 'Assertion passed',
assertion: params.assertion,
});
});
it('should pass when word count equals min', () => {
const params: AssertionParams = {
...defaultParams,
assertion: { type: 'word-count', value: { min: 5, max: 10 } },
renderedValue: { min: 5, max: 10 } as AssertionValue,
outputString: 'This is a test sentence',
inverse: false,
};
const result = handleWordCount(params);
expect(result.pass).toBe(true);
});
it('should pass when word count equals max', () => {
const params: AssertionParams = {
...defaultParams,
assertion: { type: 'word-count', value: { min: 1, max: 5 } },
renderedValue: { min: 1, max: 5 } as AssertionValue,
outputString: 'This is a test sentence',
inverse: false,
};
const result = handleWordCount(params);
expect(result.pass).toBe(true);
});
it('should fail when word count is below min', () => {
const params: AssertionParams = {
...defaultParams,
assertion: { type: 'word-count', value: { min: 10, max: 20 } },
renderedValue: { min: 10, max: 20 } as AssertionValue,
outputString: 'Too few words',
inverse: false,
};
const result = handleWordCount(params);
expect(result).toEqual({
pass: false,
score: 0,
reason: 'Word count 3 is not between 10 and 20',
assertion: params.assertion,
});
});
it('should fail when word count is above max', () => {
const params: AssertionParams = {
...defaultParams,
assertion: { type: 'word-count', value: { min: 1, max: 3 } },
renderedValue: { min: 1, max: 3 } as AssertionValue,
outputString: 'This has way too many words in it',
inverse: false,
};
const result = handleWordCount(params);
expect(result).toEqual({
pass: false,
score: 0,
reason: 'Word count 8 is not between 1 and 3',
assertion: params.assertion,
});
});
});
describe('min only', () => {
it('should pass when word count meets minimum', () => {
const params: AssertionParams = {
...defaultParams,
assertion: { type: 'word-count', value: { min: 3 } },
renderedValue: { min: 3 } as AssertionValue,
outputString: 'This is a test sentence',
inverse: false,
};
const result = handleWordCount(params);
expect(result).toEqual({
pass: true,
score: 1,
reason: 'Assertion passed',
assertion: params.assertion,
});
});
it('should fail when word count is below minimum', () => {
const params: AssertionParams = {
...defaultParams,
assertion: { type: 'word-count', value: { min: 10 } },
renderedValue: { min: 10 } as AssertionValue,
outputString: 'Too short',
inverse: false,
};
const result = handleWordCount(params);
expect(result).toEqual({
pass: false,
score: 0,
reason: 'Word count 2 is less than minimum 10',
assertion: params.assertion,
});
});
});
describe('max only', () => {
it('should pass when word count is below maximum', () => {
const params: AssertionParams = {
...defaultParams,
assertion: { type: 'word-count', value: { max: 10 } },
renderedValue: { max: 10 } as AssertionValue,
outputString: 'Short text here',
inverse: false,
};
const result = handleWordCount(params);
expect(result).toEqual({
pass: true,
score: 1,
reason: 'Assertion passed',
assertion: params.assertion,
});
});
it('should fail when word count exceeds maximum', () => {
const params: AssertionParams = {
...defaultParams,
assertion: { type: 'word-count', value: { max: 3 } },
renderedValue: { max: 3 } as AssertionValue,
outputString: 'This text has too many words',
inverse: false,
};
const result = handleWordCount(params);
expect(result).toEqual({
pass: false,
score: 0,
reason: 'Word count 6 is greater than maximum 3',
assertion: params.assertion,
});
});
});
describe('inverse mode', () => {
it('should pass inverse when word count does not match', () => {
const params: AssertionParams = {
...defaultParams,
assertion: { type: 'not-word-count', value: 10 },
renderedValue: 10 as AssertionValue,
outputString: 'Short text',
inverse: true,
};
const result = handleWordCount(params);
expect(result.pass).toBe(true);
expect(result.score).toBe(1);
expect(result.reason).toBe('Assertion passed');
});
it('should fail inverse when word count matches with descriptive message', () => {
const params: AssertionParams = {
...defaultParams,
assertion: { type: 'not-word-count', value: 2 },
renderedValue: 2 as AssertionValue,
outputString: 'Two words',
inverse: true,
};
const result = handleWordCount(params);
expect(result.pass).toBe(false);
expect(result.score).toBe(0);
expect(result.reason).toBe('Expected word count to not equal 2, but got 2');
});
it('should fail inverse with range and provide descriptive message', () => {
const params: AssertionParams = {
...defaultParams,
assertion: { type: 'not-word-count', value: { min: 1, max: 5 } },
renderedValue: { min: 1, max: 5 } as AssertionValue,
outputString: 'Three word text',
inverse: true,
};
const result = handleWordCount(params);
expect(result.pass).toBe(false);
expect(result.reason).toBe('Expected word count to not be between 1 and 5, but got 3');
});
it('should fail inverse with min only and provide descriptive message', () => {
const params: AssertionParams = {
...defaultParams,
assertion: { type: 'not-word-count', value: { min: 2 } },
renderedValue: { min: 2 } as AssertionValue,
outputString: 'Three word text',
inverse: true,
};
const result = handleWordCount(params);
expect(result.pass).toBe(false);
expect(result.reason).toBe('Expected word count to be less than 2, but got 3');
});
it('should fail inverse with max only and provide descriptive message', () => {
const params: AssertionParams = {
...defaultParams,
assertion: { type: 'not-word-count', value: { max: 5 } },
renderedValue: { max: 5 } as AssertionValue,
outputString: 'Three word text',
inverse: true,
};
const result = handleWordCount(params);
expect(result.pass).toBe(false);
expect(result.reason).toBe('Expected word count to be greater than 5, but got 3');
});
});
describe('edge cases', () => {
it('should handle text with newlines', () => {
const params: AssertionParams = {
...defaultParams,
assertion: { type: 'word-count', value: 7 },
renderedValue: 7 as AssertionValue,
outputString: 'First line\nSecond line here\nThird line',
inverse: false,
};
const result = handleWordCount(params);
expect(result.pass).toBe(true);
});
it('should handle text with tabs', () => {
const params: AssertionParams = {
...defaultParams,
assertion: { type: 'word-count', value: 4 },
renderedValue: 4 as AssertionValue,
outputString: 'Word\tWith\tTabs\tHere',
inverse: false,
};
const result = handleWordCount(params);
expect(result.pass).toBe(true);
});
it('should throw error when value is null', () => {
const params: AssertionParams = {
...defaultParams,
assertion: { type: 'word-count', value: null as any },
renderedValue: null as any,
outputString: 'Some text',
inverse: false,
};
expect(() => handleWordCount(params)).toThrow('"word-count" assertion must have a value');
});
it('should throw error when object has neither min nor max', () => {
const params: AssertionParams = {
...defaultParams,
assertion: { type: 'word-count', value: {} as any },
renderedValue: {} as any,
outputString: 'Some text',
inverse: false,
};
expect(() => handleWordCount(params)).toThrow(
'"word-count" assertion object must have "min" and/or "max" properties',
);
});
it('should throw error when value is invalid type', () => {
const params: AssertionParams = {
...defaultParams,
assertion: { type: 'word-count', value: 'invalid' as any },
renderedValue: 'invalid' as any,
outputString: 'Some text',
inverse: false,
};
expect(() => handleWordCount(params)).toThrow(
'"word-count" assertion value must be a number or an object with min/max properties',
);
});
it('should throw error when min is greater than max', () => {
const params: AssertionParams = {
...defaultParams,
assertion: { type: 'word-count', value: { min: 10, max: 5 } },
renderedValue: { min: 10, max: 5 } as AssertionValue,
outputString: 'Some text',
inverse: false,
};
expect(() => handleWordCount(params)).toThrow(
'"word-count" assertion: min (10) must be less than or equal to max (5)',
);
});
});
});