Files
0pt/tests/document-workbench/doc0-source-client.test.ts
T

70 lines
4.6 KiB
TypeScript

import { createHash } from 'node:crypto';
import { describe, expect, it, vi } from 'vitest';
import { Doc0SourceClient, Doc0SourceUnavailableError } from '../../src/document-workbench/doc0-source-client';
const hash = (value: string) => createHash('sha256').update(value).digest('hex');
const receipt = { documentId: 'doc-a', versionId: 'version-a', checksumSha256: 'a'.repeat(64), pipelineStage: 'parsed' };
const input = { tenantId: 'tenant-a', projectId: 'project-a', actorId: 'user-a', requestId: 'request-a', receipt };
const text = 'Acme 😀';
const metadata = { tenantId: input.tenantId, documentId: receipt.documentId, versionId: receipt.versionId,
documentTextId: 'text-a', parserRunId: 'run-a', segmentsTruncated: false, segmentCount: 1, charCount: text.length, textSha256: hash(text) };
const segment = { tenantId: input.tenantId, documentId: receipt.documentId, versionId: receipt.versionId,
documentTextId: 'text-a', parserRunId: 'run-a', segmentId: 'segment-a', sequence: 0, text, charStart: 0,
charEnd: text.length, segmentSha256: hash(text), sourceVersionSha256: receipt.checksumSha256 };
const payload = { offset: 0, texts: [metadata], segments: [segment] };
describe('DOC0 extracted source client', () => {
it('reads scoped, checksum-bound segment text without caching or redirects', async () => {
const fetchImpl = vi.fn(async (url: string | URL | Request, init?: RequestInit) => {
expect(String(url)).toContain('/v1/doc0/documents/doc-a/versions/version-a/segments?limit=100&offset=0');
expect(new Headers(init?.headers).get('x-tenant-id')).toBe('tenant-a');
expect(new Headers(init?.headers).get('x-actor-id')).toBe('user-a');
expect(init?.redirect).toBe('error');
expect(init?.cache).toBe('no-store');
return Response.json(payload);
});
const client = new Doc0SourceClient({ baseUrl: 'https://doc0.test', serviceToken: 'test', fetchImpl });
expect(await client.read(input)).toEqual({ sources: [{ id: 'segment-a', text }], reportedTextSha256: hash(text) });
});
it('rejects incomplete, forged, cross-tenant or inconsistent text instead of passing it to a model', async () => {
const bad = [
{ ...payload, texts: [{ ...metadata, segmentsTruncated: true }] },
{ ...payload, texts: [{ ...metadata, segmentCount: 2 }] },
{ ...payload, texts: [{ ...metadata, tenantId: 'tenant-b' }] },
{ ...payload, texts: [metadata, metadata] },
{ ...payload, segments: [{ ...segment, sourceVersionSha256: 'b'.repeat(64) }] },
{ ...payload, segments: [{ ...segment, text: 'fabricated' }] },
{ ...payload, segments: [{ ...segment, tenantId: 'tenant-b' }] },
{ ...payload, segments: [{ ...segment, sequence: 1 }] },
{ ...payload, segments: [{ ...segment, charEnd: 5 }] },
{ ...payload, offset: 1 },
];
for (const candidate of bad) {
const client = new Doc0SourceClient({ baseUrl: 'https://doc0.test', serviceToken: 'test', fetchImpl: async () => Response.json(candidate) });
await expect(client.read(input)).rejects.toBeInstanceOf(Doc0SourceUnavailableError);
}
});
it('rejects empty, oversized, unavailable and unparsed sources', async () => {
const fetchImpl = vi.fn(async () => Response.json(payload));
const client = new Doc0SourceClient({ baseUrl: 'https://doc0.test', serviceToken: 'test', fetchImpl });
await expect(client.read({ ...input, receipt: { ...receipt, pipelineStage: 'parse_failed' } })).rejects.toBeInstanceOf(Doc0SourceUnavailableError);
expect(fetchImpl).not.toHaveBeenCalled();
for (const response of [Response.json({ ...payload, texts: [] }), new Response('x'.repeat(1024 * 1024 + 1)), new Response('', { status: 503 })]) {
const bad = new Doc0SourceClient({ baseUrl: 'https://doc0.test', serviceToken: 'test', fetchImpl: async () => response });
await expect(bad.read(input)).rejects.toBeInstanceOf(Doc0SourceUnavailableError);
}
});
it('pins the original scope and validated credential during an in-flight read', async () => {
const mutable = structuredClone(input);
const config = { baseUrl: 'https://doc0.test', serviceToken: 'original', fetchImpl: async (_url: string | URL | Request, init?: RequestInit) => {
expect(new Headers(init?.headers).get('authorization')).toBe('Bearer original');
mutable.tenantId = 'tenant-b';
mutable.receipt.checksumSha256 = 'b'.repeat(64);
return Response.json(payload);
} };
const client = new Doc0SourceClient(config);
config.serviceToken = 'changed';
expect(await client.read(mutable)).toMatchObject({ sources: [{ id: 'segment-a', text }] });
});
});