package mailapi import ( "context" "net/http" "os" "strings" "testing" "github.com/getkin/kin-openapi/openapi3" "github.com/getkin/kin-openapi/routers/gorillamux" "gitea.perlbach24.de/scripte/nexarch/mail/internal/search" ) // TestOpenAPIDocument_ValidatesAgainstStandardTool ist die geforderte // Pflichtprüfung 4 (INT-01): Validierungslauf des OpenAPI-Dokuments // gegen ein Standardwerkzeug — github.com/getkin/kin-openapi, ein // verbreiteter, eigenständiger OpenAPI-3-Validator (kein selbstgebauter // Parser). func TestOpenAPIDocument_ValidatesAgainstStandardTool(t *testing.T) { loader := openapi3.NewLoader() doc, err := loader.LoadFromFile("openapi.yaml") if err != nil { t.Fatalf("openapi.yaml laden: %v", err) } if err := doc.Validate(context.Background()); err != nil { t.Fatalf("openapi.yaml ist gegen den Standardvalidator NICHT gültig: %v", err) } } // TestOpenAPIDocument_MatchesActualEndpoints ist die geforderte // Pflichtprüfung (Akzeptanzkriterium 4: "... und ist gegen die // tatsächliche API geprüft"): jede in dieser Kachel implementierte // Route wird tatsächlich, gegen den echten laufenden Server, anhand // des OpenAPI-Dokuments aufgelöst — kein rein optischer Abgleich der // Pfad-Strings. func TestOpenAPIDocument_MatchesActualEndpoints(t *testing.T) { loader := openapi3.NewLoader() doc, err := loader.LoadFromFile("openapi.yaml") if err != nil { t.Fatalf("openapi.yaml laden: %v", err) } if err := doc.Validate(context.Background()); err != nil { t.Fatalf("openapi.yaml validieren: %v", err) } router, err := gorillamux.NewRouter(doc) if err != nil { t.Fatalf("router aus openapi.yaml bauen: %v", err) } ts, sc, _ := setupTestServer(t) sc.put("mandant-a", search.Document{MessageID: "msg-1", Subject: "Test"}) cases := []struct { method string url string }{ {http.MethodGet, "/api/v1/mail/messages?tenant=mandant-a"}, {http.MethodGet, "/api/v1/mail/messages/msg-1?tenant=mandant-a"}, {http.MethodGet, "/api/v1/mail/messages/msg-1/attachments/0?tenant=mandant-a"}, } for _, c := range cases { t.Run(c.method+" "+c.url, func(t *testing.T) { req, err := http.NewRequest(c.method, ts.URL+c.url, nil) if err != nil { t.Fatalf("request bauen: %v", err) } route, _, err := router.FindRoute(req) if err != nil { t.Fatalf("route für %s %s nicht im OpenAPI-Dokument gefunden: %v", c.method, c.url, err) } if route == nil { t.Fatalf("keine route gefunden für %s %s", c.method, c.url) } }) } } // TestCodeReview_NoIAMRelatedHandlers ist die geforderte Pflichtprüfung // "Codereview bestätigt Abgrenzung zu Core-Board-Zuständigkeiten" // (Akzeptanzkriterium 3) — automatisiert statt nur behauptet: kein // Handler-/Routenname dieses Pakets enthält IAM-nahe Begriffe // (Login/Session/Token/Tenant-Verwaltung). func TestCodeReview_NoIAMRelatedHandlers(t *testing.T) { content, err := os.ReadFile("mailapi.go") if err != nil { t.Fatalf("mailapi.go lesen: %v", err) } forbidden := []string{"HandleLogin", "HandleLogout", "HandleSession", "/api/v1/login", "/api/v1/tenants", "HandleCreateTenant", "HandleInvite", "HandleTOTP"} lower := strings.ToLower(string(content)) for _, f := range forbidden { if strings.Contains(lower, strings.ToLower(f)) { t.Fatalf("mailapi.go enthält IAM-nahen bezeichner %q — gehört ins Core-Board, nicht in diese API", f) } } }