testkit

package
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 10, 2026 License: Apache-2.0 Imports: 38 Imported by: 0

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

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

func AssertLocalizedProblem(t *testing.T, resp *http.Response, wantCode, wantTitle string)

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

func AssertNegotiatedLocale(t *testing.T, cat *i18n.Catalog, acceptLang, wantLocale string)

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

func CreateCapacity(t *testing.T, h *DBHandle, tenant, userID uuid.UUID) uuid.UUID

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

func CreatePermission(t *testing.T, h *DBHandle, key string, sensitive bool)

CreatePermission inserts a permission into the global catalog.

func CreateProbeTable

func CreateProbeTable(t *testing.T, h *DBHandle) string

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

func CreateResourceType(t *testing.T, h *DBHandle, key string)

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

func CreateRole(t *testing.T, h *DBHandle, tenant uuid.UUID, key string, perms ...string) uuid.UUID

CreateRole inserts a tenant role granting the given permissions and returns id.

func CreateUser

func CreateUser(t *testing.T, h *DBHandle) uuid.UUID

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

func NewLocaleRequest(method, target, locale string, cat *i18n.Catalog) *http.Request

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

func RunModuleContract(t *testing.T, m module.Module)

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.

func TenantCtx

func TenantCtx(tenant uuid.UUID) context.Context

TenantCtx returns a context scoped to the tenant, for driving h.TxM.

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

func NewDB(t testing.TB) *DBHandle

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

type RowFactory func(tenant uuid.UUID) map[string]any

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

type TenantHandle struct {
	ID uuid.UUID
}

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

func NewWorkflowSim(t *testing.T, h *DBHandle, rt *workflow.Runtime) *WorkflowSim

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.

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.

Jump to

Keyboard shortcuts

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