Turns a request path plus the two routing fields (`do`/`action`) into a single Resolution for BOTH doors (D-039): the RESERVED level-1 switch first (home/access/setup/api/static/media/ws/dev — D-004), then the GUID classifier, then the registry. Identity and the module registry are injected interfaces, so the package is PURE — no HTTP stack, no DB (asserted by purity_test.go). All 22 spec rows are covered.
110 lines
2.9 KiB
Go
110 lines
2.9 KiB
Go
package routing
|
|
|
|
// This file holds in-memory fakes for the two injected dependencies. It must
|
|
// import no HTTP and no DB package (SPEC-002 §5, the purity assertion).
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
)
|
|
|
|
// fakeResolver is an in-memory Resolver: guid -> enrolled. A missing guid is
|
|
// "not found". It records the guids it was asked about.
|
|
type fakeResolver struct {
|
|
users map[string]bool // guid -> enrolled
|
|
calls []string
|
|
}
|
|
|
|
func (f *fakeResolver) GUID(guid string) (bool, bool) {
|
|
f.calls = append(f.calls, guid)
|
|
enrolled, found := f.users[guid]
|
|
return found, enrolled
|
|
}
|
|
|
|
// forbiddenResolver fails the test if it is ever called. It proves the reserved
|
|
// switch never touches identity (SPEC-002 §3.5 / test row 2).
|
|
type forbiddenResolver struct {
|
|
t *testing.T
|
|
}
|
|
|
|
func (r forbiddenResolver) GUID(guid string) (bool, bool) {
|
|
r.t.Helper()
|
|
r.t.Errorf("resolver must not be called (guid=%q)", guid)
|
|
return false, false
|
|
}
|
|
|
|
// fakeModule is an in-memory Module: a function whitelist, an optional numeric
|
|
// classifier, a default function, and an action whitelist.
|
|
type fakeModule struct {
|
|
name string
|
|
deflt string // function for an empty/absent PARAM3
|
|
routes map[string]string // PARAM3 -> function
|
|
numeric bool // classify numeric PARAM3 as "detail"
|
|
actions map[string]string // action -> function
|
|
}
|
|
|
|
func (m *fakeModule) Name() string { return m.name }
|
|
|
|
func (m *fakeModule) Route(function string, tail Params) (string, Params, bool) {
|
|
if function == "" {
|
|
return m.deflt, tail, true
|
|
}
|
|
if fn, ok := m.routes[function]; ok {
|
|
return fn, tail, true
|
|
}
|
|
if m.numeric && IsNumeric(function) {
|
|
return "detail", Params{function}, true
|
|
}
|
|
return "", nil, false
|
|
}
|
|
|
|
func (m *fakeModule) Action(action string) (string, bool) {
|
|
fn, ok := m.actions[action]
|
|
return fn, ok
|
|
}
|
|
|
|
// fakeRegistry is an in-memory Registry.
|
|
type fakeRegistry map[string]Module
|
|
|
|
func (r fakeRegistry) Module(name string) (Module, bool) {
|
|
m, ok := r[name]
|
|
return m, ok
|
|
}
|
|
|
|
// The module set used by the test matrix (SPEC-002 §3.7 uses `requests`).
|
|
var (
|
|
homeModule = &fakeModule{name: "home", deflt: "dashboard"}
|
|
|
|
requestsModule = &fakeModule{
|
|
name: "requests",
|
|
deflt: "list",
|
|
routes: map[string]string{
|
|
"open": "open",
|
|
"closed": "closed",
|
|
"pto": "pto",
|
|
"equipment": "equipment",
|
|
},
|
|
numeric: true,
|
|
actions: map[string]string{"submit": "submit"},
|
|
}
|
|
|
|
hrModule = &fakeModule{
|
|
name: "hr",
|
|
routes: map[string]string{"save-data": "save-data"},
|
|
actions: map[string]string{"save-data": "save-data"},
|
|
}
|
|
)
|
|
|
|
var testRegistry = fakeRegistry{
|
|
"home": homeModule,
|
|
"requests": requestsModule,
|
|
"hr": hrModule,
|
|
}
|
|
|
|
// resolveFmt renders a Resolution compactly for failure messages.
|
|
func resolveFmt(r Resolution) string {
|
|
return fmt.Sprintf(
|
|
"{Kind:%s Workflow:%q Module:%q Function:%q Params:%v Redirect:%q}",
|
|
r.Kind, r.Workflow, r.Module, r.Function, []string(r.Params), r.Redirect,
|
|
)
|
|
}
|