Documentation
¶
Overview ¶
Package testkit is wowapi's public integration-test harness: the one package permitted to compose everything (kernel, app, adapters, modules) so that both the framework and external product repositories can exercise their code against a real Postgres with the same fixtures, fakes, and assertions.
Production packages MUST NOT import testkit (boundary lint). testkit MAY import kernel/*, app, adapters, and module.
Database strategy (D-0022) ¶
No testcontainers. The admin DSN comes from WOWAPI_TEST_DSN (fallback DATABASE_URL); tests skip with a clear message when neither is set. Kernel migrations run once per process into a content-addressed template database (wowapi_tmpl_<hash>); every test then gets an exclusive database cloned with CREATE DATABASE … TEMPLATE and dropped on cleanup. See docs/blueprint/08 §2 and decisions D-0022/D-0023/D-0025.
Index ¶
- func AssertAllowed(t *testing.T, h *DBHandle, e authz.Evaluator, a authz.Actor, perm string, ...)
- func AssertDenied(t *testing.T, h *DBHandle, e authz.Evaluator, a authz.Actor, perm string, ...)
- func AssertLocalizedProblem(t *testing.T, resp *http.Response, wantCode, wantTitle string)
- func AssertNegotiatedLocale(t *testing.T, cat *i18n.Catalog, acceptLang, wantLocale string)
- func AssertRLSIsolation(t *testing.T, h *DBHandle, table string, row RowFactory)
- func AssertRLSIsolationSeeded(t *testing.T, h *DBHandle, table string, tenantA, tenantB uuid.UUID, ...)
- func CreateCapacity(t *testing.T, h *DBHandle, tenant, userID uuid.UUID) uuid.UUID
- func CreateOrg(t *testing.T, h *DBHandle, tenant uuid.UUID, parent *uuid.UUID, name string) uuid.UUID
- func CreatePermission(t *testing.T, h *DBHandle, key string, sensitive bool)
- func CreateProbeTable(t *testing.T, h *DBHandle) string
- func CreateResource(t *testing.T, h *DBHandle, tenant uuid.UUID, resType string, org *uuid.UUID) resource.Ref
- func CreateResourceType(t *testing.T, h *DBHandle, key string)
- func CreateResourceTypeAndResource(t *testing.T, h *DBHandle, tenant uuid.UUID, resType string) resource.Ref
- func CreateRole(t *testing.T, h *DBHandle, tenant uuid.UUID, key string, perms ...string) uuid.UUID
- func CreateUser(t *testing.T, h *DBHandle) uuid.UUID
- func GrantRole(t *testing.T, h *DBHandle, tenant, capacity, role uuid.UUID, scopeKind string, ...)
- func NewLocaleRequest(method, target, locale string, cat *i18n.Catalog) *http.Request
- func RequireDB() bool
- func RunModuleContract(t *testing.T, m module.Module)
- func SeedWorkflowDefinition(t *testing.T, h *DBHandle, tenant *uuid.UUID, key string, version int, ...) uuid.UUID
- func TenantCtx(tenant uuid.UUID) context.Context
- type DBHandle
- type RowFactory
- type TenantHandle
- type TokenIssuer
- type TokenOption
- type WorkflowSim
- func (s *WorkflowSim) Approve(stepKey string, asActor authz.Actor) *WorkflowSim
- func (s *WorkflowSim) ExpectStatus(status string) *WorkflowSim
- func (s *WorkflowSim) ExpectStep(stepKey string) *WorkflowSim
- func (s *WorkflowSim) InstanceID() uuid.UUID
- func (s *WorkflowSim) Reject(stepKey string, asActor authz.Actor, comment string) *WorkflowSim
- func (s *WorkflowSim) Start(defKey string, res resource.Ref, input map[string]any) *WorkflowSim
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AssertAllowed ¶
func AssertAllowed(t *testing.T, h *DBHandle, e authz.Evaluator, a authz.Actor, perm string, target authz.Target)
AssertAllowed fails the test unless the evaluator allows actor to exercise perm on target. The check runs inside a tenant transaction (the production path) scoped to the actor's tenant.
func AssertDenied ¶
func AssertDenied(t *testing.T, h *DBHandle, e authz.Evaluator, a authz.Actor, perm string, target authz.Target)
AssertDenied fails the test unless the evaluator denies actor perm on target.
func AssertLocalizedProblem ¶ added in v1.1.0
AssertLocalizedProblem decodes an application/problem+json response and asserts its title is the localized wantTitle while its machine Code equals wantCode — the core i18n invariant: user-facing text localizes, machine codes stay stable. It consumes resp.Body.
func AssertNegotiatedLocale ¶ added in v1.1.0
AssertNegotiatedLocale asserts that Accept-Language header value acceptLang, negotiated against cat's supported locales (RFC 9110 q-values), resolves to wantLocale. It mirrors the real httpx.Locale middleware path, so a test proves the negotiation contract without standing up a server.
func AssertRLSIsolation ¶
func AssertRLSIsolation(t *testing.T, h *DBHandle, table string, row RowFactory)
AssertRLSIsolation proves the four tenant-isolation properties for a tenant-scoped table (03 §1): (1) rows written as tenant A are invisible to tenant B; (2) a query without tenant context fails (no default tenant); (3) WITH CHECK blocks writing a row whose tenant_id differs from the bound tenant; (4) the writing tenant sees its own row. The table must carry the standard tenant_id column; row describes one minimal row EXCLUDING tenant_id.
It uses two fresh (unseeded) tenant ids and the app_rt runtime role, which fits any tenant-scoped table whose tenant_id has no FK to tenants and that app_rt may write. Tables that FK tenant_id -> tenants, or that only app_platform may write, use AssertRLSIsolationSeeded instead.
func AssertRLSIsolationSeeded ¶
func AssertRLSIsolationSeeded(t *testing.T, h *DBHandle, table string, tenantA, tenantB uuid.UUID, row RowFactory, writeTxM database.TxManager)
AssertRLSIsolationSeeded proves the same four isolation properties as AssertRLSIsolation, but against CALLER-SUPPLIED tenant ids and a caller-chosen operating role. Seeding the tenants first lets it cover tables whose tenant_id carries a FK to tenants (organizations, parties, roles, policies); passing writeTxM lets it cover tables writable only by app_platform (authz config, audit anchors, integration/webhook config) under the role that actually operates them. The unbound-read defense-in-depth check always runs on the app_rt runtime pool, so fail-closed is asserted against the least-privileged runtime identity regardless of writeTxM.
func CreateCapacity ¶
CreateCapacity inserts an active acting capacity for a user in a tenant.
func CreateOrg ¶
func CreateOrg(t *testing.T, h *DBHandle, tenant uuid.UUID, parent *uuid.UUID, name string) uuid.UUID
CreateOrg inserts an organization (optionally under a parent) and returns its id.
func CreatePermission ¶
CreatePermission inserts a permission into the global catalog.
func CreateProbeTable ¶
CreateProbeTable creates a minimal tenant-scoped table following every convention from 03 §1 (tenant_id, ENABLE + FORCE RLS, standard policy, grants to app_rt) so RLS mechanics can be proven before real tenant tables ship (D-0025). Returns the table name.
The table is created through h.Admin (owner). FORCE ROW LEVEL SECURITY is what makes RLS apply even though the runtime role reaches the table via SET ROLE from a superuser login — the policy is enforced against app_rt.
func CreateResource ¶
func CreateResource(t *testing.T, h *DBHandle, tenant uuid.UUID, resType string, org *uuid.UUID) resource.Ref
CreateResource inserts a kernel resources mirror row (via Admin) and returns its Ref. resType must be a registered resource type.
func CreateResourceType ¶
CreateResourceType registers a resource type in the global catalog.
func CreateResourceTypeAndResource ¶
func CreateResourceTypeAndResource(t *testing.T, h *DBHandle, tenant uuid.UUID, resType string) resource.Ref
CreateResourceTypeAndResource registers a resource type (if needed) and inserts a resources mirror row, returning its Ref — convenient for tests that need a resource-scoped target or aggregate.
func CreateRole ¶
CreateRole inserts a tenant role granting the given permissions and returns id.
func CreateUser ¶
CreateUser inserts a global user and returns its id.
func GrantRole ¶
func GrantRole(t *testing.T, h *DBHandle, tenant, capacity, role uuid.UUID, scopeKind string, scopeID *uuid.UUID, scopeType string)
GrantRole assigns a role to a capacity at the given scope (tenant/org/ resource_type/resource). scopeID/scopeType may be zero/empty per scope.
func NewLocaleRequest ¶ added in v1.1.0
NewLocaleRequest builds an *http.Request whose context already carries the negotiated locale and catalog, as the httpx.Locale middleware would bind them. Use it to drive a handler under a specific locale without wiring the full middleware chain — WriteError and validation inside the handler will localize against cat.
func RequireDB ¶
func RequireDB() bool
RequireDB reports whether the environment mandates that DB-backed tests run (rather than skip when no DSN). Set WOWAPI_REQUIRE_DB=1 in CI/release gates. Exported so external suites (scratch-consumer, E2E) share one policy.
func RunModuleContract ¶
RunModuleContract is the kernel's module conformance suite (blueprint 08 §2, 11): it registers the module ALONE on a fresh kernel and asserts it
- boots and validates (routes have metadata, permissions are declared, no dependency/registry errors) on an EMPTY config namespace — defaults must be complete;
- migrates and seeds IDEMPOTENTLY (running each twice is a no-op);
- enforces RLS on every module-owned table;
- REJECTS an invalid config namespace (unknown key) at boot.
It requires a real Postgres (skips without a DSN, like NewDB).
func SeedWorkflowDefinition ¶
func SeedWorkflowDefinition(t *testing.T, h *DBHandle, tenant *uuid.UUID, key string, version int, appliesTo string, raw []byte) uuid.UUID
SeedWorkflowDefinition inserts a workflow_definitions row so an instance's definition_id FK resolves. tenant==nil seeds a module template (tenant_id NULL). The definition graph itself lives in the workflow.Registry; the jsonb here is the persisted mirror. Returns the definition id.
Types ¶
type DBHandle ¶
type DBHandle struct {
Name string // per-test database name
Admin *pgxpool.Pool // owner credentials — fixtures, probe DDL
Runtime *pgxpool.Pool // connects AS app_rt — what production code sees
Platform *pgxpool.Pool // connects AS app_platform — kernel/seed catalog writes
TxM database.TxManager // manager over Runtime
// PlatformTxM is a tenant-bound TxManager over Platform (SET ROLE app_platform)
// for kernel background work that mutates append-only-to-app_rt tables under a
// bound tenant — e.g. document scan-status + retention voiding.
PlatformTxM database.TxManager
}
DBHandle is what NewDB hands a test: an admin pool (owner privileges, for fixtures/DDL) and a runtime pool (SET ROLE app_rt, RLS-enforced) on a database that is EXCLUSIVELY this test's.
func NewDB ¶
NewDB provisions an exclusive database for t cloned from the migrated kernel template, returning admin + runtime pools and a TxManager over runtime. It skips (never fails) when no admin DSN is configured. Accepts testing.TB so both tests and benchmarks (Benchmark* over the DB-backed hot paths) share it.
type RowFactory ¶
RowFactory produces the non-tenant columns for one probe row. It is called once per insert (once as tenant A, once for the WITH CHECK probe) so unique columns such as the primary key differ between calls — the assert supplies tenant_id itself, so the factory MUST NOT set it (any tenant_id it returns is overwritten). The uuid argument is the tenant the row is being written under.
type TenantHandle ¶
TenantHandle is a created tenant plus convenient ids.
func CreateTenant ¶
func CreateTenant(t *testing.T, h *DBHandle) TenantHandle
CreateTenant inserts a tenant and returns its handle.
type TokenIssuer ¶
type TokenIssuer struct {
// contains filtered or unexported fields
}
TokenIssuer holds a locally-generated RSA keypair and mints RS256 JWTs the auth.Verifier accepts. It is the fixture every authenticated test uses (blueprint 08 §2, D-0037): pair KeySource() with an auth.Verifier, then Issue tokens for the subjects/tenants/capacities under test.
func NewTokenIssuer ¶
func NewTokenIssuer() *TokenIssuer
NewTokenIssuer generates a fresh 2048-bit RSA keypair and returns an issuer keyed under a stable test kid. It panics on key-generation failure since a test harness cannot proceed without it.
func (*TokenIssuer) Issue ¶
func (ti *TokenIssuer) Issue(subject string, tenantID, capacityID uuid.UUID, opts ...TokenOption) string
Issue mints a signed RS256 JWT for subject in tenantID with the given capacityID (pass uuid.Nil to omit the capacity claim). Standard claims (iss/aud/exp/iat/nbf) default to the values the auth.Verifier expects and are tunable via opts. The kid header is set so KeySource resolves the key.
func (*TokenIssuer) KeySource ¶
func (ti *TokenIssuer) KeySource() auth.KeySource
KeySource returns an auth.KeySource exposing this issuer's public key under its kid, ready to wire into an auth.Verifier.
func (*TokenIssuer) PublicKey ¶
func (ti *TokenIssuer) PublicKey() *rsa.PublicKey
PublicKey returns the issuer's RSA public key. Tests use it to construct negative fixtures (e.g. algorithm-confusion forgeries).
type TokenOption ¶
type TokenOption func(*tokenConfig)
TokenOption customizes a minted token so tests can drive the verifier's issuer/audience/expiry/impersonation/break-glass checks.
func WithAMR ¶ added in v1.1.0
func WithAMR(amr ...string) TokenOption
WithAMR sets the standard amr (authentication-methods-references) claim (RFC 8176, e.g. WithAMR("pwd", "mfa")), driving the auth.Verifier's propagation into authz.Actor.AMR and the evaluator's step-up check.
func WithAudience ¶
func WithAudience(aud string) TokenOption
WithAudience overrides the aud claim (default "wowapi").
func WithBreakGlass ¶
func WithBreakGlass(on bool) TokenOption
WithBreakGlass sets the break_glass claim.
func WithExpiry ¶
func WithExpiry(d time.Duration) TokenOption
WithExpiry sets the token lifetime relative to now (default +1h). A negative value mints an already-expired token.
func WithImpersonator ¶
func WithImpersonator(id uuid.UUID) TokenOption
WithImpersonator sets the impersonator_user_id claim.
func WithIssuer ¶
func WithIssuer(iss string) TokenOption
WithIssuer overrides the iss claim (default "wowapi-test").
type WorkflowSim ¶
type WorkflowSim struct {
// contains filtered or unexported fields
}
WorkflowSim is a fluent driver that exercises a workflow.Runtime against a real test database (blueprint §1.3):
NewWorkflowSim(t, h, rt).
Start("requests.approval", res, input).
Approve("manager_review", approver).
ExpectStep("auto_provision").
ExpectStatus("completed")
Every step runs the transition through the Runtime and fails the test on error, so a test reads as the state machine it drives.
func NewWorkflowSim ¶
NewWorkflowSim binds a sim to a runtime and DB handle. The tenant is inferred from the first Start call's resource (looked up via the resources mirror).
func (*WorkflowSim) Approve ¶
func (s *WorkflowSim) Approve(stepKey string, asActor authz.Actor) *WorkflowSim
Approve records an approval on the open task at stepKey.
func (*WorkflowSim) ExpectStatus ¶
func (s *WorkflowSim) ExpectStatus(status string) *WorkflowSim
ExpectStatus asserts the instance's status.
func (*WorkflowSim) ExpectStep ¶
func (s *WorkflowSim) ExpectStep(stepKey string) *WorkflowSim
ExpectStep asserts the instance's current_step.
func (*WorkflowSim) InstanceID ¶
func (s *WorkflowSim) InstanceID() uuid.UUID
InstanceID returns the started instance id.
func (*WorkflowSim) Reject ¶
func (s *WorkflowSim) Reject(stepKey string, asActor authz.Actor, comment string) *WorkflowSim
Reject records a rejection (with comment) on the open task at stepKey.
func (*WorkflowSim) Start ¶
func (s *WorkflowSim) Start(defKey string, res resource.Ref, input map[string]any) *WorkflowSim
Start begins an instance in its own tenant transaction and remembers the id.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package fakes holds the deterministic test doubles wowapi injects through the same constructors production uses (08 §2): a manual-advance clock and a deterministic IDGen.
|
Package fakes holds the deterministic test doubles wowapi injects through the same constructors production uses (08 §2): a manual-advance clock and a deterministic IDGen. |