Files
paperlessmanager/paperless-backend/src/paperless/task-metadata.util.spec.ts
T
bjoernpoettker beaa1be4a5
Build and Push Multi-Platform Images / build-and-push (push) Successful in 31s
feat(backend): Metadaten direkt beim Paperless-Upload übergeben
E-Mail-Import und External-Upload setzen ASN, Custom Fields (CF3/CF7/CF9),
Dokumenttyp, Tags, Owner und Belegdatum jetzt bereits beim post_document,
statt erst nachträglich über den Task-Processor-Cron (30 s + PATCH).

- Neu: deriveTaskMetadata() in task-metadata.util.ts als gemeinsame
  Ableitung (Task-Felder → Paperless-Metadaten) für beide Upload-Pfade
  und den Task-Processor – identische Parsing-Regeln, kein Copy-Paste
- Task-Processor nutzt den Helper (verhaltensidentischer Refactor) und
  patcht weiterhin idempotent nach; zuständig bleibt er für alles
  ID-Abhängige (CF8-Verknüpfungen, Anlage-Titel, Owner-Entfernung, Notizen)
- FAILURE-Zweig loggt Paperless-Fehlertext und Belegnummer vor dem
  Löschen des Tasks (ASN-Kollision kann jetzt den Consume scheitern lassen)
- Tests: 18 Unit-Tests für deriveTaskMetadata, 3 Charakterisierungstests
  pinnen den PATCH-Payload von processSuccessfulTask

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 22:46:42 +02:00

140 lines
4.9 KiB
TypeScript

import { deriveTaskMetadata } from './task-metadata.util';
describe('deriveTaskMetadata', () => {
describe('archiveSerialNumber', () => {
it('parst ASN aus Asn und entfernt alle Nicht-Ziffern', () => {
const result = deriveTaskMetadata({ Asn: 'ASN-123' });
expect(result.archiveSerialNumber).toBe(123);
});
it('leitet ASN aus InterneBelegnummer ab (nur Bindestriche entfernen)', () => {
const result = deriveTaskMetadata({ InterneBelegnummer: '2026-000123' });
expect(result.archiveSerialNumber).toBe(2026000123);
});
it('bevorzugt Asn vor InterneBelegnummer', () => {
const result = deriveTaskMetadata({
Asn: '42',
InterneBelegnummer: '2026-000123',
});
expect(result.archiveSerialNumber).toBe(42);
});
it('nutzt keinen Belegnummer-Fallback, wenn Asn gesetzt aber unparsebar ist', () => {
// Entspricht exakt der Processor-Logik: Fallback nur bei leerem Asn
const result = deriveTaskMetadata({
Asn: 'abc',
InterneBelegnummer: '2026-000123',
});
expect(result.archiveSerialNumber).toBeUndefined();
});
it('lässt ASN weg, wenn InterneBelegnummer nicht parsebar ist', () => {
const result = deriveTaskMetadata({ InterneBelegnummer: 'Beleg-X' });
expect(result.archiveSerialNumber).toBeUndefined();
});
});
describe('customFields (CF7/CF3/CF9)', () => {
it('setzt CF7 aus InterneBelegnummer', () => {
const result = deriveTaskMetadata({ InterneBelegnummer: '2026-000123' });
expect(result.customFields['7']).toBe('2026-000123');
});
it('setzt CF3 aus externeBelegnummer', () => {
const result = deriveTaskMetadata({ externeBelegnummer: 'RE-4711' });
expect(result.customFields['3']).toBe('RE-4711');
});
it('setzt CF9 aus Eingangsdatum als YYYY-MM-DD', () => {
const result = deriveTaskMetadata({
Eingangsdatum: new Date('2026-07-14T10:30:00Z'),
});
expect(result.customFields['9']).toBe('2026-07-14');
});
it('übernimmt CustomFieldsJson als Basis mit normalisierten numerischen Keys', () => {
const result = deriveTaskMetadata({
CustomFieldsJson: JSON.stringify({ '4': 'wert4', abc: 'ignoriert' }),
});
expect(result.customFields).toEqual({ '4': 'wert4' });
});
it('überschreibt CustomFieldsJson-Basis mit dedizierten Feldern CF7/CF3/CF9', () => {
const result = deriveTaskMetadata({
InterneBelegnummer: '2026-000123',
externeBelegnummer: 'RE-4711',
Eingangsdatum: new Date('2026-07-14T00:00:00Z'),
CustomFieldsJson: JSON.stringify({
'7': 'alt7',
'3': 'alt3',
'9': 'alt9',
}),
});
expect(result.customFields['7']).toBe('2026-000123');
expect(result.customFields['3']).toBe('RE-4711');
expect(result.customFields['9']).toBe('2026-07-14');
});
it('ignoriert ungültiges CustomFieldsJson still', () => {
const result = deriveTaskMetadata({
CustomFieldsJson: '{kein json',
InterneBelegnummer: '2026-000123',
});
expect(result.customFields).toEqual({ '7': '2026-000123' });
});
it('liefert leeres customFields-Objekt ohne Eingaben', () => {
const result = deriveTaskMetadata({});
expect(result.customFields).toEqual({});
});
});
describe('documentType / created / owner / tags', () => {
it('übernimmt DocumentType nur wenn truthy', () => {
expect(deriveTaskMetadata({ DocumentType: 5 }).documentType).toBe(5);
expect(
deriveTaskMetadata({ DocumentType: 0 }).documentType,
).toBeUndefined();
expect(
deriveTaskMetadata({ DocumentType: null }).documentType,
).toBeUndefined();
});
it('leitet created als ISO-String aus Belegdatum ab', () => {
const result = deriveTaskMetadata({
Belegdatum: new Date('2026-07-01T00:00:00Z'),
});
expect(result.created).toBe('2026-07-01T00:00:00.000Z');
});
it('setzt owner nur bei truthy BetriebID', () => {
expect(deriveTaskMetadata({ BetriebID: 3 }).owner).toBe(3);
expect(deriveTaskMetadata({ BetriebID: null }).owner).toBeUndefined();
expect(deriveTaskMetadata({}).owner).toBeUndefined();
});
it('parst Tags-CSV und filtert Ungültiges', () => {
const result = deriveTaskMetadata({ Tags: '1, 2, x' });
expect(result.tags).toEqual([1, 2]);
});
it('lässt tags bei leerem Tags-Feld weg', () => {
expect(deriveTaskMetadata({ Tags: null }).tags).toBeUndefined();
expect(deriveTaskMetadata({}).tags).toBeUndefined();
});
});
describe('leere Eingaben', () => {
it('lässt bei leeren Strings alle Felder weg', () => {
const result = deriveTaskMetadata({
InterneBelegnummer: '',
Asn: '',
externeBelegnummer: '',
Tags: '',
});
expect(result).toEqual({ customFields: {} });
});
});
});