Documentation
¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
var DefinitionFailureScenarios = []Scenario{ { Name: "unknown route policy", Definitions: []Definition{{Prefix: "/", Routes: func(g *route.Group, mark Marker) { g.GET("/items", mark("handler")).Policy("protectd") }}}, Err: route.ErrUnknownPolicy, }, { Name: "unknown group policy", Definitions: []Definition{{Prefix: "/", Routes: func(g *route.Group, mark Marker) { g.Group("/items").Policy("protectd").GET("", mark("handler")) }}}, Err: route.ErrUnknownPolicy, }, { Name: "route param not in path", Definitions: []Definition{{Prefix: "/", Routes: func(g *route.Group, mark Marker) { g.GET("/items/:id", mark("handler")).Param("item_id", route.Numeric) }}}, Err: route.ErrUnknownParam, }, { Name: "empty param name", Definitions: []Definition{{Prefix: "/", Routes: func(g *route.Group, mark Marker) { g.GET("/items/:id", mark("handler")).Param("", route.Numeric) }}}, Err: route.ErrUnknownParam, }, { Name: "param on catch-all", Definitions: []Definition{{Prefix: "/", Routes: func(g *route.Group, mark Marker) { g.GET("/files/*path", mark("handler")).Param("path", route.Numeric) }}}, Err: route.ErrInvalidParamPattern, }, { Name: "invalid group pattern", Definitions: []Definition{{Prefix: "/", Routes: func(g *route.Group, mark Marker) { g.Group("/items").Param("id", route.Regex(`[0-9`)).GET("/:id", mark("handler")) }}}, Err: route.ErrInvalidParamPattern, }, { Name: "duplicate route", Definitions: []Definition{{Prefix: "/", Routes: func(g *route.Group, mark Marker) { g.GET("/items", mark("handler")) g.Group("/items").GET("/", mark("handler")) }}}, Err: route.ErrDuplicateRoute, }, { Name: "ambiguous route", Definitions: []Definition{{Prefix: "/", Routes: func(g *route.Group, mark Marker) { g.GET("/items/:id", mark("handler")).Param("id", route.Numeric) g.GET("/items/:slug", mark("handler")).Param("slug", route.Regex(`[a-z]+`)) }}}, Err: route.ErrAmbiguousRoute, }, { Name: "duplicate route across calls", Definitions: []Definition{ {Prefix: "/api", Routes: func(g *route.Group, mark Marker) { g.GET("/items", mark("handler")) }}, {Prefix: "/", Routes: func(g *route.Group, mark Marker) { g.GET("/api/items", mark("handler")) }}, }, Err: route.ErrDuplicateRoute, }, { Name: "ambiguous route across calls", Definitions: []Definition{ {Prefix: "/", Routes: func(g *route.Group, mark Marker) { g.GET("/items/:id", mark("handler")) }}, {Prefix: "/", Routes: func(g *route.Group, mark Marker) { g.GET("/items/:slug", mark("handler")) }}, }, Err: route.ErrAmbiguousRoute, }, }
DefinitionFailureScenarios are route definitions DefineRoutes rejects, the last definition of each scenario fails.
var ExportFailureScenarios = []Scenario{ { Name: "mapping for unknown policy", ExporterOpts: []traefik.Option{ traefik.WithService("items-api"), traefik.WithPolicyMiddlewares("protectd", "forward-auth@file"), }, Definitions: []Definition{{Prefix: "/", Routes: func(g *route.Group, mark Marker) { g.GET("/items", mark("handler")) }}}, Err: route.ErrUnknownPolicy, }, { Name: "missing service", Definitions: []Definition{{Prefix: "/", Routes: func(g *route.Group, mark Marker) { g.GET("/items", mark("handler")) }}}, Err: traefik.ErrMissingService, }, { Name: "paths producing the same router name", ExporterOpts: []traefik.Option{ traefik.WithService("items-api"), }, Definitions: []Definition{{Prefix: "/", Routes: func(g *route.Group, mark Marker) { g.GET("/a-b", mark("handler")) g.GET("/a/b", mark("handler")) }}}, Err: traefik.ErrDuplicateRouter, }, { Name: "backtick in host", ExporterOpts: []traefik.Option{ traefik.WithService("items-api"), traefik.WithHost("api.example.com`) || Host(`evil.example.com"), }, Definitions: []Definition{{Prefix: "/", Routes: func(g *route.Group, mark Marker) { g.GET("/items", mark("handler")) }}}, Err: traefik.ErrBacktickInRule, }, { Name: "backtick in static path", ExporterOpts: []traefik.Option{ traefik.WithService("items-api"), }, Definitions: []Definition{{Prefix: "/", Routes: func(g *route.Group, mark Marker) { g.GET("/items`", mark("handler")) }}}, Err: traefik.ErrBacktickInRule, }, { Name: "backtick in param pattern", ExporterOpts: []traefik.Option{ traefik.WithService("items-api"), }, Definitions: []Definition{{Prefix: "/", Routes: func(g *route.Group, mark Marker) { g.GET("/items/:id", mark("handler")).Param("id", route.Regex("a`b")) }}}, Err: traefik.ErrBacktickInRule, }, }
ExportFailureScenarios are route definitions the Traefik exporter rejects, failing DefineRoutes without writing the config file.
var ExportScenarios = []Scenario{ { Name: "full configuration", ExporterOpts: []traefik.Option{ traefik.WithRouterPrefix("setups"), traefik.WithService("setups-api"), traefik.WithHost("api.example.com"), traefik.WithEntryPoints("websecure"), traefik.WithCertResolver("letsencrypt"), traefik.WithDefaultMiddlewares("cors@file"), traefik.WithPolicyMiddlewares("protected", "forward-auth@file"), traefik.WithPolicyMiddlewares("admin", "forward-auth@file", "admin-ipallowlist@file"), }, Definitions: []Definition{{Prefix: "/api/v1", Routes: func(g *route.Group, mark Marker) { setups := g.Group("/setups").Param("setup_id", route.UUID) setups.GET("", mark("handler")) setups.GET("/mine", mark("handler")).Policy("protected") setups.GET("/:setup_id", mark("handler")) setups.POST("/:setup_id/rate/:vote", mark("handler")). Policy("protected"). Param("vote", route.OneOf("up", "down")) setups.DELETE("/:setup_id", mark("handler")).Policy("protected", "admin") g.GET("/users/:user_id/files/*filepath", mark("handler")).Param("user_id", route.Numeric) g.Internal("/internal").GET("/status", mark("handler")) }}}, }, { Name: "minimal configuration", ExporterOpts: []traefik.Option{ traefik.WithService("items-api"), }, Definitions: []Definition{{Prefix: "/", Routes: func(g *route.Group, mark Marker) { g.GET("/", mark("handler")) g.GET("/items/:id", mark("handler")).Param("id", route.Numeric).Policy("protected") g.GET("/items/:id/tags/:tag", mark("handler")).Param("id", route.Numeric) }}}, }, { Name: "router names", ExporterOpts: []traefik.Option{ traefik.WithRouterPrefix("Items API"), traefik.WithService("items-api"), }, Definitions: []Definition{{Prefix: "/", Routes: func(g *route.Group, mark Marker) { g.GET("/items/id", mark("handler")) g.GET("/items/:id", mark("handler")) g.GET("/files/*path", mark("handler")) }}}, }, { Name: "path shapes", ExporterOpts: []traefik.Option{ traefik.WithService("items-api"), }, Definitions: []Definition{{Prefix: "/", Routes: func(g *route.Group, mark Marker) { g.GET("/api/v1/setups", mark("handler")) g.GET("/constrained/:id", mark("handler")).Param("id", route.Numeric) g.GET("/unconstrained/:id", mark("handler")) g.POST("/multiple/:setup_id/rate/:vote", mark("handler")). Param("setup_id", route.UUID). Param("vote", route.OneOf("up", "down")) g.GET("/mixed/:user_id/files/:name", mark("handler")).Param("user_id", route.Numeric) g.Group("/group").Param("user_id", route.Numeric).GET("/:user_id", mark("handler")) g.GET("/v1.0/files+(all)", mark("handler")) g.GET("/static/*filepath", mark("handler")) }}}, }, { Name: "inherited policies and internal routes", ExporterOpts: []traefik.Option{ traefik.WithService("setups-api"), traefik.WithPolicyMiddlewares("public", "rate-limit@file"), traefik.WithPolicyMiddlewares("protected", "forward-auth@file"), }, Definitions: []Definition{{Prefix: "/api/v1", Routes: func(g *route.Group, mark Marker) { g.Policy("public") setups := g.Group("/setups").Param("setup_id", route.UUID) setups.GET("", mark("handler")) setups.POST("/:setup_id", mark("handler")).Policy("protected", "public") g.Internal("/internal").Group("/nested").GET("/status", mark("handler")) }}}, }, { Name: "routes from every definition", ExporterOpts: []traefik.Option{ traefik.WithService("items-api"), }, Definitions: []Definition{ {Prefix: "/api", Routes: func(g *route.Group, mark Marker) { g.GET("/items", mark("handler")) g.POST("/items", mark("handler")) }}, {Prefix: "/", Routes: func(g *route.Group, mark Marker) { g.GET("/", mark("handler")) }}, }, }, }
ExportScenarios are route definitions whose exported Traefik config is compared against a golden file.
var InvalidPatternScenarios = []PatternScenario{ {Name: "nil", Pattern: nil}, {Name: "empty", Pattern: route.Regex(``)}, {Name: "one of without values", Pattern: route.OneOf()}, {Name: "does not compile", Pattern: route.Regex(`[a-z`)}, {Name: "does not compile as a group", Pattern: route.Regex(`\Qabc`)}, {Name: "literal slash", Pattern: route.Regex(`a/b`)}, {Name: "escaped slash", Pattern: route.Regex(`\/`)}, {Name: "character class including slash", Pattern: route.Regex(`[a/b]+`)}, {Name: "negated character class not excluding slash", Pattern: route.Regex(`[^.]+`)}, {Name: "non-whitespace class", Pattern: route.Regex(`\S+`)}, {Name: "any character", Pattern: route.Regex(`.+`)}, {Name: "begin anchor", Pattern: route.Regex(`^[a-z]+`)}, {Name: "end anchor", Pattern: route.Regex(`[a-z]+$`)}, {Name: "text anchor", Pattern: route.Regex(`\A[a-z]+`)}, }
InvalidPatternScenarios are patterns DefineRoutes rejects.
var MatchingPatternScenarios = []PatternScenario{ {Name: "uuid lowercase", Pattern: route.UUID, Value: "3f2c1a9e-8b7d-4c6e-9f10-2a3b4c5d6e7f"}, {Name: "uuid uppercase", Pattern: route.UUID, Value: "3F2C1A9E-8B7D-4C6E-9F10-2A3B4C5D6E7F"}, {Name: "numeric digits", Pattern: route.Numeric, Value: "12345"}, {Name: "numeric zero", Pattern: route.Numeric, Value: "0"}, {Name: "one of first value", Pattern: route.OneOf("up", "down"), Value: "up"}, {Name: "one of second value", Pattern: route.OneOf("up", "down"), Value: "down"}, {Name: "one of metacharacters matched literally", Pattern: route.OneOf("a.b"), Value: "a.b"}, {Name: "regex", Pattern: route.Regex(`[a-z]+-[0-9]+`), Value: "item-12"}, {Name: "negated character class excluding slash", Pattern: route.Regex(`[^/]+`), Value: "a.b"}, {Name: "negated character class excluding slash and dot", Pattern: route.Regex(`[^/.]+`), Value: "ab"}, {Name: "digit class", Pattern: route.Regex(`\d+`), Value: "12"}, {Name: "escaped anchors", Pattern: route.Regex(`\^[a-z]+\$`), Value: "%5Eab%24"}, }
MatchingPatternScenarios are param values their pattern accepts.
var NonMatchingPatternScenarios = []PatternScenario{ {Name: "uuid one character short", Pattern: route.UUID, Value: "3f2c1a9e-8b7d-4c6e-9f10-2a3b4c5d6e7"}, {Name: "uuid missing hyphen", Pattern: route.UUID, Value: "3f2c1a9e8b7d-4c6e-9f10-2a3b4c5d6e7f"}, {Name: "uuid with trailing characters", Pattern: route.UUID, Value: "3f2c1a9e-8b7d-4c6e-9f10-2a3b4c5d6e7fa"}, {Name: "uuid with non-hex character", Pattern: route.UUID, Value: "3f2c1a9e-8b7d-4c6e-9f10-2a3b4c5d6e7g"}, {Name: "numeric with letter", Pattern: route.Numeric, Value: "12a"}, {Name: "numeric negative", Pattern: route.Numeric, Value: "-1"}, {Name: "numeric decimal", Pattern: route.Numeric, Value: "1.5"}, {Name: "one of value with suffix", Pattern: route.OneOf("up", "down"), Value: "upx"}, {Name: "one of value with prefix", Pattern: route.OneOf("up", "down"), Value: "xdown"}, {Name: "one of values joined", Pattern: route.OneOf("up", "down"), Value: "updown"}, {Name: "one of metacharacters are not wildcards", Pattern: route.OneOf("a.b"), Value: "axb"}, {Name: "regex is anchored", Pattern: route.Regex(`[a-z]+-[0-9]+`), Value: "item-12x"}, }
NonMatchingPatternScenarios are param values their pattern rejects.
var NotFoundRouteScenarios = []Scenario{ { Name: "param mismatch skips policies and the handler", Definitions: []Definition{{Prefix: "/api", Routes: func(g *route.Group, mark Marker) { g.Group("/items", mark("group")). GET("/:id", mark("handler")). Policy("first"). Param("id", route.Numeric) }}}, Path: "/api/items/abc", Calls: []string{"group"}, }, { Name: "group param applies to routes with the param", Definitions: []Definition{{Prefix: "/api", Routes: func(g *route.Group, mark Marker) { g.Group("/items").Param("id", route.Numeric).GET("/:id", mark("handler")) }}}, Path: "/api/items/abc", }, { Name: "route param override rejects the group pattern", Definitions: []Definition{{Prefix: "/api", Routes: func(g *route.Group, mark Marker) { g.Group("/items"). Param("id", route.Numeric). GET("/:id", mark("handler")). Param("id", route.OneOf("me")) }}}, Path: "/api/items/1", }, }
NotFoundRouteScenarios are GET requests that get a 404 from the param check.
var Policies = []string{"first", "second", "third", "public", "protected", "admin"}
Policies are registered on every test module, each records its name when it runs.
var ServedRouteScenarios = []Scenario{ { Name: "handler order is group middleware, param check, policies, handler", Definitions: []Definition{{Prefix: "/api", Routes: func(g *route.Group, mark Marker) { g.Group("/items", mark("group")). GET("/:id", mark("handler")). Policy("first", "second"). Param("id", route.Numeric) }}}, Path: "/api/items/1", Calls: []string{"group", "first", "second", "handler"}, }, { Name: "nested group middleware runs outermost first", Definitions: []Definition{{Prefix: "/api", Routes: func(g *route.Group, mark Marker) { g.Group("/items", mark("outer")). Group("/sub", mark("inner")). GET("", mark("handler")) }}}, Path: "/api/items/sub", Calls: []string{"outer", "inner", "handler"}, }, { Name: "policies are ordered parent, group, route and deduplicated", Definitions: []Definition{{Prefix: "/api", Routes: func(g *route.Group, mark Marker) { g.Policy("first") items := g.Group("/items").Policy("second", "first") items.GET("", mark("handler")).Policy("third", "second") }}}, Path: "/api/items", Calls: []string{"first", "second", "third", "handler"}, }, { Name: "group policies added after the route still apply", Definitions: []Definition{{Prefix: "/api", Routes: func(g *route.Group, mark Marker) { items := g.Group("/items") items.GET("", mark("handler")).Policy("second") items.Policy("first") }}}, Path: "/api/items", Calls: []string{"first", "second", "handler"}, }, { Name: "group param ignored for routes without the param", Definitions: []Definition{{Prefix: "/api", Routes: func(g *route.Group, mark Marker) { g.Group("/items").Param("id", route.Numeric).GET("", mark("handler")) }}}, Path: "/api/items", Calls: []string{"handler"}, }, { Name: "route param overrides group param", Definitions: []Definition{{Prefix: "/api", Routes: func(g *route.Group, mark Marker) { g.Group("/items"). Param("id", route.Numeric). GET("/:id", mark("handler")). Param("id", route.OneOf("me")) }}}, Path: "/api/items/me", Calls: []string{"handler"}, }, { Name: "internal routes are served", Definitions: []Definition{{Prefix: "/api", Routes: func(g *route.Group, mark Marker) { g.Internal("/internal").GET("/status", mark("handler")) }}}, Path: "/api/internal/status", Calls: []string{"handler"}, }, { Name: "same path with different methods", Definitions: []Definition{{Prefix: "/", Routes: func(g *route.Group, mark Marker) { g.GET("/items/:id", mark("handler")) g.DELETE("/items/:id", mark("delete")) }}}, Path: "/items/1", Calls: []string{"handler"}, }, { Name: "root route on root prefix", Definitions: []Definition{{Prefix: "/", Routes: func(g *route.Group, mark Marker) { g.GET("/", mark("handler")) }}}, Path: "/", Calls: []string{"handler"}, }, { Name: "root route on group maps to the group prefix", Definitions: []Definition{{Prefix: "/api", Routes: func(g *route.Group, mark Marker) { g.Group("/v1").GET("/", mark("handler")) }}}, Path: "/api/v1", Calls: []string{"handler"}, }, { Name: "duplicate slashes collapsed", Definitions: []Definition{{Prefix: "/api/", Routes: func(g *route.Group, mark Marker) { g.Group("//v1/").GET("//items", mark("handler")) }}}, Path: "/api/v1/items", Calls: []string{"handler"}, }, { Name: "missing slashes added", Definitions: []Definition{{Prefix: "api", Routes: func(g *route.Group, mark Marker) { g.Group("v1").GET("items", mark("handler")) }}}, Path: "/api/v1/items", Calls: []string{"handler"}, }, }
ServedRouteScenarios are GET requests that reach their route.
Functions ¶
This section is empty.
Types ¶
type Definition ¶
Definition is one DefineRoutes call, Routes defines the routes under Prefix using mark for every handler and middleware.
type Marker ¶
type Marker func(name string) gin.HandlerFunc
Marker returns a handler that records name when it runs.
type PatternScenario ¶
PatternScenario is a GET /items/:id route whose id is constrained by Pattern, requested with Value as the id.
type Scenario ¶
type Scenario struct {
Name string
ExporterOpts []traefik.Option
Definitions []Definition
Path string
Calls []string
Err error
}
Scenario holds everything a test needs: the exporter options, the route definitions, the GET path to request, the handlers expected to run for it, and the error expected from defining the routes.