QA-01: teststrategie & testdatenbestand
CI / Core-Modul (Build/Vet/Test) (push) Failing after 3s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Successful in 2s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Failing after 58s
CI / Web (dms-browser) (push) Failing after 51s
CI / Web (pdf-viewer) (push) Failing after 45s
CI / Web (shl) (push) Failing after 20s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Failing after 2s

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 <noreply@anthropic.com>
This commit is contained in:
patrickandClaude Sonnet 5 committed 2026-09-18 14:56:50 +02:00
1 parent cbeff53ec5
commit 4e7ef754a0
8 files changed
+551 -5

No files matched your search

+33
View File
@@ -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)
}
}
+92
View File
@@ -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.
+70
View File
@@ -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
}
+110
View File
@@ -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)
}
}
+142
View File
@@ -0,0 +1,142 @@
package testcorpus
import (
"archive/zip"
"bytes"
"fmt"
)
const contentTypesDocx = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
</Types>`
const rootRelsDocx = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
</Relationships>`
const documentXMLTemplate = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body>%s</w:body>
</w:document>`
// 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("<w:p><w:r><w:t>%s</w:t></w:r></w:p>", 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 = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
</Types>`
const rootRelsXlsx = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
</Relationships>`
const workbookXML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
<sheets><sheet name="Tabelle1" sheetId="1" r:id="rId1"/></sheets>
</workbook>`
const workbookRels = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>
</Relationships>`
// 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 := `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>`
for r, row := range rows {
sheet += fmt.Sprintf(`<row r="%d">`, r+1)
for c, cell := range row {
ref := fmt.Sprintf("%s%d", columnLetter(c), r+1)
sheet += fmt.Sprintf(`<c r="%s" t="inlineStr"><is><t>%s</t></is></c>`, ref, escapeXML(cell))
}
sheet += `</row>`
}
sheet += `</sheetData></worksheet>`
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("&amp;")
case '<':
out.WriteString("&lt;")
case '>':
out.WriteString("&gt;")
default:
out.WriteRune(r)
}
}
return out.String()
}
+42
View File
@@ -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<</Length %d>>stream\n%s\nendstream endobj\n", len(content), content)
pdf := "%PDF-1.4\n" +
"1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj\n" +
"2 0 obj<</Type/Pages/Kids[3 0 R]/Count 1>>endobj\n" +
"3 0 obj<</Type/Page/Parent 2 0 R/MediaBox[0 0 595 842]/Contents 4 0 R/Resources<</Font<</F1 5 0 R>>>>>>endobj\n" +
obj4 +
"5 0 obj<</Type/Font/Subtype/Type1/BaseFont/Helvetica>>endobj\n" +
"xref\n0 6\n0000000000 65535 f \n" +
"trailer<</Size 6/Root 1 0 R/Info<</Title(" + escapePDFString(title) + ")>>>>\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)
}
+52
View File
@@ -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()
}
+10 -5
View File
@@ -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")/.."