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 (Lint/Build/Test/Coverage/Artefakt) (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
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
193 lines
7.0 KiB
Go
193 lines
7.0 KiB
Go
package watchfolder
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"gitea.perlbach24.de/scripte/nexarch/dms/internal/batchsplit"
|
|
"gitea.perlbach24.de/scripte/nexarch/dms/internal/metadatafield"
|
|
"gitea.perlbach24.de/scripte/nexarch/dms/internal/testcorpus"
|
|
)
|
|
|
|
const scanPrefix = "NXSEP:"
|
|
|
|
func scanText() testcorpus.ScanPage { return testcorpus.ScanPage{Kind: testcorpus.ScanText} }
|
|
func scanBlank() testcorpus.ScanPage {
|
|
return testcorpus.ScanPage{Kind: testcorpus.ScanBlank}
|
|
}
|
|
func scanCode(v string) testcorpus.ScanPage {
|
|
return testcorpus.ScanPage{Kind: testcorpus.ScanCode128, Value: scanPrefix + v}
|
|
}
|
|
|
|
func scanEnv(t *testing.T, fieldType string) (*guardEnv, string) {
|
|
t.Helper()
|
|
g := newGuardEnv(t, "acme")
|
|
classifier := batchsplit.Chain{
|
|
batchsplit.DetectorClassifier{Detector: batchsplit.MarkerDetector{}},
|
|
batchsplit.BarcodeClassifier{Prefix: scanPrefix},
|
|
batchsplit.BlankClassifier{},
|
|
}
|
|
g.w.cfg.Splitter = func(b []byte) ([]batchsplit.Part, error) {
|
|
return batchsplit.SplitParts(b, classifier, batchsplit.PopplerRenderer{})
|
|
}
|
|
g.w.cfg.Deliveries = batchsplit.NewStore(g.e.Pool)
|
|
f, err := metadatafield.Create(context.Background(), g.e.Pool, "belegnummer", "Belegnummer", fieldType, false, nil, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
g.w.cfg.OnPartValue = func(ctx context.Context, documentID, value string) error {
|
|
return metadatafield.SetValue(ctx, g.e.Pool, documentID, f.ID, value)
|
|
}
|
|
return g, f.ID
|
|
}
|
|
|
|
func (g *guardEnv) dropScan(t *testing.T, name string, pages ...testcorpus.ScanPage) string {
|
|
t.Helper()
|
|
b, err := testcorpus.GenerateScanStack(pages)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
p := filepath.Join(g.dir, name)
|
|
if err := os.WriteFile(p, b, 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return p
|
|
}
|
|
|
|
func (g *guardEnv) valueOf(t *testing.T, title, fieldID string) string {
|
|
t.Helper()
|
|
var id string
|
|
if err := g.e.Pool.QueryRow(context.Background(), `SELECT id FROM documents WHERE title = $1`, title).Scan(&id); err != nil {
|
|
t.Fatalf("dokument %q: %v", title, err)
|
|
}
|
|
v, err := metadatafield.ValueFor(context.Background(), g.e.Pool, id, fieldID)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return v
|
|
}
|
|
|
|
// AK1: Leerseiten trennen einen gescannten Stapel im Hot-Folder in Einzeldokumente.
|
|
func TestScanBatch_BlankPagesSplitStack(t *testing.T) {
|
|
g, _ := scanEnv(t, metadatafield.TypeText)
|
|
src := g.dropScan(t, "scan.pdf", scanText(), scanText(), scanBlank(), scanText(), scanBlank(), scanText())
|
|
g.w.ScanOnce(context.Background())
|
|
got := g.titles(t)
|
|
want := []string{"scan-teil-01.pdf", "scan-teil-02.pdf", "scan-teil-03.pdf"}
|
|
if len(got) != len(want) {
|
|
t.Fatalf("dokumente = %v, want %v", got, want)
|
|
}
|
|
for i := range want {
|
|
if got[i] != want[i] {
|
|
t.Fatalf("dokumente = %v, want %v", got, want)
|
|
}
|
|
}
|
|
if exists(src) {
|
|
t.Fatal("quelldatei nach erfolgreicher trennung nicht entfernt")
|
|
}
|
|
}
|
|
|
|
// AK2: der Barcode-Wert der Trennseite wird als Metadatenfeld des folgenden Dokuments uebernommen.
|
|
func TestScanBatch_BarcodeValueIsStoredAsMetadata(t *testing.T) {
|
|
g, fieldID := scanEnv(t, metadatafield.TypeText)
|
|
g.dropScan(t, "post.pdf", scanCode("B-100"), scanText(), scanText(), scanCode("B-200"), scanText())
|
|
g.w.ScanOnce(context.Background())
|
|
if titles := g.titles(t); len(titles) != 2 {
|
|
t.Fatalf("dokumente = %v, want 2", titles)
|
|
}
|
|
if v := g.valueOf(t, "post-teil-01.pdf", fieldID); v != "B-100" {
|
|
t.Errorf("teil 1: belegnummer = %q, want B-100", v)
|
|
}
|
|
if v := g.valueOf(t, "post-teil-02.pdf", fieldID); v != "B-200" {
|
|
t.Errorf("teil 2: belegnummer = %q, want B-200", v)
|
|
}
|
|
}
|
|
|
|
// Ohne Metadatenfeld-Hook wird trotzdem getrennt (Wert optional).
|
|
func TestScanBatch_BarcodeSplitsWithoutMetadataHook(t *testing.T) {
|
|
g, _ := scanEnv(t, metadatafield.TypeText)
|
|
g.w.cfg.OnPartValue = nil
|
|
g.dropScan(t, "ohne.pdf", scanText(), scanCode("X"), scanText())
|
|
g.w.ScanOnce(context.Background())
|
|
if titles := g.titles(t); len(titles) != 2 {
|
|
t.Fatalf("dokumente = %v, want 2", titles)
|
|
}
|
|
}
|
|
|
|
// Trennseite nur am Anfang: das Dokument darf die Trennseite nicht enthalten und bekommt den Wert.
|
|
func TestScanBatch_LeadingSeparatorIsNotImportedWithDocument(t *testing.T) {
|
|
g, fieldID := scanEnv(t, metadatafield.TypeText)
|
|
src := g.dropScan(t, "kopf.pdf", scanCode("K-1"), scanText(), scanText())
|
|
g.w.ScanOnce(context.Background())
|
|
if titles := g.titles(t); len(titles) != 1 || titles[0] != "kopf-teil-01.pdf" {
|
|
t.Fatalf("dokumente = %v, want [kopf-teil-01.pdf]", titles)
|
|
}
|
|
if v := g.valueOf(t, "kopf-teil-01.pdf", fieldID); v != "K-1" {
|
|
t.Errorf("belegnummer = %q, want K-1", v)
|
|
}
|
|
if exists(src) {
|
|
t.Fatal("quelldatei nicht entfernt")
|
|
}
|
|
}
|
|
|
|
// AK3: Stapel ohne Trennseite bleibt ein Dokument mit unveraendertem Titel.
|
|
func TestScanBatch_WithoutSeparatorsStaysOneDocument(t *testing.T) {
|
|
g, _ := scanEnv(t, metadatafield.TypeText)
|
|
g.dropScan(t, "einzeln.pdf", scanText(), scanText(), scanText())
|
|
g.w.ScanOnce(context.Background())
|
|
if titles := g.titles(t); len(titles) != 1 || titles[0] != "einzeln.pdf" {
|
|
t.Fatalf("dokumente = %v, want [einzeln.pdf]", titles)
|
|
}
|
|
}
|
|
|
|
// Ein ungueltiger Barcode-Wert fuer das Feld (Typ Zahl) verhindert den Import nicht.
|
|
func TestScanBatch_InvalidMetadataValueDoesNotBlockImport(t *testing.T) {
|
|
g, fieldID := scanEnv(t, metadatafield.TypeNumber)
|
|
g.dropScan(t, "zahl.pdf", scanCode("keine-zahl"), scanText(), scanCode("42"), scanText())
|
|
g.w.ScanOnce(context.Background())
|
|
if titles := g.titles(t); len(titles) != 2 {
|
|
t.Fatalf("dokumente = %v, want 2", titles)
|
|
}
|
|
if v := g.valueOf(t, "zahl-teil-01.pdf", fieldID); v != "" {
|
|
t.Errorf("ungueltiger wert wurde uebernommen: %q", v)
|
|
}
|
|
if v := g.valueOf(t, "zahl-teil-02.pdf", fieldID); v != "42" {
|
|
t.Errorf("gueltiger wert = %q, want 42", v)
|
|
}
|
|
}
|
|
|
|
// Fehlt das Rasterwerkzeug, landet der Stapel im Fehlerordner, statt ungeteilt importiert zu werden.
|
|
func TestScanBatch_MissingRendererMovesBatchToErrorFolder(t *testing.T) {
|
|
g, _ := scanEnv(t, metadatafield.TypeText)
|
|
classifier := batchsplit.Chain{batchsplit.BlankClassifier{}}
|
|
g.w.cfg.Splitter = func(b []byte) ([]batchsplit.Part, error) {
|
|
return batchsplit.SplitParts(b, classifier, batchsplit.PopplerRenderer{Binary: "/nicht/vorhanden/pdftoppm"})
|
|
}
|
|
g.dropScan(t, "kein-tool.pdf", scanText(), scanBlank(), scanText())
|
|
g.w.ScanOnce(context.Background())
|
|
if !exists(filepath.Join(g.dir, errorSubdir, "kein-tool.pdf")) {
|
|
t.Fatal("stapel ohne rasterwerkzeug nicht im fehlerordner")
|
|
}
|
|
if titles := g.titles(t); len(titles) != 0 {
|
|
t.Fatalf("dokumente = %v, want keine", titles)
|
|
}
|
|
}
|
|
|
|
// Erneut eingelieferter Stapel erzeugt keine Dubletten (Lieferregister greift auch bei Bild-Stapeln).
|
|
func TestScanBatch_RedeliveryCreatesNoDuplicates(t *testing.T) {
|
|
g, _ := scanEnv(t, metadatafield.TypeText)
|
|
pages := []testcorpus.ScanPage{scanText(), scanBlank(), scanText()}
|
|
g.dropScan(t, "wieder.pdf", pages...)
|
|
g.w.ScanOnce(context.Background())
|
|
g.dropScan(t, "wieder.pdf", pages...)
|
|
g.w.ScanOnce(context.Background())
|
|
if titles := g.titles(t); len(titles) != 2 {
|
|
t.Fatalf("dokumente = %v, want 2", titles)
|
|
}
|
|
if !exists(filepath.Join(g.dir, duplicateSubdir, "wieder.pdf")) {
|
|
t.Fatal("erneute lieferung nicht im dublettenordner")
|
|
}
|
|
}
|