package swagger import ( "os" "path/filepath" "regexp" "runtime" "sort" "strings" "testing" ) // TestRouteDocs_CoversEveryRouterEntry parses the tenant-api router source and // asserts that every registered route has a corresponding entry in routeDocs. // This is a static-analysis safety net: when a new handler is wired up in // tenant or ops transport routers, this test fails until a Chinese // summary is added. func TestRouteDocs_CoversEveryRouterEntry(t *testing.T) { routerPaths, bootstrapPath := findRepoSources(t) routes := parseEngineRoutes(t, bootstrapPath) for _, routerPath := range routerPaths { routes = append(routes, parseRoutes(t, routerPath)...) } if len(routes) == 0 { t.Fatalf("could not extract any routes from %s", strings.Join(routerPaths, ", ")) } missing := make([]string, 0) for _, r := range routes { if shouldSkipRoute(r.path) { continue } if _, ok := routeDocs[r.method+" "+r.path]; !ok { missing = append(missing, r.method+" "+r.path) } } sort.Strings(missing) if len(missing) > 0 { t.Fatalf("routeDocs is missing %d route(s):\n %s", len(missing), strings.Join(missing, "\n ")) } registered := map[string]struct{}{} for _, r := range routes { registered[r.method+" "+r.path] = struct{}{} } extras := make([]string, 0) for k := range routeDocs { if strings.HasPrefix(k, "GET /swagger") { continue } if _, ok := registered[k]; !ok { extras = append(extras, k) } } sort.Strings(extras) if len(extras) > 0 { t.Fatalf("routeDocs has %d entry/entries with no matching route (renamed or removed):\n %s", len(extras), strings.Join(extras, "\n ")) } } type parsedRoute struct { method string path string } var ( groupRe = regexp.MustCompile(`(\w+)\s*:?=\s*([\w.]+)\.Group\("([^"]*)"\)`) methodVarRe = regexp.MustCompile(`\b(\w+)\.(GET|POST|PUT|PATCH|DELETE)\("([^"]*)"`) methodEngRe = regexp.MustCompile(`\b(?:app\.Engine|engine)\.(GET|POST|PUT|PATCH|DELETE)\("([^"]*)"`) ) func parseRoutes(t *testing.T, path string) []parsedRoute { t.Helper() src, err := os.ReadFile(path) if err != nil { t.Fatalf("read %s: %v", path, err) } text := string(src) prefixes := map[string][2]string{} // var -> [parent, prefix] for _, line := range strings.Split(text, "\n") { if m := groupRe.FindStringSubmatch(line); m != nil { prefixes[m[1]] = [2]string{m[2], m[3]} } } resolve := func(v string) string { var parts []string for { pp, ok := prefixes[v] if !ok { break } parts = append([]string{pp[1]}, parts...) v = pp[0] } return strings.Join(parts, "") } routes := make([]parsedRoute, 0) seen := map[string]struct{}{} add := func(method, fullPath string) { key := method + " " + fullPath if _, ok := seen[key]; ok { return } seen[key] = struct{}{} routes = append(routes, parsedRoute{method: method, path: fullPath}) } for _, line := range strings.Split(text, "\n") { for _, m := range methodVarRe.FindAllStringSubmatch(line, -1) { v, meth, p := m[1], m[2], m[3] if _, ok := prefixes[v]; !ok { continue } add(meth, resolve(v)+p) } for _, m := range methodEngRe.FindAllStringSubmatch(line, -1) { add(m[1], m[2]) } } return routes } func parseEngineRoutes(t *testing.T, path string) []parsedRoute { t.Helper() src, err := os.ReadFile(path) if err != nil { t.Fatalf("read %s: %v", path, err) } routes := make([]parsedRoute, 0) for _, m := range methodEngRe.FindAllStringSubmatch(string(src), -1) { routes = append(routes, parsedRoute{method: m[1], path: m[2]}) } return routes } func findRepoSources(t *testing.T) (routers []string, bootstrap string) { t.Helper() _, file, _, ok := runtime.Caller(0) if !ok { t.Fatalf("runtime.Caller failed") } // .../server/internal/shared/swagger/descriptions_parity_test.go -> // .../server is three levels up from this file's parent dir. serverRoot := filepath.Clean(filepath.Join(filepath.Dir(file), "..", "..", "..")) return []string{ filepath.Join(serverRoot, "internal", "tenant", "transport", "router.go"), filepath.Join(serverRoot, "internal", "ops", "transport", "router.go"), }, filepath.Join(serverRoot, "internal", "bootstrap", "bootstrap.go") }