Documentation
¶
Overview ¶
Package conformance is the executable contract of router.Router.
The interface states the SIGNATURES; this package states the BEHAVIOUR. Two implementations can satisfy the interface — compile without a complaint — and disagree on everything that matters. That is not hypothetical: it is what happened. One registered routes by path alone (so two methods on one path panicked), the other ran its access gate before establishing identity (so every guarded route was a permanent 403). Both compiled. Both were wrong. Nothing caught it, because the only implementation under test was the mock — the one nobody deploys.
A compiler cannot catch "these two implementations behave differently": both satisfy the interface. So it has to become something that goes red. This is that something.
An implementation proves conformance from its own test package:
func TestHTTPDConformance(t *testing.T) {
conformance.Run(t, conformance.Factory{New: newHTTPD, Verify: verifyStartup})
}
The shape (exported suite, parameterized by a factory, one t.Run per clause) is the one the Go team uses for the same job: golang.org/x/net/nettest.TestConn for net.Conn, and testing/fstest.TestFS for fs.FS. This package importing "testing" in non-test code is deliberate and is the whole point — a _test.go file cannot be imported by another repo.
Index ¶
Constants ¶
const ( // UserAuthorized holds Action on Resource: a guarded route must SERVE it. UserAuthorized = "conformance-authorized" // must reject it — authentication is not authorization. UserUnauthorized = "conformance-unauthorized" // Anonymous is no identity at all. Anonymous = "" )
The identities the suite drives through ServeFunc.
const Action = model.Update
Action is the permission the suite's guarded routes require.
const Resource model.Resource = "conformance"
Resource is the resource the suite guards its routes with.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Factory ¶
type Factory struct {
// New returns an EMPTY Router and the ServeFunc that drives it. It is called once per
// case, so no case can be polluted by another's routes.
New func(t *testing.T, s Setup) (router.Router, ServeFunc)
// Verify reports the error the implementation raises AT STARTUP for the routes
// registered so far — nil meaning "this configuration is legal".
//
// Optional. An implementation that cannot fail at startup leaves it nil, and the
// contradiction case skips with a loud reason instead of passing quietly.
Verify func(r router.Router) error
// ServeOp drives ONE request through a route registered via OpRegistry.Op(name, h) — the
// provider-side counterpart of router.Caller.Call(name, args, cb). It receives the SAME
// Router New built, so registration (by the clause) and invocation (by this func) share
// one instance; name is the op name the clause registered.
//
// Optional. An implementation that does not yet implement Op leaves this nil, and the
// Op clauses skip with a loud reason instead of failing to compile.
ServeOp func(r router.Router, name string, body []byte, userID string) Response
}
Factory builds a fresh Router plus the driver that sends a request through it.
type Response ¶
Response is what came back from the transport. It is deliberately not an http.Response: the contract is isomorphic, and one of its implementations does not run on a server.
type ServeFunc ¶
ServeFunc drives ONE request through the Router under test and reports what came back.
It must go through the implementation's REAL pipeline — identity, access gate, middleware, handler. A ServeFunc that calls the matched handler directly proves the opposite of what it claims to: the gate is precisely what is under test.
userID is the identity the transport reports for this caller ("" = anonymous). The implementation routes it through whatever its authentication seam is, the same one a production caller would go through.
type Setup ¶
type Setup struct {
// Authorize answers whether an identity holds a permission. The implementation MUST
// install it as its authorizer. Wiring something else — or nothing — makes the
// authorization cases meaningless.
Authorize model.Authorizer
}
Setup is what the suite hands the implementation so it can be built the way the suite needs to drive it.