From 4e7ef754a04e28aa1026d1cbbd875486390618ea Mon Sep 17 00:00:00 2001 From: patrick Date: Fri, 18 Sep 2026 14:56:50 +0200 Subject: [PATCH] QA-01: teststrategie & testdatenbestand Testpyramide, Abdeckungsziele und verbindliches Security-Merge-Gate schriftlich festgelegt (dms/docs/QA-01-TESTSTRATEGIE.md). Merge-Gate aus FDN-07 (check-security-test-coverage.sh) um drei real identifizierte, tenant-bezogene Pakete erweitert (documentapi, upload, versioning). Reproduzierbarer, vollstaendig synthetischer Referenzkorpus (PDF, Office, TIFF-Scan-Ersatz) ueber dms/internal/testcorpus + go run ./cmd/gen-test-corpus, mit Test, der Formatabdeckung und Parsbarkeit der erzeugten Dateien prueft. Co-Authored-By: Claude Sonnet 5 --- dms/cmd/gen-test-corpus/main.go | 33 +++++ dms/docs/QA-01-TESTSTRATEGIE.md | 92 +++++++++++++ dms/internal/testcorpus/corpus.go | 70 ++++++++++ dms/internal/testcorpus/corpus_test.go | 110 +++++++++++++++ dms/internal/testcorpus/office.go | 142 ++++++++++++++++++++ dms/internal/testcorpus/pdf.go | 42 ++++++ dms/internal/testcorpus/tiff.go | 52 +++++++ dms/scripts/check-security-test-coverage.sh | 15 ++- 8 files changed, 551 insertions(+), 5 deletions(-) create mode 100644 dms/cmd/gen-test-corpus/main.go create mode 100644 dms/docs/QA-01-TESTSTRATEGIE.md create mode 100644 dms/internal/testcorpus/corpus.go create mode 100644 dms/internal/testcorpus/corpus_test.go create mode 100644 dms/internal/testcorpus/office.go create mode 100644 dms/internal/testcorpus/pdf.go create mode 100644 dms/internal/testcorpus/tiff.go diff --git a/dms/cmd/gen-test-corpus/main.go b/dms/cmd/gen-test-corpus/main.go new file mode 100644 index 00000000..05dbeb3c --- /dev/null +++ b/dms/cmd/gen-test-corpus/main.go @@ -0,0 +1,33 @@ +// gen-test-corpus erzeugt den QA-01-Referenzkorpus reproduzierbar unter +// dms/testdata/corpus. Aufruf: go run ./cmd/gen-test-corpus +package main + +import ( + "fmt" + "os" + "path/filepath" + + "gitea.perlbach24.de/scripte/nexarch/dms/internal/testcorpus" +) + +func main() { + files, err := testcorpus.Generate() + if err != nil { + fmt.Fprintln(os.Stderr, "Korpus-Erzeugung fehlgeschlagen:", err) + os.Exit(1) + } + + root := "testdata/corpus" + for _, f := range files { + dst := filepath.Join(root, f.Path) + if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil { + fmt.Fprintln(os.Stderr, "Verzeichnis anlegen fehlgeschlagen:", err) + os.Exit(1) + } + if err := os.WriteFile(dst, f.Content, 0o644); err != nil { + fmt.Fprintln(os.Stderr, "Datei schreiben fehlgeschlagen:", err) + os.Exit(1) + } + fmt.Println("erzeugt:", dst) + } +} diff --git a/dms/docs/QA-01-TESTSTRATEGIE.md b/dms/docs/QA-01-TESTSTRATEGIE.md new file mode 100644 index 00000000..e05a6df0 --- /dev/null +++ b/dms/docs/QA-01-TESTSTRATEGIE.md @@ -0,0 +1,92 @@ +# QA-01 – Teststrategie DMS-Modul + +Welle 3. Baut auf `FDN-07` (CI-Pipeline & Testharness) auf und formalisiert +deren Merge-Gate-Mechanik zu einer schriftlichen Strategie, ohne sie neu zu +implementieren. + +## Testpyramide + +Stack: Go-Backend (`dms/`, Module pro fachlichem Bereich unter `internal/`) ++ Next.js-Frontends (`web/shl`, `web/dms-browser`, `web/pdf-viewer`). + +| Ebene | Anteil (Zielbild) | Was hier getestet wird | Werkzeug | +|---|---|---|---| +| Unit | ca. 70% | Einzelne Funktionen/Methoden innerhalb eines `internal/`-Pakets: Validierung, Zustandsübergänge, Fehlerpfade, reine Berechnungen (z. B. Checksummen, Diff-Logik in `internal/versioning`). Keine echte DB/kein echtes S3 nötig, wo Interfaces das zulassen. | `go test` | +| Integration | ca. 25% | Zusammenspiel mehrerer Pakete gegen eine echte, pro Lauf frisch aufgesetzte Testdatenbank (`scripts/reset-test-env.sh`, FDN-01/FDN-07-Konvention) bzw. echten Storage-Treiber: HTTP-Handler-Tests (`*api`-Pakete) mit echter Transaktion, Upload-Pfad Ende-zu-Ende innerhalb des Go-Prozesses. | `go test` gegen `NEXARCH_DMS_TEST_DB_PASSWORD`-DB | +| E2E | ca. 5% | Kompletter Durchlauf durch die Next.js-Oberflächen gegen ein laufendes Backend (Browser-Interaktion). Bewusst klein gehalten: teuer, langsam, wartungsintensiv – nur für die kritischsten Nutzerpfade (Login/Upload/Dokumentenansicht), sobald diese Kacheln existieren. | Vitest/Playwright-Äquivalent im `web/*`-Bereich, aktuell nur Komponenten-/Typtests (`vitest run --coverage`, `tsc --noEmit` laut `ci.yml`) | + +Begründung für die Gewichtung: Business-Logik (Tenant-Scoping, +Verschlüsselung, Zustandsmaschinen bei Versionierung/Retention) liegt fast +vollständig im Go-Backend; die Frontends sind überwiegend Darstellung +bestehender API-Daten. Ein hoher E2E-Anteil würde hier vor allem Laufzeit +kosten, ohne proportional mehr Fehler zu finden. + +## Abdeckungsziele + +- **Sicherheitsrelevante Pakete** (siehe unten): Pflicht, keine Zahl als + Ziel, sondern *jede* neue Funktion mit Tenant-Bezug/Zugriffsprüfung/ + kryptografischer Operation braucht einen Test, bevor sie gemerged wird + (siehe Merge-Gate unten). Das ist die schärfere, verbindliche Regel – + eine reine Prozentzahl (z. B. "80% Coverage") lässt sich erfüllen, ohne + den kritischen Pfad zu treffen. +- **Übrige `internal/`-Pakete**: Richtwert 60% Zeilenabdeckung + (`go tool cover -func`, bereits in `ci.yml` ausgewiesen), als Frühwarnsystem + für unbeabsichtigt untestierten Code – kein hartes Gate, weil pauschale + Prozentziele in Nicht-Kernpaketen leicht zu Alibi-Tests statt echter + Fehlervermeidung führen. +- **Frontend**: `tsc --noEmit` (Typsicherheit) + `vitest run --coverage` + als Basis, kein Zahlenziel in dieser Kachel festgelegt – gehört in die + jeweiligen Frontend-Kacheln. + +## Pflichttests für sicherheitsrelevanten Code (verbindliches Merge-Gate) + +Bekannter Fehler aus Vorgängerprojekten: Auth, Tenant-Scoping und +Protokoll-Zustandsmaschinen blieben trotz hoher Kritikalität oft ungetestet. +Deshalb gilt ab dieser Kachel verbindlich, nicht optional: + +> Kein Merge von Code in einem als sicherheitsrelevant eingestuften Paket +> ohne mindestens eine begleitende Testdatei mit mindestens einem +> `func Test...`. + +Technisch durchgesetzt über das bereits in FDN-07 eingeführte +`dms/scripts/check-security-test-coverage.sh`, als `dms-security-gate`-Job +in `.gitea/workflows/ci.yml` verankert und `dms-build-test` per `needs:` +nachgeschaltet (kein grüner Gesamtbuild ohne bestandenes Gate). Diese Kachel +erweitert die Paketliste um drei real per Tenant-Bezug (`TenantID`) +identifizierte Pakete, die in FDN-07 noch fehlten: + +``` +internal/crypto (bereits FDN-07) +internal/storage (bereits FDN-07) +internal/downloadapi (bereits FDN-07) +internal/versioningapi (bereits FDN-07) +internal/audittrail (bereits FDN-07) +internal/documentapi (neu, QA-01: Tenant-Bezug in handler.go) +internal/upload (neu, QA-01: Tenant-Bezug in service.go) +internal/versioning (neu, QA-01: Tenant-Bezug in versioning.go/diff.go) +``` + +Kein eigenständiges Auth-Paket existiert im Repo noch nicht (Platzhalter, +Liste bei dessen Einführung ergänzen – unverändert aus FDN-07 übernommen). + +Diese Kachel implementiert das Gate nicht neu, sondern erweitert nur dessen +Datenliste (kleinste Lösung, kein Umbau des Mechanismus). + +## Referenzkorpus + +Siehe `dms/testdata/corpus/` (erzeugt durch `go run ./cmd/gen-test-corpus` +aus `dms/internal/testcorpus`). Formate: PDF (Rechnung, Anschreiben), Office +(.docx, .xlsx), Scan-Ersatz (.tiff, da Scans im System laut DOC-19/INT-06 +als TIFF verarbeitet werden). Details und DSGVO-Erklärung siehe +`dms/docs/QA-01-PRUEFPROTOKOLL.md`. + +## Nicht Bestandteil dieser Kachel + +Konkrete Prüfgates für einzelne fachliche Bereiche (Dokumentenkern, Suche/ +Texterkennung, Metadaten/Klassifizierung, Workflow/Benachrichtigung, +Last-/Leistungstest) sind eigene Kacheln (QA-03, QA-04, QA-05, QA-07, +QA-10). Diese Strategie liefert nur den Rahmen (Pyramide, Abdeckungsziele, +Security-Gate) und den Referenzkorpus, auf dem jene Kacheln später ihre +eigenen, fachspezifischen Prüfungen aufbauen können – der Korpus in +`dms/testdata/corpus/` ist bewusst so angelegt, dass er von künftigen +Prüfgate-Kacheln direkt als Eingabedaten wiederverwendet werden kann. diff --git a/dms/internal/testcorpus/corpus.go b/dms/internal/testcorpus/corpus.go new file mode 100644 index 00000000..cb251149 --- /dev/null +++ b/dms/internal/testcorpus/corpus.go @@ -0,0 +1,70 @@ +package testcorpus + +// File ist eine erzeugte Korpus-Datei mit ihrem relativen Zielpfad. +type File struct { + Path string + Content []byte +} + +// Generate baut den vollstaendigen Referenzkorpus. Alle Inhalte sind frei +// erfunden (fiktive Firma "Musterhandel Nordlicht GmbH", fiktive Personen) - +// keine Ableitung aus echten Kunden-/Mandantendaten. +func Generate() ([]File, error) { + var files []File + + invoicePDF := GeneratePDF("Rechnung 2026-0001", []string{ + "Musterhandel Nordlicht GmbH", + "Fjordweg 12, 24939 Fiktivstadt", + "", + "Rechnung Nr. 2026-0001 vom 03.02.2026", + "Kunde: Anna Testperson, Beispielallee 7, 20099 Fiktivhafen", + "", + "Pos. 1 Bueromaterial-Set 1 Stk. 42,50 EUR", + "Pos. 2 Aktenordner A4 10 Stk. 35,00 EUR", + "", + "Gesamtbetrag: 77,50 EUR", + }) + files = append(files, File{Path: "pdf/rechnung-muster-2026-0001.pdf", Content: invoicePDF}) + + letterPDF := GeneratePDF("Schreiben Aktenzeichen FIK-2026-042", []string{ + "Kanzlei Musterrecht Partnerschaft mbB", + "Domplatz 3, 33098 Fiktivburg", + "", + "Ihr Aktenzeichen: FIK-2026-042", + "Sehr geehrter Herr Beispielmann,", + "", + "hiermit bestaetigen wir den Eingang Ihrer Unterlagen vom 10.01.2026.", + "Wir werden uns bis zum 28.02.2026 mit weiteren Informationen melden.", + "", + "Mit freundlichen Gruessen", + "Dr. Petra Fiktivnam", + }) + files = append(files, File{Path: "pdf/schreiben-muster-fik-2026-042.pdf", Content: letterPDF}) + + docx, err := GenerateDocx([]string{ + "Widerrufsbelehrung (Muster)", + "Musterhandel Nordlicht GmbH, Fjordweg 12, 24939 Fiktivstadt", + "Sie haben das Recht, binnen vierzehn Tagen ohne Angabe von Gruenden diesen Vertrag zu widerrufen.", + "Die Widerrufsfrist betraegt vierzehn Tage ab dem Tag des Vertragsabschlusses.", + }) + if err != nil { + return nil, err + } + files = append(files, File{Path: "office/widerrufsbelehrung-muster.docx", Content: docx}) + + xlsx, err := GenerateXlsx([][]string{ + {"Position", "Artikel", "Menge", "Einzelpreis", "Gesamt"}, + {"1", "Bueromaterial-Set", "1", "42,50", "42,50"}, + {"2", "Aktenordner A4", "10", "3,50", "35,00"}, + {"", "", "", "Summe", "77,50"}, + }) + if err != nil { + return nil, err + } + files = append(files, File{Path: "office/rechnungspositionen-muster.xlsx", Content: xlsx}) + + scan := GenerateTIFF(600, 800, 0xE8) + files = append(files, File{Path: "scan/scan-muster-eingangspost-001.tiff", Content: scan}) + + return files, nil +} diff --git a/dms/internal/testcorpus/corpus_test.go b/dms/internal/testcorpus/corpus_test.go new file mode 100644 index 00000000..0c2c440f --- /dev/null +++ b/dms/internal/testcorpus/corpus_test.go @@ -0,0 +1,110 @@ +package testcorpus + +import ( + "archive/zip" + "bytes" + "encoding/binary" + "strings" + "testing" +) + +func TestGenerate_AllFormatsCoveredAndParseable(t *testing.T) { + files, err := Generate() + if err != nil { + t.Fatalf("Generate: %v", err) + } + + wantExt := map[string]bool{".pdf": false, ".docx": false, ".xlsx": false, ".tiff": false} + for _, f := range files { + for ext := range wantExt { + if strings.HasSuffix(f.Path, ext) { + wantExt[ext] = true + } + } + } + for ext, found := range wantExt { + if !found { + t.Errorf("kein Korpus-Eintrag mit Endung %s", ext) + } + } + + for _, f := range files { + switch { + case strings.HasSuffix(f.Path, ".pdf"): + assertValidPDF(t, f.Path, f.Content) + case strings.HasSuffix(f.Path, ".docx"), strings.HasSuffix(f.Path, ".xlsx"): + assertValidOOXMLZip(t, f.Path, f.Content) + case strings.HasSuffix(f.Path, ".tiff"): + assertValidTIFF(t, f.Path, f.Content) + } + } +} + +func assertValidPDF(t *testing.T, path string, content []byte) { + t.Helper() + if !bytes.HasPrefix(content, []byte("%PDF-")) { + t.Errorf("%s: fehlender PDF-Header", path) + } + if !bytes.Contains(content, []byte("%%EOF")) { + t.Errorf("%s: fehlendes PDF-EOF-Marker", path) + } +} + +func assertValidOOXMLZip(t *testing.T, path string, content []byte) { + t.Helper() + r, err := zip.NewReader(bytes.NewReader(content), int64(len(content))) + if err != nil { + t.Fatalf("%s: kein gueltiges Zip: %v", path, err) + } + hasContentTypes := false + for _, f := range r.File { + if f.Name == "[Content_Types].xml" { + hasContentTypes = true + } + } + if !hasContentTypes { + t.Errorf("%s: [Content_Types].xml fehlt, kein gueltiges OOXML-Paket", path) + } +} + +func assertValidTIFF(t *testing.T, path string, content []byte) { + t.Helper() + if len(content) < 8 || string(content[:2]) != "II" { + t.Fatalf("%s: fehlender TIFF-Byteorder-Header", path) + } + magic := binary.LittleEndian.Uint16(content[2:4]) + if magic != 42 { + t.Errorf("%s: falsches TIFF-Magic %d", path, magic) + } + ifdOffset := binary.LittleEndian.Uint32(content[4:8]) + if int(ifdOffset) >= len(content) { + t.Fatalf("%s: IFD-Offset ausserhalb der Datei", path) + } + numTags := binary.LittleEndian.Uint16(content[ifdOffset : ifdOffset+2]) + if numTags == 0 { + t.Errorf("%s: keine IFD-Tags", path) + } +} + +func TestGenerateTIFF_DimensionsRoundtrip(t *testing.T) { + data := GenerateTIFF(10, 5, 0x80) + + ifdOffset := binary.LittleEndian.Uint32(data[4:8]) + numTags := binary.LittleEndian.Uint16(data[ifdOffset : ifdOffset+2]) + + var width, height uint32 + for i := 0; i < int(numTags); i++ { + entryOff := ifdOffset + 2 + uint32(i*12) + tag := binary.LittleEndian.Uint16(data[entryOff : entryOff+2]) + value := binary.LittleEndian.Uint32(data[entryOff+8 : entryOff+12]) + switch tag { + case 256: + width = value + case 257: + height = value + } + } + if width != 10 || height != 5 { + t.Errorf("Breite/Hoehe nicht rekonstruierbar: got %dx%d, want 10x5", width, height) + } +} diff --git a/dms/internal/testcorpus/office.go b/dms/internal/testcorpus/office.go new file mode 100644 index 00000000..ab0da179 --- /dev/null +++ b/dms/internal/testcorpus/office.go @@ -0,0 +1,142 @@ +package testcorpus + +import ( + "archive/zip" + "bytes" + "fmt" +) + +const contentTypesDocx = ` + + + + +` + +const rootRelsDocx = ` + + +` + +const documentXMLTemplate = ` + +%s +` + +// GenerateDocx baut ein minimal gueltiges .docx (OOXML-Zip) mit den +// uebergebenen Absaetzen als Flietext. +func GenerateDocx(paragraphs []string) ([]byte, error) { + body := "" + for _, p := range paragraphs { + body += fmt.Sprintf("%s", escapeXML(p)) + } + documentXML := fmt.Sprintf(documentXMLTemplate, body) + + buf := &bytes.Buffer{} + zw := zip.NewWriter(buf) + files := map[string]string{ + "[Content_Types].xml": contentTypesDocx, + "_rels/.rels": rootRelsDocx, + "word/document.xml": documentXML, + } + for name, content := range files { + w, err := zw.Create(name) + if err != nil { + return nil, err + } + if _, err := w.Write([]byte(content)); err != nil { + return nil, err + } + } + if err := zw.Close(); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +const contentTypesXlsx = ` + + + + + +` + +const rootRelsXlsx = ` + + +` + +const workbookXML = ` + + +` + +const workbookRels = ` + + +` + +// GenerateXlsx baut eine minimal gueltige .xlsx mit den uebergebenen Zeilen +// als Inline-Strings (kein Shared-String-Table, absichtlich einfach). +func GenerateXlsx(rows [][]string) ([]byte, error) { + sheet := ` +` + for r, row := range rows { + sheet += fmt.Sprintf(``, r+1) + for c, cell := range row { + ref := fmt.Sprintf("%s%d", columnLetter(c), r+1) + sheet += fmt.Sprintf(`%s`, ref, escapeXML(cell)) + } + sheet += `` + } + sheet += `` + + buf := &bytes.Buffer{} + zw := zip.NewWriter(buf) + files := map[string]string{ + "[Content_Types].xml": contentTypesXlsx, + "_rels/.rels": rootRelsXlsx, + "xl/workbook.xml": workbookXML, + "xl/_rels/workbook.xml.rels": workbookRels, + "xl/worksheets/sheet1.xml": sheet, + } + for name, content := range files { + w, err := zw.Create(name) + if err != nil { + return nil, err + } + if _, err := w.Write([]byte(content)); err != nil { + return nil, err + } + } + if err := zw.Close(); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func columnLetter(idx int) string { + letters := "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + if idx < 26 { + return string(letters[idx]) + } + return string(letters[idx/26-1]) + string(letters[idx%26]) +} + +func escapeXML(s string) string { + out := bytes.Buffer{} + for _, r := range s { + switch r { + case '&': + out.WriteString("&") + case '<': + out.WriteString("<") + case '>': + out.WriteString(">") + default: + out.WriteRune(r) + } + } + return out.String() +} diff --git a/dms/internal/testcorpus/pdf.go b/dms/internal/testcorpus/pdf.go new file mode 100644 index 00000000..df1f5410 --- /dev/null +++ b/dms/internal/testcorpus/pdf.go @@ -0,0 +1,42 @@ +// Package testcorpus erzeugt einen synthetischen, reproduzierbaren +// Referenzkorpus fuer QA-01 (Testpyramide/Testdatenbestand). Alle Inhalte +// sind frei erfunden (Firmennamen, Personen, Betraege) - keine Ableitung aus +// echten Kunden-/Mandantendaten (DSGVO-Vorgabe des Projekts). +package testcorpus + +import "fmt" + +// GeneratePDF baut ein minimal gueltiges PDF (Struktur wie thumbnail_test.go +// minimalValidPDF) mit einem eingebetteten Textinhalt in der Content-Stream. +func GeneratePDF(title string, lines []string) []byte { + content := "BT /F1 12 Tf 50 700 Td\n" + for _, l := range lines { + content += fmt.Sprintf("(%s) Tj 0 -18 Td\n", escapePDFString(l)) + } + content += "ET" + + obj4 := fmt.Sprintf("4 0 obj<>stream\n%s\nendstream endobj\n", len(content), content) + + pdf := "%PDF-1.4\n" + + "1 0 obj<>endobj\n" + + "2 0 obj<>endobj\n" + + "3 0 obj<>>>>>endobj\n" + + obj4 + + "5 0 obj<>endobj\n" + + "xref\n0 6\n0000000000 65535 f \n" + + "trailer<>>>\n" + + "startxref\n0\n%%EOF" + + return []byte(pdf) +} + +func escapePDFString(s string) string { + out := make([]byte, 0, len(s)) + for _, b := range []byte(s) { + if b == '(' || b == ')' || b == '\\' { + out = append(out, '\\') + } + out = append(out, b) + } + return string(out) +} diff --git a/dms/internal/testcorpus/tiff.go b/dms/internal/testcorpus/tiff.go new file mode 100644 index 00000000..c5947826 --- /dev/null +++ b/dms/internal/testcorpus/tiff.go @@ -0,0 +1,52 @@ +package testcorpus + +import ( + "bytes" + "encoding/binary" +) + +// GenerateTIFF baut ein minimal gueltiges Baseline-TIFF (unkomprimiert, +// 1 Byte/Pixel Graustufen) als Ersatz fuer einen eingescannten Beleg. +// Byteorder little-endian ("II"), eine einzige IFD, ein Strip. +func GenerateTIFF(width, height int, fill byte) []byte { + pixelData := bytes.Repeat([]byte{fill}, width*height) + + const numTags = 8 + ifdOffset := uint32(8) + ifdSize := uint32(2 + numTags*12 + 4) + pixelOffset := ifdOffset + ifdSize + + buf := &bytes.Buffer{} + buf.WriteString("II") + binary.Write(buf, binary.LittleEndian, uint16(42)) + binary.Write(buf, binary.LittleEndian, ifdOffset) + + binary.Write(buf, binary.LittleEndian, uint16(numTags)) + + writeTag := func(tag, typ uint16, count, value uint32) { + binary.Write(buf, binary.LittleEndian, tag) + binary.Write(buf, binary.LittleEndian, typ) + binary.Write(buf, binary.LittleEndian, count) + binary.Write(buf, binary.LittleEndian, value) + } + + const ( + typeShort = 3 + typeLong = 4 + ) + + writeTag(256, typeLong, 1, uint32(width)) // ImageWidth + writeTag(257, typeLong, 1, uint32(height)) // ImageLength + writeTag(258, typeShort, 1, 8) // BitsPerSample + writeTag(259, typeShort, 1, 1) // Compression: none + writeTag(262, typeShort, 1, 1) // PhotometricInterpretation: BlackIsZero + writeTag(273, typeLong, 1, pixelOffset) // StripOffsets + writeTag(277, typeShort, 1, 1) // SamplesPerPixel + writeTag(279, typeLong, 1, uint32(len(pixelData))) // StripByteCounts + + binary.Write(buf, binary.LittleEndian, uint32(0)) // naechste IFD: keine + + buf.Write(pixelData) + + return buf.Bytes() +} diff --git a/dms/scripts/check-security-test-coverage.sh b/dms/scripts/check-security-test-coverage.sh index 28aa61f0..79319869 100755 --- a/dms/scripts/check-security-test-coverage.sh +++ b/dms/scripts/check-security-test-coverage.sh @@ -1,9 +1,11 @@ #!/usr/bin/env bash -# Merge-Gate FDN-07 Akzeptanzkriterium 5: sicherheitsrelevante Pakete -# (Verschluesselung, Tenant-Scoping/Speicher, Berechtigungspruefung) muessen -# mindestens eine Testdatei mit mindestens einer func Test... besitzen. -# Kein eigenstaendiges Auth-Paket vorhanden (noch nicht implementiert) - -# Liste bei dessen Einfuehrung ergaenzen. +# Merge-Gate FDN-07 Akzeptanzkriterium 5 / QA-01 Akzeptanzkriterium 4: +# sicherheitsrelevante Pakete (Verschluesselung, Tenant-Scoping/Speicher, +# Berechtigungspruefung, Auth-nahe Logik) muessen mindestens eine Testdatei +# mit mindestens einer func Test... besitzen. Liste um documentapi/upload/ +# versioning erweitert (QA-01, echte Tenant-Scoping-Treffer per grep +# bestaetigt). Kein eigenstaendiges Auth-Paket vorhanden (noch nicht +# implementiert) - Liste bei dessen Einfuehrung ergaenzen. set -euo pipefail SECURITY_PACKAGES=( @@ -12,6 +14,9 @@ SECURITY_PACKAGES=( "internal/downloadapi" "internal/versioningapi" "internal/audittrail" + "internal/documentapi" + "internal/upload" + "internal/versioning" ) cd "$(dirname "$0")/.."