package test import ( "RenewCMS/api" "RenewCMS/internal/infrastructure/config" "net/http" "os" "regexp" "sort" "strings" "testing" "github.com/go-chi/chi/v5" ) var catalogueRoutePatterns = []*regexp.Regexp{ regexp.MustCompile(`\w+\(\s*'[^']+',\s*'([A-Z]+)',\s*'([^']+)'`), regexp.MustCompile(`row\(\s*'([A-Z]+)',\s*'([^']+)'`), regexp.MustCompile(`method: '([A-Z]+)',\s*path: '([^']+)'`), } func catalogueRoutes(t *testing.T) map[string]bool { t.Helper() raw, err := os.ReadFile("../web/src/data/apiCatalogue.ts") if err != nil { t.Fatal(err) } routes := map[string]bool{} for _, pattern := range catalogueRoutePatterns { for _, m := range pattern.FindAllStringSubmatch(string(raw), -1) { routes[m[1]+" "+strings.TrimSuffix(m[2], "/")] = true } } return routes } func routerRoutes(t *testing.T) map[string]bool { t.Helper() router := api.InitBackendRoutes(&api.UseCases{}, config.Settings{}) routes := map[string]bool{} err := chi.Walk(router, func(method, route string, _ http.Handler, _ ...func(http.Handler) http.Handler) error { route = strings.ReplaceAll(route, "/*", "") routes[method+" /v1"+strings.TrimSuffix(route, "/")] = true return nil }) if err != nil { t.Fatal(err) } return routes } var routesNotYetInTheCatalogue = []string{ "DELETE /v1/content-type/{key}", "DELETE /v1/content-type/{key}/entry/{id}", "DELETE /v1/image-variants/{name}", "DELETE /v1/media/{id}/variants/{name}", "DELETE /v1/role/{id}", "GET /v1/builtin-type", "GET /v1/builtin-type/{key}", "GET /v1/content-type", "GET /v1/content-type/{key}", "GET /v1/content-type/{key}/entry", "GET /v1/content-type/{key}/entry/{id}", "GET /v1/image-variants", "GET /v1/image-variants/cache", "GET /v1/media/{id}/variants", "GET /v1/user/lookup", "GET /v1/permission", "GET /v1/role", "GET /v1/role/{id}", "PATCH /v1/image-variants/{name}", "POST /v1/content-type", "POST /v1/content-type/{key}/entry", "POST /v1/image-variants", "POST /v1/image-variants/cache/purge", "POST /v1/image-variants/{name}/purge", "POST /v1/media/{id}/variants", "POST /v1/media/{id}/variants/{name}", "POST /v1/role", "POST /v1/role/{id}/reassign", "POST /v1/role/{id}/reset", "PUT /v1/content-type/{key}", "PUT /v1/content-type/{key}/entry/{id}", "PUT /v1/image-variants/{name}", "PUT /v1/role/{id}", } func TestRouterRoutesAreInTheCatalogue(t *testing.T) { documented := catalogueRoutes(t) exempt := map[string]bool{} for _, route := range routesNotYetInTheCatalogue { exempt[route] = true } actual := routerRoutes(t) var missing, stale []string for route := range actual { if !documented[route] && !exempt[route] { missing = append(missing, route) } } for route := range exempt { if documented[route] || !actual[route] { stale = append(stale, route) } } sort.Strings(missing) sort.Strings(stale) if len(missing) > 0 { t.Errorf("routes missing from web/src/data/apiCatalogue.ts:\n%s", strings.Join(missing, "\n")) } if len(stale) > 0 { t.Errorf("routes to remove from routesNotYetInTheCatalogue:\n%s", strings.Join(stale, "\n")) } } func TestCatalogueRoutesExistInTheRouter(t *testing.T) { actual := routerRoutes(t) var unknown []string for route := range catalogueRoutes(t) { if strings.Contains(route, " /v1") && !actual[route] { unknown = append(unknown, route) } } sort.Strings(unknown) if len(unknown) > 0 { t.Errorf("catalogue routes the router does not serve:\n%s", strings.Join(unknown, "\n")) } }