Adds an authenticated upload flow to the buyer web app: an operator uploads a PDF, the server extracts + paragraph-aligned-chunks it (~2.2k tokens), then runs a hierarchical map-reduce summarization by paying marketplace suppliers per chunk — one on-chain escrow per map call and per reduce node. One book upload fans out into dozens of real mainnet escrows (the intended marketplace traffic) while producing a genuine full-book summary. Server module (buyer/src/pdf/): PDF extraction (text-only, rejects scanned), token-aware chunker, supplier discovery/round-robin filtered to capable models (kimi/deepseek/gpt; excludes qwen2.5:0.5b), pre-spend cost estimate + wallet-floor guard, and a JobStore orchestrator that reuses submitPrompt → findSubmittedEscrowRef → runAccept per call. v1 serializes PostEscrow under a mutex (the shared tx builder can't pin wallet inputs, so parallel escrows would double-spend); retries failed calls on a different supplier then continues with gaps. 6 /v1/pdf-* routes (multipart upload, estimate, confirm-gated start, SSE progress, result, .md download) behind the existing session auth + page/chunk/ balance caps. SPA adds a 4-step BookSummarizer wizard. Tests: chunk/estimate/supplier-pool/extract unit tests + orchestrator (happy/gap/failover) + routes integration. Full buyer suite green (181 tests); SPA build + typecheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
28 lines
952 B
TypeScript
28 lines
952 B
TypeScript
/**
|
|
* buyer-pdf-extract.test.ts — extraction error handling. A non-PDF buffer must
|
|
* surface as a structured PdfExtractionError (not an unhandled throw), so the
|
|
* upload route can map it to a clean 4xx.
|
|
*/
|
|
|
|
import { describe, it, expect } from "vitest";
|
|
import { extractPdfText } from "../../buyer/src/pdf/extract.js";
|
|
import { PdfExtractionError } from "../../buyer/src/pdf/types.js";
|
|
|
|
describe("extractPdfText", () => {
|
|
it("throws a structured PdfExtractionError on a non-PDF buffer", async () => {
|
|
await expect(extractPdfText(Buffer.from("definitely not a pdf"))).rejects.toBeInstanceOf(
|
|
PdfExtractionError,
|
|
);
|
|
});
|
|
|
|
it("error carries a machine-readable reason", async () => {
|
|
try {
|
|
await extractPdfText(Buffer.from("nope"));
|
|
throw new Error("expected throw");
|
|
} catch (e) {
|
|
expect(e).toBeInstanceOf(PdfExtractionError);
|
|
expect((e as PdfExtractionError).reason).toBeTruthy();
|
|
}
|
|
});
|
|
});
|