CI (SIAX Cloud) / integration (push) Skipped
CI (SIAX Cloud) / sonar (push) Skipped
masterplan-lock / check (push) Successful in 6s
CI (SIAX Cloud) / sonar (pull_request) Skipped
masterplan-lock / check (pull_request) Successful in 3s
CI (SIAX Cloud) / security (push) Successful in 18s
CI (SIAX Cloud) / security (pull_request) Successful in 20s
Security Scan (SIAX Cloud) / security (push) Successful in 35s
CI (SIAX Cloud) / integration (pull_request) Successful in 43s
CI (SIAX Cloud) / quality (push) Successful in 1m23s
CI (SIAX Cloud) / quality (pull_request) Successful in 1m22s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 2m4s
60 lines
4.0 KiB
TypeScript
60 lines
4.0 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { validateStructuredOutput } from '../../src/document-workbench/structured-output-validation';
|
|
|
|
const sample = {
|
|
fields: [{ name: 'name', type: 'string' as const, required: true }, { name: 'count', type: 'integer' as const, required: true }],
|
|
value: { name: 'Acme', count: 12 },
|
|
sources: [{ id: 'page-1', text: 'Name: Acme\nCount: 12' }],
|
|
evidence: [
|
|
{ field: 'name', sourceId: 'page-1', start: 6, end: 10, transform: 'exact' as const },
|
|
{ field: 'count', sourceId: 'page-1', start: 18, end: 20, transform: 'integer' as const },
|
|
],
|
|
};
|
|
describe('structured output mechanical validation', () => {
|
|
it('accepts typed fields with matching source ranges', () => {
|
|
expect(validateStructuredOutput(sample).status).toBe('mechanically_validated');
|
|
});
|
|
it('does not accept invented, mistyped or extra values', () => {
|
|
const result = validateStructuredOutput({ ...sample, value: { name: 'Other', count: '12', extra: 1 } });
|
|
expect(result).toMatchObject({ status: 'needs_review', issues: expect.arrayContaining([
|
|
{ field: 'name', code: 'evidence_mismatch' }, { field: 'count', code: 'invalid_type' }, { field: 'extra', code: 'unexpected_field' },
|
|
]) });
|
|
});
|
|
it('rejects absent, duplicate or out-of-range evidence and ambiguous sources', () => {
|
|
expect(validateStructuredOutput({ ...sample, evidence: [] }).status).toBe('needs_review');
|
|
expect(validateStructuredOutput({ ...sample, evidence: [...sample.evidence, sample.evidence[0]!] }).status).toBe('needs_review');
|
|
expect(validateStructuredOutput({ ...sample, evidence: [{ ...sample.evidence[0]!, end: 999 }, sample.evidence[1]!] }).status).toBe('needs_review');
|
|
expect(validateStructuredOutput({ ...sample, sources: [...sample.sources, sample.sources[0]!] }).status).toBe('needs_review');
|
|
});
|
|
it('refuses unsafe integers and absent required fields', () => {
|
|
expect(validateStructuredOutput({ ...sample, value: { name: 'Acme', count: Number.MAX_SAFE_INTEGER + 1 } }).status).toBe('needs_review');
|
|
expect(validateStructuredOutput({ ...sample, value: { name: 'Acme' }, evidence: [sample.evidence[0]!] }).status).toBe('needs_review');
|
|
});
|
|
it('preserves exact decimal precision and accepts real ISO calendar dates', () => {
|
|
const text = '9007199254740993.20 2024-02-29';
|
|
const input = {
|
|
fields: [{ name: 'amount', type: 'decimal' as const, required: true }, { name: 'date', type: 'date' as const, required: true }],
|
|
value: { amount: '9007199254740993.20', date: '2024-02-29' }, sources: [{ id: 'p', text }],
|
|
evidence: [
|
|
{ field: 'amount', sourceId: 'p', start: 0, end: 19, transform: 'decimal' as const },
|
|
{ field: 'date', sourceId: 'p', start: 20, end: 30, transform: 'date' as const },
|
|
],
|
|
};
|
|
expect(validateStructuredOutput(input).status).toBe('mechanically_validated');
|
|
expect(validateStructuredOutput({ ...input, value: { ...input.value, amount: 9007199254740994 } }).status).toBe('needs_review');
|
|
expect(validateStructuredOutput({ ...input, value: { ...input.value, amount: '9007199254740993.2' } }).status).toBe('needs_review');
|
|
});
|
|
it.each(['2023-02-29', '1900-02-29', '2024-13-01', '2024-04-31', '0000-01-01'])('rejects invalid date %s even if present in the source', (text) => {
|
|
expect(validateStructuredOutput({
|
|
fields: [{ name: 'date', type: 'date', required: true }], value: { date: text }, sources: [{ id: 'p', text }],
|
|
evidence: [{ field: 'date', sourceId: 'p', start: 0, end: text.length, transform: 'date' }],
|
|
}).status).toBe('needs_review');
|
|
});
|
|
it.each(['01.0', '1e3', '1,25', '+1.0', '.5'])('rejects ambiguous decimal %s rather than guessing', (text) => {
|
|
expect(validateStructuredOutput({
|
|
fields: [{ name: 'amount', type: 'decimal', required: true }], value: { amount: text }, sources: [{ id: 'p', text }],
|
|
evidence: [{ field: 'amount', sourceId: 'p', start: 0, end: text.length, transform: 'decimal' }],
|
|
}).status).toBe('needs_review');
|
|
});
|
|
});
|