CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
91 lines
3.5 KiB
TypeScript
91 lines
3.5 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { render, screen, waitFor } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { I18nProvider, ToastProvider } from "@nexarch/shl";
|
|
import { CameraCaptureManager } from "./CameraCaptureManager";
|
|
import type { CapturedPage } from "../lib/mobileCapture";
|
|
|
|
const ORIGINAL_ENV = process.env;
|
|
let capturedOnSubmit: ((pages: CapturedPage[]) => void) | undefined;
|
|
|
|
vi.mock("./CameraCapture", () => ({
|
|
CameraCapture: ({ onSubmit }: { onSubmit: (pages: CapturedPage[]) => void }) => {
|
|
capturedOnSubmit = onSubmit;
|
|
return null;
|
|
},
|
|
}));
|
|
|
|
function renderManager() {
|
|
return render(
|
|
<I18nProvider initialLocale="de">
|
|
<ToastProvider>
|
|
<CameraCaptureManager />
|
|
</ToastProvider>
|
|
</I18nProvider>,
|
|
);
|
|
}
|
|
|
|
function page(id: string): CapturedPage {
|
|
return { id, blob: new Blob([new Uint8Array(10)], { type: "image/jpeg" }), previewUrl: `blob:${id}` };
|
|
}
|
|
|
|
beforeEach(() => {
|
|
process.env = { ...ORIGINAL_ENV, NEXT_PUBLIC_UPLOAD_API_URL: "http://backend.test" };
|
|
});
|
|
|
|
afterEach(() => {
|
|
process.env = ORIGINAL_ENV;
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
describe("CameraCaptureManager", () => {
|
|
// Akzeptanzkriterium 2: die Aufnahme laeuft ueber die bestehende DOC-01-Upload-API.
|
|
it("reicht jede gesammelte Seite ueber die bestehende Upload-API ein", async () => {
|
|
global.fetch = vi.fn(async (input: RequestInfo | URL) => {
|
|
const url = String(input);
|
|
if (url.endsWith("/uploads")) return new Response(JSON.stringify({ sessionId: `sess-${Math.random()}` }), { status: 201 });
|
|
if (url.includes("/chunk?offset=")) return new Response(JSON.stringify({ bytesReceived: 10 }), { status: 200 });
|
|
if (url.endsWith("/complete")) return new Response(JSON.stringify({ documentId: "doc-1", revisionId: "rev-1" }), { status: 200 });
|
|
throw new Error(`unerwarteter Aufruf: ${url}`);
|
|
}) as unknown as typeof fetch;
|
|
|
|
renderManager();
|
|
await waitFor(() => expect(capturedOnSubmit).toBeDefined());
|
|
capturedOnSubmit!([page("seite-1"), page("seite-2")]);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getAllByText(/Abgeschlossen/)).toHaveLength(2);
|
|
});
|
|
});
|
|
|
|
it("erlaubt Retry fuer eine fehlgeschlagene Seite", async () => {
|
|
let chunkCalls = 0;
|
|
global.fetch = vi.fn(async (input: RequestInfo | URL) => {
|
|
const url = String(input);
|
|
if (url.endsWith("/uploads")) return new Response(JSON.stringify({ sessionId: "sess-retry" }), { status: 201 });
|
|
if (url.includes("/chunk?offset=")) {
|
|
chunkCalls += 1;
|
|
if (chunkCalls === 1) return new Response("upload: fehler", { status: 500 });
|
|
return new Response(JSON.stringify({ bytesReceived: 10 }), { status: 200 });
|
|
}
|
|
if (url.endsWith("/uploads/sess-retry")) {
|
|
return new Response(
|
|
JSON.stringify({ sessionId: "sess-retry", filename: "kassenbeleg-1.jpg", totalSize: 10, bytesReceived: 0, status: "uploading" }),
|
|
{ status: 200 },
|
|
);
|
|
}
|
|
if (url.endsWith("/complete")) return new Response(JSON.stringify({ documentId: "doc-1", revisionId: "rev-1" }), { status: 200 });
|
|
throw new Error(`unerwarteter Aufruf: ${url}`);
|
|
}) as unknown as typeof fetch;
|
|
|
|
renderManager();
|
|
await waitFor(() => expect(capturedOnSubmit).toBeDefined());
|
|
capturedOnSubmit!([page("seite-1")]);
|
|
|
|
const retryButton = await screen.findByRole("button", { name: "Erneut versuchen" });
|
|
await userEvent.click(retryButton);
|
|
|
|
await waitFor(() => expect(screen.getByText(/Abgeschlossen/)).toBeInTheDocument());
|
|
});
|
|
});
|