// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0 // The document-review flow in a browser (plan item AT2.3): upload a PDF, the worker reads it through the // fake platform's OCR job API, the four labelled lines of the fixture become proposals (its drawing page // is reported as not read), a person approves, edits and rejects, and the Excel export holds the approved // lines and only those. import { createHash, randomBytes } from "node:crypto" import { readFileSync } from "node:fs" import { expect, test } from "@playwright/test" import { sheetRows } from "../modules/documents/__tests__/support/unzip" import { call, makeOrganization, signIn, unique, violations } from "./support/app" const PDF = Buffer.from("%PDF-1.4\n1 0 obj<<>>endobj\ntrailer<<>>\n%%EOF\n") test("a fixture PDF becomes lines a person reviews, and only approved lines are exported", async ({ page }) => { await signIn(page, `${unique("reviewer")}@example.test`) const { org, workspace, project } = await makeOrganization(page) const base = `/${org}/${workspace}/${project}/documents` await page.goto(base) await expect(page.getByRole("heading", { level: 1, name: "Documents" })).toBeVisible() expect(await violations(page)).toEqual([]) // upload through the file input of the form: the request answers before any OCR has run await page .getByLabel("Upload a document") .setInputFiles({ name: "report.pdf", mimeType: "application/pdf", buffer: PDF }) await page.getByRole("button", { name: "Upload", exact: true }).click() // an upload is three requests (begin, the piece, complete), and the page reloads itself when the // last has answered, with the file in the list: wait for that, or the navigation below would cut // the upload off in the middle await expect(page.getByRole("row", { name: /report\.pdf/ })).toBeVisible({ timeout: 30_000 }) // the worker picks the file up, submits it to the OCR job API and proposes the lines await expect(async () => { await page.goto(base) await expect(page.getByRole("row", { name: /report\.pdf.*Ready for review/ })).toBeVisible({ timeout: 2_000 }) }).toPass({ timeout: 60_000, intervals: [1_000] }) await page.getByRole("link", { name: "Review report.pdf" }).click() await expect(page.getByRole("heading", { level: 1 })).toContainText("report.pdf") // what was proposed by a machine says so, in text await expect(page.getByText("AI-generated content")).toBeVisible() await expect(page.getByRole("row")).toHaveCount(5) // header and four lines // the fixture's third page is a drawing: the extraction left it out, and the page says so await expect(page.getByRole("heading", { name: "Pages not read" })).toBeVisible() await expect(page.getByText("Page 3: drawing. It was left out of the extraction.")).toBeVisible() await expect(page.getByRole("button", { name: "Export approved lines (Excel)" })).toBeDisabled() expect(await violations(page)).toEqual([]) // each line shows where it was read await expect(page.getByRole("complementary", { name: "Source, page 2" }).first()).toContainText( "Total amount: 1 250,00 EUR", ) // approve one, correct another, reject a third await page.getByRole("button", { name: "Approve Reference" }).click() await expect(page.getByRole("row", { name: /Reference.*Approved/ })).toBeVisible() await page.getByRole("button", { name: "Edit Issued on" }).click() await page.getByLabel("Label").fill("Issue date") await page.getByLabel("Value").fill("2026-04-02") await page.getByRole("button", { name: "Save" }).click() await expect(page.getByRole("row", { name: /Issue date.*Edited/ })).toBeVisible() await page.getByRole("button", { name: "Approve Issue date" }).click() await expect(page.getByRole("row", { name: /Issue date.*Approved/ })).toBeVisible() await page.getByRole("button", { name: "Reject Signed by" }).click() await expect(page.getByRole("row", { name: /Signed by.*Rejected/ })).toBeVisible() // the line nobody decided is still waiting, and is not exported await expect(page.getByRole("row", { name: /Total amount.*To review/ })).toBeVisible() const download = page.waitForEvent("download") await page.getByRole("button", { name: "Export approved lines (Excel)" }).click() const file = await download expect(file.suggestedFilename()).toBe("report-approved.xlsx") const path = await file.path() const rows = sheetRows(new Uint8Array(readFileSync(path))) expect(rows).toEqual([ ["Label", "Value", "Page", "Source quote", "Edited by a reviewer"], ["Reference", "QR-2026-04", 1, "Reference: QR-2026-04", "no"], ["Issue date", "2026-04-02", 1, "Issued on: 2026-04-01", "yes"], ]) expect(await violations(page)).toEqual([]) }) test("a 30 MB PDF is uploaded in the browser with a progress bar, stored whole, and digested by the platform", async ({ page, }) => { await signIn(page, `${unique("uploader")}@example.test`) const { org, workspace, project } = await makeOrganization(page) const base = `/${org}/${workspace}/${project}/documents` await page.goto(base) // 30 MB that start like a PDF and are not all one byte, so a piece that arrives short or twice // cannot hash to the same digest. The proxy would cut such a body at 10 MB if it ran for it. const size = 30 * 1024 * 1024 const buffer = Buffer.concat([Buffer.from("%PDF-1.4\n"), randomBytes(size - 9)]) expect(buffer.length).toBe(size) // Slow the upload to 8 MB/s, so that the bar is on screen for seconds and not for a blink: what // the person sees is the requirement, and a loopback upload would be over before a poll looked. const cdp = await page.context().newCDPSession(page) await cdp.send("Network.enable") await cdp.send("Network.emulateNetworkConditions", { offline: false, latency: 0, downloadThroughput: -1, uploadThroughput: 8 * 1024 * 1024, }) await page.getByLabel("Upload a document").setInputFiles({ name: "tender.pdf", mimeType: "application/pdf", buffer }) await page.getByRole("button", { name: "Upload", exact: true }).click() // the bar names the file and how far it has got, and the page reloads when the upload is done await expect(page.getByRole("progressbar", { name: /Uploading tender\.pdf: \d+%/ })).toBeVisible() await expect(page.getByRole("row", { name: /tender\.pdf/ })).toBeVisible({ timeout: 60_000 }) await cdp.send("Network.emulateNetworkConditions", { offline: false, latency: 0, downloadThroughput: -1, uploadThroughput: -1, }) const listed = await call<{ files: Array<{ filename: string; size: number; sha256: string }> }>( page, "files.file.list", { org, workspace, project, }, ) const stored = listed.files.find((file) => file.filename === "tender.pdf") expect(stored?.size).toBe(size) expect(stored?.sha256).toBe(createHash("sha256").update(buffer).digest("hex")) }) test("another organization's member does not see the document", async ({ page, browser }) => { await signIn(page, `${unique("owner")}@example.test`) const mine = await makeOrganization(page) const stranger = await browser.newPage() await signIn(stranger, `${unique("stranger")}@example.test`) const response = await stranger.goto(`/${mine.org}/${mine.workspace}/${mine.project}/documents`) // the organization layout answers a non-member as it answers a stranger: the not-found page, as a // 404, with no document list and no names expect(response?.status()).toBe(404) await expect(stranger.getByRole("heading", { level: 1, name: "Page not found" })).toBeVisible() expect(await stranger.content()).not.toContain(`${mine.org} Review`) await stranger.close() })