data

package
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 28, 2026 License: MIT Imports: 3 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
var Policies = []string{"first", "second", "third", "public", "protected", "admin"}

Policies are registered on every test module, each records its name when it runs.

View Source
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

type Definition struct {
	Prefix string
	Routes func(g *route.Group, mark Marker)
}

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

type PatternScenario struct {
	Name    string
	Pattern *route.Pattern
	Value   string
}

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL