internal/adapters/tenantengine: HTTPClient.LiveRoles() calls tenant-engine's
GET /tenants/{id}/roles/live. LiveRolesResult.Available is the load-bearing
field -- false on any transport error, non-200, or malformed body, never
inferred as zero roles. AttachToContext() writes both tenant_roles and
tenant_roles_available into a CheckRequest.Context map; a consuming Rego
policy must check tenant_roles_available before trusting tenant_roles.
Architectural finding recorded in the workplan: engine.go's Check() has no
context-adapter hook, and none of the existing topaz/relationship/rule
adapters are wired into cmd/flex-auth either -- they're standalone packages
for downstream composition. This adapter is a request-preparation helper a
protected system's own request-building code calls before POST /v1/check,
not an engine-internal hook, matching that precedent exactly.
9 Go tests; gofmt/vet/build clean; go test ./... green repo-wide.
Verified as a real three-service chain: live flex-auth serve + live
tenant-engine, created a tenant and granted it a CUS role through the real
flex-auth-gated write path, then read it back through this adapter (via a
throwaway harness, not committed) -- known tenant: roles=[CUS]
available=true; unknown tenant: roles=[] available=false err="status 404".
FLEX-WP-0008 closed: T01-T04 all done. tenant-engine's write path is now
real end-to-end; any other protected system can pull live tenant capability
role context, fail-closed. Remaining open item unchanged from TEN-WP-0003:
KEY-WP-0005 (key-cape's IAM Profile core-claims gap).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
42 lines
1.2 KiB
Go
42 lines
1.2 KiB
Go
package tenantengine_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/netkingdom/flex-auth/internal/adapters/tenantengine"
|
|
)
|
|
|
|
func TestAttachToContextSetsRolesAndAvailability(t *testing.T) {
|
|
ctx := tenantengine.AttachToContext(nil, tenantengine.LiveRolesResult{
|
|
Roles: []string{"CUS"},
|
|
Available: true,
|
|
})
|
|
|
|
if ctx["tenant_roles_available"] != true {
|
|
t.Fatalf("tenant_roles_available = %v, want true", ctx["tenant_roles_available"])
|
|
}
|
|
roles, ok := ctx["tenant_roles"].([]string)
|
|
if !ok || len(roles) != 1 || roles[0] != "CUS" {
|
|
t.Fatalf("tenant_roles = %v", ctx["tenant_roles"])
|
|
}
|
|
}
|
|
|
|
func TestAttachToContextMarksUnavailableOnFailure(t *testing.T) {
|
|
ctx := tenantengine.AttachToContext(map[string]any{"existing": "field"}, tenantengine.LiveRolesResult{
|
|
Available: false,
|
|
})
|
|
|
|
if ctx["tenant_roles_available"] != false {
|
|
t.Fatalf("tenant_roles_available = %v, want false", ctx["tenant_roles_available"])
|
|
}
|
|
if ctx["existing"] != "field" {
|
|
t.Fatal("AttachToContext must not clobber unrelated context fields")
|
|
}
|
|
}
|
|
|
|
func TestAttachToContextHandlesNilContext(t *testing.T) {
|
|
ctx := tenantengine.AttachToContext(nil, tenantengine.LiveRolesResult{Available: true, Roles: []string{}})
|
|
if ctx == nil {
|
|
t.Fatal("expected a non-nil map")
|
|
}
|
|
}
|