import { PaperlessTaskProcessorService } from './paperless-task-processor.service'; import { Task } from '../database/entities/task.entity'; /** * Charakterisierungstest für processSuccessfulTask: pinnt den PATCH-Payload * fest, damit der Refactor auf deriveTaskMetadata() verhaltensidentisch bleibt. * custom_fields wird ordnungsunabhängig verglichen (Paperless behandelt das * Array als Menge). */ function createTask(overrides: Partial = {}): Task { return { TaskId: 'task-uuid-1', InterneBelegnummer: '', DocumentType: null, Eingangsdatum: null, Fertig: 0, Tags: null, BetriebID: null, Lieferant: null, externeBelegnummer: null, EinkaufID: null, Belegdatum: null, PaperlessDocumentID: null, TaskReferenceID: null, BarcodeJson: null, DuplikatZU: null, CustomFieldsJson: null, Asn: null, SourceAttachmentID: null, SourceAttachmentRange: null, ...overrides, } as Task; } function sortByField(fields: Array<{ field: number }>) { return [...fields].sort((a, b) => a.field - b.field); } /** Erwartete Form des PATCH-Payloads an updateDocument. */ interface PatchPayload { custom_fields: Array<{ field: number; value: unknown }>; archive_serial_number?: number; document_type?: number; created?: string; owner?: number | null; tags?: number[]; title?: string; } describe('PaperlessTaskProcessorService.processSuccessfulTask', () => { let paperlessService: { getDocument: jest.Mock; updateDocument: jest.Mock; addNote: jest.Mock; getDocumentMetadata: jest.Mock; getTask: jest.Mock; }; let taskRepo: { save: jest.Mock }; let documentRepo: { findOne: jest.Mock; create: jest.Mock; save: jest.Mock }; let attachmentRepo: { findOne: jest.Mock; save: jest.Mock }; let service: PaperlessTaskProcessorService; /** Zugriff auf die private Methode, typsicher für den Test gekapselt. */ function runProcessSuccessfulTask( task: Task, apiTask: { related_document: number }, parentTask: Task | null, ): Promise { return ( service as unknown as { processSuccessfulTask: ( t: Task, a: { related_document: number }, p: Task | null, ) => Promise; } ).processSuccessfulTask(task, apiTask, parentTask); } function lastPatchPayload(): PatchPayload { const calls = paperlessService.updateDocument.mock.calls as Array< [number, PatchPayload] >; return calls[calls.length - 1][1]; } beforeEach(() => { paperlessService = { getDocument: jest.fn(), updateDocument: jest.fn().mockResolvedValue(undefined), addNote: jest.fn().mockResolvedValue(undefined), getDocumentMetadata: jest.fn().mockResolvedValue({ original_checksum: 'abc', original_filename: 'test.pdf', }), getTask: jest.fn(), }; taskRepo = { save: jest.fn().mockResolvedValue(undefined) }; documentRepo = { findOne: jest.fn().mockResolvedValue(null), create: jest.fn((v: object) => v), save: jest.fn().mockResolvedValue(undefined), }; attachmentRepo = { findOne: jest.fn().mockResolvedValue(null), save: jest.fn().mockResolvedValue(undefined), }; service = new PaperlessTaskProcessorService( taskRepo as any, documentRepo as any, attachmentRepo as any, paperlessService as any, ); }); it('baut den PATCH-Payload mit ASN, CF3/CF7/CF9, Typ, created, owner und Tag-Merge', async () => { paperlessService.getDocument.mockResolvedValue({ id: 101, title: 'Testdokument', custom_fields: [{ field: 7, value: 'alt' }], tags: [99], }); const task = createTask({ InterneBelegnummer: '2026-000123', externeBelegnummer: 'RE-9', Eingangsdatum: new Date('2026-07-14T00:00:00Z'), Belegdatum: new Date('2026-07-01T00:00:00Z'), DocumentType: 2, BetriebID: 3, Tags: '1,2', CustomFieldsJson: JSON.stringify({ '4': 'x' }), }); await runProcessSuccessfulTask(task, { related_document: 101 }, null); expect(paperlessService.updateDocument).toHaveBeenCalledTimes(1); expect(paperlessService.updateDocument).toHaveBeenCalledWith( 101, expect.anything(), ); const payload = lastPatchPayload(); expect(payload.archive_serial_number).toBe(2026000123); expect(payload.document_type).toBe(2); expect(payload.created).toBe('2026-07-01T00:00:00.000Z'); expect(payload.owner).toBe(3); expect(payload.tags).toEqual([99, 1, 2]); expect(sortByField(payload.custom_fields)).toEqual([ { field: 3, value: 'RE-9' }, { field: 4, value: 'x' }, { field: 7, value: '2026-000123' }, { field: 9, value: '2026-07-14' }, ]); expect(task.Fertig).toBe(1); expect(task.PaperlessDocumentID).toBe(101); }); it('entfernt den Owner (null) bei leerer BetriebID', async () => { paperlessService.getDocument.mockResolvedValue({ id: 102, title: 'Ohne Betrieb', custom_fields: [], tags: [], }); const task = createTask({ InterneBelegnummer: '2026-000124' }); await runProcessSuccessfulTask(task, { related_document: 102 }, null); expect(lastPatchPayload().owner).toBeNull(); }); it('überschreibt bei Anlagen document_type/title und verknüpft CF8 zum Elterndokument', async () => { paperlessService.getDocument .mockResolvedValueOnce({ id: 103, title: 'Anlage-Roh', custom_fields: [], tags: [], }) .mockResolvedValueOnce({ id: 55, title: 'Eltern' }); paperlessService.getTask.mockResolvedValue([{ related_document: 55 }]); const task = createTask({ DocumentType: 2, TaskReferenceID: 'parent-uuid', }); const parentTask = createTask({ TaskId: 'parent-uuid', InterneBelegnummer: '2026-000100', Fertig: 1, }); await runProcessSuccessfulTask(task, { related_document: 103 }, parentTask); const payload = lastPatchPayload(); expect(payload.document_type).toBe(5); expect(payload.title).toBe('Anlage zu 2026-000100'); expect(sortByField(payload.custom_fields)).toEqual([ { field: 8, value: [55] }, ]); }); });