fix: Kennzahlen mit Timeout laden, ungültige Seitenbereiche als 400 melden
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
32b5228f68
commit
79dadb9c93
3 files changed
+70
-6
No files matched your search
@@ -1,5 +1,5 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { ConflictException } from '@nestjs/common';
|
import { BadRequestException, ConflictException } from '@nestjs/common';
|
||||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { PDFDocument } from 'pdf-lib';
|
import { PDFDocument } from 'pdf-lib';
|
||||||
@@ -208,6 +208,33 @@ describe('EmailImportService – Duplikatprüfung', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getAttachmentPdf', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
contentRepo.findOne.mockResolvedValue({
|
||||||
|
Content1: await pdfMitSeiten(3),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('liefert den gewünschten Seitenbereich', async () => {
|
||||||
|
const pdf = await service.getAttachmentPdf(7, { start: 2, end: 3 });
|
||||||
|
expect((await PDFDocument.load(pdf)).getPageCount()).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
{ start: 3, end: 2 },
|
||||||
|
{ start: 0, end: 2 },
|
||||||
|
{ start: 5, end: 6 },
|
||||||
|
{ start: Number.NaN, end: 2 },
|
||||||
|
])(
|
||||||
|
'meldet einen ungültigen Seitenbereich $start–$end als 400',
|
||||||
|
async (pages) => {
|
||||||
|
await expect(service.getAttachmentPdf(7, pages)).rejects.toThrow(
|
||||||
|
BadRequestException,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
describe('Import als Version', () => {
|
describe('Import als Version', () => {
|
||||||
it('hängt den Beleg als Version an, statt ein neues Dokument anzulegen', async () => {
|
it('hängt den Beleg als Version an, statt ein neues Dokument anzulegen', async () => {
|
||||||
contentRepo.findOne.mockResolvedValue({
|
contentRepo.findOne.mockResolvedValue({
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
HttpException,
|
HttpException,
|
||||||
HttpStatus,
|
HttpStatus,
|
||||||
ConflictException,
|
ConflictException,
|
||||||
|
BadRequestException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
@@ -370,8 +371,19 @@ export class EmailImportService {
|
|||||||
ignoreEncryption: true,
|
ignoreEncryption: true,
|
||||||
});
|
});
|
||||||
const total = pdfDoc.getPageCount();
|
const total = pdfDoc.getPageCount();
|
||||||
const startIdx = Math.max(1, pages.start) - 1;
|
const ende = pages.end === 999 ? total : Math.min(pages.end, total);
|
||||||
const endIdx = Math.min(pages.end === 999 ? total : pages.end, total) - 1;
|
if (
|
||||||
|
!Number.isInteger(pages.start) ||
|
||||||
|
!Number.isInteger(ende) ||
|
||||||
|
pages.start < 1 ||
|
||||||
|
pages.start > ende
|
||||||
|
) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
`Ungültiger Seitenbereich ${pages.start}–${pages.end} (Dokument hat ${total} Seiten)`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const startIdx = pages.start - 1;
|
||||||
|
const endIdx = ende - 1;
|
||||||
const sliced = await PDFDocument.create();
|
const sliced = await PDFDocument.create();
|
||||||
const indices = Array.from(
|
const indices = Array.from(
|
||||||
{ length: endIdx - startIdx + 1 },
|
{ length: endIdx - startIdx + 1 },
|
||||||
|
|||||||
@@ -3,6 +3,25 @@ import { Alert, Button, Modal, Space, Spin, Tag, Typography, message } from 'ant
|
|||||||
import { paperlessApi, type BelegnummerPruefung, type VergleichKennzahlen } from '../api/paperless';
|
import { paperlessApi, type BelegnummerPruefung, type VergleichKennzahlen } from '../api/paperless';
|
||||||
import { useIsMobile } from '../hooks/useIsMobile';
|
import { useIsMobile } from '../hooks/useIsMobile';
|
||||||
|
|
||||||
|
/** Länger wird auf Seiten-/Wortzahl nicht gewartet – der Vergleich bleibt trotzdem möglich. */
|
||||||
|
const KENNZAHLEN_TIMEOUT_MS = 30000;
|
||||||
|
|
||||||
|
function mitTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
|
||||||
|
return new Promise<T>((resolve, reject) => {
|
||||||
|
const timer = setTimeout(() => reject(new Error('Zeitüberschreitung')), ms);
|
||||||
|
promise.then(
|
||||||
|
(wert) => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
resolve(wert);
|
||||||
|
},
|
||||||
|
(err: unknown) => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
reject(err instanceof Error ? err : new Error(String(err)));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export interface DuplikatVergleichModalProps {
|
export interface DuplikatVergleichModalProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
belegnummer: string;
|
belegnummer: string;
|
||||||
@@ -70,6 +89,7 @@ export function DuplikatVergleichModal({
|
|||||||
const [vorhandenUrl, setVorhandenUrl] = useState<string | null>(null);
|
const [vorhandenUrl, setVorhandenUrl] = useState<string | null>(null);
|
||||||
const [neuUrl, setNeuUrl] = useState<string | null>(null);
|
const [neuUrl, setNeuUrl] = useState<string | null>(null);
|
||||||
const [kennzahlen, setKennzahlen] = useState<VergleichKennzahlen | null>(null);
|
const [kennzahlen, setKennzahlen] = useState<VergleichKennzahlen | null>(null);
|
||||||
|
const [kennzahlenFehler, setKennzahlenFehler] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -79,6 +99,7 @@ export function DuplikatVergleichModal({
|
|||||||
let urlNeu: string | null = null;
|
let urlNeu: string | null = null;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setKennzahlen(null);
|
setKennzahlen(null);
|
||||||
|
setKennzahlenFehler(false);
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const [altBlob, neuBlob] = await Promise.all([paperlessApi.getDocumentPdfBlob(paperlessDocumentId), ladeNeuesPdf()]);
|
const [altBlob, neuBlob] = await Promise.all([paperlessApi.getDocumentPdfBlob(paperlessDocumentId), ladeNeuesPdf()]);
|
||||||
@@ -93,12 +114,12 @@ export function DuplikatVergleichModal({
|
|||||||
if (!cancelled) setLoading(false);
|
if (!cancelled) setLoading(false);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
ladeKennzahlen()
|
mitTimeout(ladeKennzahlen(), KENNZAHLEN_TIMEOUT_MS)
|
||||||
.then((k) => {
|
.then((k) => {
|
||||||
if (!cancelled) setKennzahlen(k);
|
if (!cancelled) setKennzahlen(k);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
if (!cancelled) message.warning('Seiten- und Wortzahl konnten nicht ermittelt werden');
|
if (!cancelled) setKennzahlenFehler(true);
|
||||||
});
|
});
|
||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
@@ -114,7 +135,11 @@ export function DuplikatVergleichModal({
|
|||||||
const versionMoeglich = pruefung?.status === 'eindeutig';
|
const versionMoeglich = pruefung?.status === 'eindeutig';
|
||||||
|
|
||||||
let auffaelligkeiten: ReactNode = <Spin size="small" />;
|
let auffaelligkeiten: ReactNode = <Spin size="small" />;
|
||||||
if (kennzahlen) {
|
if (kennzahlenFehler) {
|
||||||
|
auffaelligkeiten = (
|
||||||
|
<Alert type="warning" showIcon message="Seiten- und Wortzahl konnten nicht ermittelt werden – bitte die PDFs direkt vergleichen." />
|
||||||
|
);
|
||||||
|
} else if (kennzahlen) {
|
||||||
const { vorhanden, neu } = kennzahlen;
|
const { vorhanden, neu } = kennzahlen;
|
||||||
const alleBekannt = [vorhanden.seiten, neu.seiten, vorhanden.woerter, neu.woerter].every((w) => w !== null);
|
const alleBekannt = [vorhanden.seiten, neu.seiten, vorhanden.woerter, neu.woerter].every((w) => w !== null);
|
||||||
const gleich = alleBekannt && vorhanden.seiten === neu.seiten && vorhanden.woerter === neu.woerter;
|
const gleich = alleBekannt && vorhanden.seiten === neu.seiten && vorhanden.woerter === neu.woerter;
|
||||||
|
|||||||
Reference in new issue
Block a user