// Package pflichttestgate erzwingt die in docs/TESTSTRATEGIE-CORE.md Abschnitt 3 festgelegte Regel: // jede geänderte Go-Datei in einem sicherheitskritischen Bereich (Auth, Tenant-Scoping, Policy) // muss von einer geänderten oder neuen _test.go-Datei im selben Package begleitet sein. package pflichttestgate import ( "path" "regexp" "strings" ) // sensitivePathPatterns beschreibt die Bereiche aus TESTSTRATEGIE-CORE.md Abschnitt 3. var sensitivePathPatterns = []*regexp.Regexp{ regexp.MustCompile(`(^|/)internal/auth/`), regexp.MustCompile(`(^|/)internal/iam/`), regexp.MustCompile(`(^|/)internal/tenant/`), regexp.MustCompile(`(^|/)internal/rbac/`), regexp.MustCompile(`(^|/)internal/policy/`), } // Violation beschreibt ein Package mit sicherheitskritischer Änderung ohne begleitende Testdatei. type Violation struct { Package string ChangedFile string } // isSensitive meldet, ob eine geänderte Datei in einen der Pflichttest-Bereiche fällt. 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 (z. B. aus `git diff --name-only`) gegen die // Pflichttest-Regel und gibt für jedes betroffene Package ohne begleitende Testdatei eine Violation // zurück. Ein leeres Ergebnis bedeutet: Gate besteht. func CheckDiff(changedFiles []string) []Violation { sensitiveByPkg := map[string]string{} // package -> erste sensible Datei (fürs Reporting) 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 }