// Package pflichttestgate erzwingt die in docs/TESTSTRATEGIE-MAIL.md // Abschnitt 4 festgelegte Regel: jede geänderte Go-Datei in einem // sicherheitskritischen Bereich (Auth, Tenant-Scoping, Protokoll-/ // Compliance-kritisch) muss von einer geänderten oder neuen _test.go- // Datei im selben Package begleitet sein. Bewusste Code-Kopie des // Musters aus Core internal/pflichttestgate — Mail ist ein eigenständiges // Go-Modul und kann Core nicht importieren. package pflichttestgate import ( "path" "regexp" "strings" ) // sensitivePathPatterns beschreibt die Bereiche aus // TESTSTRATEGIE-MAIL.md Abschnitt 4. var sensitivePathPatterns = []*regexp.Regexp{ regexp.MustCompile(`(^|/)mail/internal/auth/`), regexp.MustCompile(`(^|/)mail/internal/tenant/`), regexp.MustCompile(`(^|/)mail/internal/ingest/`), regexp.MustCompile(`(^|/)mail/internal/imap/`), regexp.MustCompile(`(^|/)mail/internal/smtp/`), regexp.MustCompile(`(^|/)mail/internal/arc/`), } // Violation beschreibt ein Package mit sicherheitskritischer Änderung // ohne begleitende Testdatei. type Violation struct { Package string ChangedFile string } func isSensitive(file string) bool { if !strings.HasSuffix(file, ".go") || strings.HasSuffix(file, "_test.go") { return false } for _, re := range sensitivePathPatterns { if re.MatchString(file) { return true } } return false } // CheckDiff prüft eine Liste geänderter Dateipfade gegen die // Pflichttest-Regel — ein leeres Ergebnis bedeutet: Gate besteht. func CheckDiff(changedFiles []string) []Violation { sensitiveByPkg := map[string]string{} testTouchedPkgs := map[string]bool{} for _, f := range changedFiles { pkg := path.Dir(f) if strings.HasSuffix(f, "_test.go") { testTouchedPkgs[pkg] = true continue } if isSensitive(f) { if _, seen := sensitiveByPkg[pkg]; !seen { sensitiveByPkg[pkg] = f } } } var violations []Violation for pkg, file := range sensitiveByPkg { if !testTouchedPkgs[pkg] { violations = append(violations, Violation{Package: pkg, ChangedFile: file}) } } return violations }