Documentation
¶
Overview ¶
Package auth0mock is the Go SDK for the auth0-mock control plane.
It wraps the /admin0/* HTTP surface of a running auth0-mock instance with a typed Go API, so test code can register stubs, inject custom JWT claims, set per-audience permissions, toggle MFA, and freeze / advance the mock's clock without hand-marshalling JSON.
This SDK is NOT for calling the mocked Auth0 APIs (/oauth/*, /api/v2/*) — point your existing Auth0 SDK (auth0-go, auth0-js) at the mock's base URL for that. The SDK is purely for shaping the mock's fixture state from Go code.
Running a mock ¶
The SDK assumes an auth0-mock instance is already reachable. Start one with Docker or the binary before any SDK call:
docker run --rm -p 8080:8080 ghcr.io/sergiught/auth0-mock:latest # or, from a source checkout: make watch
Quick start ¶
import (
"github.com/sergiught/auth0-mock/pkg/auth0mock"
"github.com/sergiught/auth0-mock/pkg/auth0mock/auth0mocktest"
)
func TestUserLookup(t *testing.T) {
ctx := context.Background()
c, err := auth0mock.NewClient("http://localhost:8080")
if err != nil {
t.Fatal(err)
}
// Reset on entry + exit, Verify all constraints at exit, in
// the correct LIFO order. The recommended one-liner setup.
auth0mocktest.Bracket(t, c)
// Stub a Management API response via the fluent builder.
// Apply returns a handle (*RegisteredExpectation) the rest of
// the test can chain hit-count constraints onto — discard with
// _ if you don't need it.
reg, err := c.ExpectGet("/api/v2/users/auth0|alice").
Respond(200).
JSON(map[string]any{"user_id": "auth0|alice", "email": "alice@example.com"}).
Apply(ctx)
if err != nil {
t.Fatal(err)
}
reg.Times(1) // Bracket's cleanup-side Verify will fail if not hit exactly once.
// Hand-build form if you already have an Expectation structured:
_, err = c.Expectations.Add(ctx, auth0mock.Expectation{
Method: "GET", Path: "/api/v2/users/{id}",
Response: auth0mock.ResponseDef{
Status: 200,
Body: auth0mock.MustJSON(map[string]any{"user_id": "any"}),
},
})
if err != nil {
t.Fatal(err)
}
// Freeze the mock's clock so tokens have deterministic
// iat/exp. The wire format is RFC 3339 with second precision,
// so any sub-second component of the time you pass is
// truncated — symmetric with what Get() reads back, but worth
// knowing if you compare a frozen Now against a time.Now()
// with nanos.
if err := c.Clock.Freeze(ctx, time.Date(2030, 1, 1, 0, 0, 0, 0, time.UTC)); err != nil {
t.Fatal(err)
}
// To restore real time, use Reset — NOT Offset(0). Offset(0)
// stays in "offset" mode (skew of zero), observationally
// identical to real for Now() but Get(...).Mode still reports
// "offset". Bracket calls Reset on cleanup so most tests don't
// need to think about this.
// ... call the system-under-test here ...
}
See examples/sdk in the repo for a runnable walk-through of every resource (expectations, claims, permissions, MFA), and examples/consumer for the worked-flow example with the real go-auth0 SDK calling through the mock.
Stability ¶
This package's API is unstable until the auth0-mock module reaches v1.0.0. Pin a tagged version (e.g. v0.227.0) and treat any minor bump as potentially breaking. The package doc will call out the stability promise when v1 is approaching.
No authentication ¶
/admin0/* is unauthenticated by design — there is no token to pass. Bind the mock to localhost or keep it inside your CI container; never expose it to an untrusted network.
Example (TestIntegration) ¶
Example_testIntegration shows the canonical bracket pattern using the auth0mocktest subpackage. Drop into every test that touches the mock so each test starts from a known-empty state and leaves no expectations behind. The auth0mocktest helpers live in the subpackage so production binaries that import auth0mock don't transitively import the testing package.
import (
"github.com/sergiught/auth0-mock/pkg/auth0mock"
"github.com/sergiught/auth0-mock/pkg/auth0mock/auth0mocktest"
)
func TestUserLookup(t *testing.T) {
c, _ := auth0mock.NewClient("http://localhost:8080")
auth0mocktest.Bracket(t, c)
reg := auth0mocktest.MustApply(t, c.ExpectGet("/api/v2/users/123").
Respond(200).
JSON(map[string]any{"user_id": "123"}))
reg.Times(1)
// ... call the system-under-test here ...
}
package main
import (
"github.com/sergiught/auth0-mock/pkg/auth0mock"
)
func main() {
c, _ := auth0mock.NewClient("http://localhost:8080")
_ = c
}
Output:
Index ¶
- func MustJSON(v any) json.RawMessage
- func NewEventID() string
- func NewStreamID() string
- type APIError
- type ClaimsClient
- type Client
- func (c *Client) BaseURL() string
- func (c *Client) Expect(method, path string) *ExpectationBuilder
- func (c *Client) ExpectDelete(path string) *ExpectationBuilder
- func (c *Client) ExpectGet(path string) *ExpectationBuilder
- func (c *Client) ExpectPatch(path string) *ExpectationBuilder
- func (c *Client) ExpectPost(path string) *ExpectationBuilder
- func (c *Client) ExpectPut(path string) *ExpectationBuilder
- func (c *Client) Reset(ctx context.Context) error
- type ClockClient
- func (cl *ClockClient) Advance(ctx context.Context, d time.Duration) error
- func (cl *ClockClient) Freeze(ctx context.Context, t time.Time) error
- func (cl *ClockClient) Get(ctx context.Context) (*ClockState, error)
- func (cl *ClockClient) Offset(ctx context.Context, d time.Duration) error
- func (cl *ClockClient) Reset(ctx context.Context) error
- type ClockState
- type EventsClient
- type Expectation
- type ExpectationBuilder
- func (b *ExpectationBuilder) Respond(status int) *ResponseBuilder
- func (b *ExpectationBuilder) WithBody(raw json.RawMessage) *ExpectationBuilder
- func (b *ExpectationBuilder) WithBodyJSON(v any) *ExpectationBuilder
- func (b *ExpectationBuilder) WithHeader(key, value string) *ExpectationBuilder
- func (b *ExpectationBuilder) WithQuery(key, value string) *ExpectationBuilder
- type ExpectationsClient
- func (e *ExpectationsClient) Add(ctx context.Context, exp Expectation) (*RegisteredExpectation, error)
- func (e *ExpectationsClient) Clear(ctx context.Context) error
- func (e *ExpectationsClient) ClearOp(ctx context.Context, method, path string) error
- func (e *ExpectationsClient) List(ctx context.Context) ([]Expectation, error)
- func (e *ExpectationsClient) Verify(ctx context.Context) error
- type MFAClient
- type Option
- type PermissionsClient
- func (p *PermissionsClient) All(ctx context.Context) (map[string][]string, error)
- func (p *PermissionsClient) Clear(ctx context.Context) error
- func (p *PermissionsClient) Delete(ctx context.Context, audience string) error
- func (p *PermissionsClient) Get(ctx context.Context, audience string) ([]string, error)
- func (p *PermissionsClient) Set(ctx context.Context, audience string, perms []string) error
- type RegisteredExpectation
- func (re *RegisteredExpectation) AnyTimes() *RegisteredExpectation
- func (re *RegisteredExpectation) AtLeast(n int64) *RegisteredExpectation
- func (re *RegisteredExpectation) AtMost(n int64) *RegisteredExpectation
- func (re *RegisteredExpectation) Clear(ctx context.Context) error
- func (re *RegisteredExpectation) Hits(ctx context.Context) (int64, error)
- func (re *RegisteredExpectation) Method() string
- func (re *RegisteredExpectation) Path() string
- func (re *RegisteredExpectation) RegisteredAt() string
- func (re *RegisteredExpectation) Times(n int64) *RegisteredExpectation
- type RequestMatcher
- type ResponseBuilder
- type ResponseDef
- type SubscriberCount
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func MustJSON ¶
func MustJSON(v any) json.RawMessage
MustJSON marshals v to a json.RawMessage and panics on error.
Use this only with values you know marshal cleanly — i.e. constants and literals in test code. For values that come from somewhere else at runtime, call json.Marshal yourself and handle the error. The point of MustJSON is to keep test setup one-liners:
c.Expectations.Add(ctx, auth0mock.Expectation{
Method: "GET", Path: "/api/v2/users/auth0|123",
Response: auth0mock.ResponseDef{
Status: 200,
Body: auth0mock.MustJSON(map[string]any{"user_id": "auth0|123"}),
},
})
func NewEventID ¶ added in v0.228.0
func NewEventID() string
NewEventID returns a fresh event ID conforming to Auth0's event-stream `id` pattern (`evt_` + 16 alphanumeric chars). Tests that don't need a specific id value can call this instead of hand-rolling a 16-character placeholder — the schema validator rejects anything that doesn't match the pattern, and a too-short or too-long literal is the most common paste-and-go mistake.
func NewStreamID ¶ added in v0.228.0
func NewStreamID() string
NewStreamID returns a fresh event-stream ID conforming to Auth0's `est_` + 16 alphanumeric chars pattern. Same rationale as NewEventID — saves callers from re-deriving "I need exactly 16 chars after the prefix" by trial and error.
Types ¶
type APIError ¶
type APIError struct {
StatusCode int `json:"statusCode"`
Reason string `json:"error"` // HTTP reason phrase, e.g. "Bad Request".
Message string `json:"message"`
ErrorCode string `json:"errorCode"`
}
APIError wraps a non-2xx response from the /admin0 plane. The mock answers errors with the same JSON envelope the real Auth0 API uses (statusCode / error / message / errorCode), so APIError carries all four fields verbatim and the SDK never invents its own shape.
StatusCode and Reason are always populated — synthesized from the HTTP response line when the server's envelope omits them. Message and ErrorCode reflect whatever the envelope carried (empty when absent). Use errors.As to extract:
var apiErr *auth0mock.APIError
if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusBadRequest {
// ...
}
Example ¶
ExampleAPIError shows how to extract the typed error from a server- side validation failure. The mock returns the same Auth0-shaped envelope on every /admin0 error, so errors.As gives you all four fields without casting through interface{}.
package main
import (
"context"
"errors"
"fmt"
"net/http"
"github.com/sergiught/auth0-mock/pkg/auth0mock"
)
func main() {
c, _ := auth0mock.NewClient("http://localhost:8080")
_, err := c.Expectations.Add(context.Background(), auth0mock.Expectation{
Method: "GET",
Path: "/api/v2/users",
Response: auth0mock.ResponseDef{}, // Missing Status → server rejects.
})
var apiErr *auth0mock.APIError
if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusBadRequest {
fmt.Println("validation failed:", apiErr.ErrorCode, apiErr.Message)
}
}
Output:
type ClaimsClient ¶
type ClaimsClient struct {
// contains filtered or unexported fields
}
ClaimsClient owns the /admin0/claims surface — the per-process map of custom JWT claims merged into every minted access token and id token. Reach it via Client.Claims.
Claims are global to the mock process: there's no per-subject or per-audience scoping at this layer (per-audience scopes/permissions live on Client.Permissions). For test isolation, call Clear from t.Cleanup.
func (*ClaimsClient) Clear ¶
func (cl *ClaimsClient) Clear(ctx context.Context) error
Clear removes every custom claim. Idempotent.
type Client ¶
type Client struct {
// Expectations is the typed entry point for the
// /admin0/expectations CRUD surface.
Expectations *ExpectationsClient
// Claims is the typed entry point for /admin0/claims — the
// per-process custom-claim map merged into every minted token.
Claims *ClaimsClient
// Permissions is the typed entry point for /admin0/permissions —
// the per-audience permission lists baked into the `permissions`
// claim of issued access tokens.
Permissions *PermissionsClient
// MFA is the typed entry point for /admin0/mfa-required — the
// process-wide toggle that gates the password and password-realm
// grants behind an MFA step-up.
MFA *MFAClient
// Clock is the typed entry point for /admin0/clock — runtime
// control over the mock's perception of time (freeze, offset,
// advance) that drives JWT iat/exp on mint and the bearer
// middleware's expiry check on validate.
Clock *ClockClient
// Events is the typed entry point for /admin0/events — pushes
// Auth0 event-stream envelopes into the SSE hub feeding GET
// /events. Mirrors the production pattern where event-stream
// producers POST events for fan-out to subscribed consumers.
Events *EventsClient
// contains filtered or unexported fields
}
Client is a typed handle to a running auth0-mock control plane (the /admin0/* HTTP surface). Safe for concurrent use across goroutines — every sub-client shares the same underlying *http.Client.
/admin0/* is unauthenticated by design; Client does not carry a token and offers no way to set one. Bind the mock to localhost or keep it inside your CI container.
func NewClient ¶
NewClient binds to a running auth0-mock instance at baseURL.
BaseURL is the root the mock is serving from — for example "http://localhost:8080" or "https://localhost:8443" — NOT the /admin0 prefix. A trailing slash is tolerated and trimmed.
Returns an error if baseURL is empty, unparsable, or missing a scheme or host. Failing fast at construction keeps typo'd URLs from looking like cryptic transport errors on the first SDK call. For tests, the auth0mocktest.Must* helpers are convenient wrappers that t.Fatal on this error.
Example ¶
ExampleNewClient shows the smallest possible round-trip — bind to a running mock and clear its state. NewClient returns an error if baseURL is empty, unparsable, or missing a scheme or host (e.g. "localhost:8080" without "http://"); failing fast here keeps typos from looking like cryptic transport errors on the first SDK call.
package main
import (
"context"
"log"
"github.com/sergiught/auth0-mock/pkg/auth0mock"
)
func main() {
c, err := auth0mock.NewClient("http://localhost:8080")
if err != nil {
log.Fatal(err)
}
if err := c.Reset(context.Background()); err != nil {
log.Fatal(err)
}
}
Output:
func (*Client) BaseURL ¶
BaseURL returns the server root the client is bound to. Mostly useful for logging and for the testing.TB helpers.
func (*Client) Expect ¶
func (c *Client) Expect(method, path string) *ExpectationBuilder
Expect starts a stub for the given method + path. Method is upper-cased server-side, so callers can pass either case.
Example ¶
ExampleClient_Expect shows the request-matched variant — when one operation needs to return different responses based on the request body. The catch-all (no WithBodyJSON) covers everything else.
package main
import (
"context"
"github.com/sergiught/auth0-mock/pkg/auth0mock"
)
func main() {
c, _ := auth0mock.NewClient("http://localhost:8080")
ctx := context.Background()
// Catch-all stub: any POST to /api/v2/users returns u_default.
_, _ = c.ExpectPost("/api/v2/users").
Respond(201).
JSON(map[string]any{"id": "u_default"}).
Apply(ctx)
// Higher-priority stub: only matches when the body has name=alice.
_, _ = c.ExpectPost("/api/v2/users").
WithBodyJSON(map[string]any{"name": "alice"}).
Respond(201).
JSON(map[string]any{"id": "u_alice"}).
Apply(ctx)
}
Output:
Example (ResolutionRules) ¶
ExampleClient_Expect_resolutionRules shows the four-tier resolution rule end-to-end: a concrete-path stub beats a template stub, and within a path a request-matched stub beats a catch-all.
package main
import (
"context"
"github.com/sergiught/auth0-mock/pkg/auth0mock"
)
func main() {
c, _ := auth0mock.NewClient("http://localhost:8080")
ctx := context.Background()
// Tier 1 — template catch-all. Wins for any user id NOT named below.
_, _ = c.ExpectGet("/api/v2/users/{id}").
Respond(200).
JSON(map[string]any{"user_id": "any"}).
Apply(ctx)
// Tier 2 — concrete-path stub beats the template for this id.
_, _ = c.ExpectGet("/api/v2/users/auth0|alice").
Respond(200).
JSON(map[string]any{"user_id": "auth0|alice"}).
Apply(ctx)
// Tier 3 — request-matched stub beats the catch-all on POST /users.
_, _ = c.ExpectPost("/api/v2/users").
Respond(201).
JSON(map[string]any{"id": "default"}).
Apply(ctx)
_, _ = c.ExpectPost("/api/v2/users").
WithBodyJSON(map[string]any{"name": "alice"}).
Respond(201).
JSON(map[string]any{"id": "u_alice"}).
Apply(ctx)
}
Output:
func (*Client) ExpectDelete ¶
func (c *Client) ExpectDelete(path string) *ExpectationBuilder
ExpectDelete starts a stub for an incoming DELETE to path.
func (*Client) ExpectGet ¶
func (c *Client) ExpectGet(path string) *ExpectationBuilder
ExpectGet starts a stub for an incoming GET to path. Convenience wrapper around Expect("GET", path).
Example ¶
ExampleClient_ExpectGet shows the recommended fluent registration path for the 80% case — stub a single GET, respond with a JSON body.
package main
import (
"context"
"log"
"github.com/sergiught/auth0-mock/pkg/auth0mock"
)
func main() {
c, _ := auth0mock.NewClient("http://localhost:8080")
_, err := c.ExpectGet("/api/v2/users/auth0|alice").
Respond(200).
JSON(map[string]any{
"user_id": "auth0|alice",
"email": "alice@example.com",
}).
Apply(context.Background())
if err != nil {
log.Fatal(err)
}
}
Output:
func (*Client) ExpectPatch ¶
func (c *Client) ExpectPatch(path string) *ExpectationBuilder
ExpectPatch starts a stub for an incoming PATCH to path.
func (*Client) ExpectPost ¶
func (c *Client) ExpectPost(path string) *ExpectationBuilder
ExpectPost starts a stub for an incoming POST to path.
func (*Client) ExpectPut ¶
func (c *Client) ExpectPut(path string) *ExpectationBuilder
ExpectPut starts a stub for an incoming PUT to path.
func (*Client) Reset ¶
Reset clears every expectation, claim, permission, and MFA flag back to the mock's startup defaults, and restores the clock to real mode. Equivalent to restarting the mock process from the perspective of registered state, but ~1000x faster — call this from t.Cleanup so each test starts from a known-empty mock.
Also drops the Client's local verification ledger (the list of *RegisteredExpectation handles tracked for Expectations.Verify) so post-Reset Verify is a clean slate matching the server's empty state. If the ledger was non-empty when dropped — i.e. there were unverified Times/AtLeast/AtMost constraints — a flag is raised so the next Verify() returns an actionable error pointing at the cleanup-ordering bug (rather than silently passing because the constraints are gone). [auth0mocktest.Bracket] gets the order right; manual t.Cleanup setups can invert it, which this guard catches.
Returns *APIError for non-2xx responses; a wrapped transport error otherwise. The local ledger is dropped regardless of server errors — partial failures on Reset are unrecoverable anyway, and a dirty ledger would only make future Verify output more confusing.
type ClockClient ¶ added in v0.227.0
type ClockClient struct {
// contains filtered or unexported fields
}
ClockClient is the typed entry point for /admin0/clock — runtime control over the mock's perception of time. Reach it via Client.Clock.
The mock has one global clock; ClockClient mutations are process-global. Tests that share a single mock across parallel workers should serialise clock mutations.
func (*ClockClient) Advance ¶ added in v0.227.0
Advance mutates the held value by d. In frozen mode the pinned instant moves by d; in offset mode the configured offset grows by d. In real mode the server returns *APIError with ErrorCode "invalid_clock_state" — Freeze or Offset first.
func (*ClockClient) Freeze ¶ added in v0.227.0
Freeze pins the mock's clock to t. Subsequent token mints and bearer validations see Now == t until the next Freeze/Offset/Reset.
The wire format is RFC 3339 with second precision, so any sub-second component of t is truncated — i.e. Freeze(t) on the client and Get(...).Now on the way back round-trip equal up to the second, not the nanosecond. The server's Mode/State accessors use the same precision, so the round-trip is symmetric — just don't compare a frozen `Now` to a `time.Now()`-with-nanos and expect Equal to hold.
func (*ClockClient) Get ¶ added in v0.227.0
func (cl *ClockClient) Get(ctx context.Context) (*ClockState, error)
Get returns the current clock state.
func (*ClockClient) Offset ¶ added in v0.227.0
Offset switches the mock's clock into offset mode with skew d. The wall clock keeps ticking; Now returns time.Now() + d server-side. Replaces any prior offset value (does not add to it — use Advance for incremental shifts).
Offset(0) is allowed and stays in "offset" mode with a zero skew — observationally identical to real mode, but Get(...).Mode still reports "offset". Use Reset to actually return the clock to real.
type ClockState ¶ added in v0.227.0
ClockState mirrors GET /admin0/clock.
Mode is one of "real", "frozen", or "offset". Now is the current effective time according to the mock — what clock.Now() returns server-side. Offset is the configured skew, populated only when Mode == "offset" (zero otherwise).
type EventsClient ¶ added in v0.228.0
type EventsClient struct {
// contains filtered or unexported fields
}
EventsClient pushes events into the mock's SSE hub. Reach it via Client.Events. Push is fire-and-forget on the consumer side — the mock fans the event out to every currently-connected subscriber and records it in the bounded replay buffer for reconnect.
func (*EventsClient) Push ¶ added in v0.228.0
func (e *EventsClient) Push(ctx context.Context, payload json.RawMessage) error
Push POSTs an Auth0 event-stream envelope to /admin0/events. Payload is the full envelope ({type, offset, event:{...}}); the mock validates it against the OpenAPI EventStreamSubscribeEventsResponseContent schema at push time and returns *APIError with errorCode "invalid_event" on validation failure. The event's `type` (outer) drives `?event_type` filtering; the inner `event.id` is what subscribers see in the SSE `id:` line.
The SDK deliberately keeps `payload` raw rather than enumerating every CloudEvent variant from Auth0's spec: callers stay in control, the schema is enforced server-side, and the SDK never becomes a translation hop that masks misshapen test data.
Returns nil on 202 Accepted; *APIError on any non-2xx, decoded from the Auth0 error envelope by the shared transport helper.
func (*EventsClient) Subscribers ¶ added in v0.229.0
func (e *EventsClient) Subscribers(ctx context.Context) (SubscriberCount, error)
Subscribers reports the SSE hub's live and lifetime-within-window subscriber counts. Intended for tests that assert on connection lifecycle — e.g. "after closing my stream, active drops back to 0". Because Active is eventually-consistent, prefer auth0mocktest.WaitForActiveSubscribers when asserting a count settles rather than reading it once.
type Expectation ¶
type Expectation struct {
ID string `json:"id,omitempty"`
Method string `json:"method"`
Path string `json:"path"`
Request *RequestMatcher `json:"request,omitempty"`
Response ResponseDef `json:"response"`
Hits int64 `json:"hits,omitempty"`
}
Expectation is a stored mock response paired with an optional request matcher. Concrete paths ("/api/v2/users/auth0|123") and OpenAPI templates ("/api/v2/users/{id}") both work; the server infers which is which from the path syntax. A nil Request is a catch-all that matches every call to the operation.
ID is assigned by the server on Add() and surfaced in List() responses. Callers leave it empty when constructing an Expectation for Add; the SDK returns the server-assigned ID via *RegisteredExpectation.
Hits is populated by List() — the count of incoming requests this expectation has matched and served. Use [Client.Expectations.List] for snapshot assertions or RegisteredExpectation.Hits for fresh per-stub reads.
Resolution rules (server-side, locked by integration tests in internal/mgmtapi):
- Exact-path stubs beat template stubs.
- Within a path, a request-matched expectation beats a catch-all.
- Within a tier, newest wins (registration order, not body content).
type ExpectationBuilder ¶
type ExpectationBuilder struct {
// contains filtered or unexported fields
}
ExpectationBuilder is the request-side phase of the fluent stub registration API. Construct one via the per-method shortcuts (Client.ExpectGet, ExpectPost) or the generic Client.Expect, then chain WithQuery / WithBodyJSON / WithBody to narrow the matcher and Respond to enter the response-side phase.
All methods mutate and return the receiver — the same pattern strings.Builder uses. Don't share a Builder across goroutines mid-build; do whatever you want with the *Client itself.
Marshal errors from WithBodyJSON / Respond.JSON do NOT panic mid- chain; they're captured on the builder and surfaced by Apply(ctx) alongside transport errors. This keeps the fluent path readable and means a bad value in test setup fails one test cleanly instead of crashing the whole test binary.
func (*ExpectationBuilder) Respond ¶
func (b *ExpectationBuilder) Respond(status int) *ResponseBuilder
Respond enters the response-side phase. Status is mandatory — the server returns 400 invalid_body for a stub with status 0.
func (*ExpectationBuilder) WithBody ¶
func (b *ExpectationBuilder) WithBody(raw json.RawMessage) *ExpectationBuilder
WithBody narrows the matcher to requests whose JSON body is a superset of raw. Use this when you have a pre-encoded json.RawMessage or need to defer marshalling.
func (*ExpectationBuilder) WithBodyJSON ¶
func (b *ExpectationBuilder) WithBodyJSON(v any) *ExpectationBuilder
WithBodyJSON narrows the matcher to requests whose JSON body is a superset of v. V is marshalled with json.Marshal — if marshalling fails (chan / func / cyclic value) the error is captured on the builder and returned by Apply(ctx) instead of panicking mid-chain. For pre-encoded bodies use WithBody.
func (*ExpectationBuilder) WithHeader ¶
func (b *ExpectationBuilder) WithHeader(key, value string) *ExpectationBuilder
WithHeader narrows the matcher to requests that carry this header with the given value. Repeated calls add more headers; later values for the same key overwrite earlier ones. Comparison is case-insensitive server-side (canonical MIME header form), so "X-Tenant" and "x-tenant" address the same header.
Subset semantics — extra incoming headers don't disqualify a match.
func (*ExpectationBuilder) WithQuery ¶
func (b *ExpectationBuilder) WithQuery(key, value string) *ExpectationBuilder
WithQuery narrows the matcher to requests that include this query-string key + value. Repeated calls add more keys; later values for the same key overwrite earlier ones. Subset semantics — extra incoming query keys don't disqualify a match.
type ExpectationsClient ¶
type ExpectationsClient struct {
// contains filtered or unexported fields
}
ExpectationsClient owns the /admin0/expectations CRUD surface. Reach it via Client.Expectations; construction is internal.
func (*ExpectationsClient) Add ¶
func (e *ExpectationsClient) Add(ctx context.Context, exp Expectation) (*RegisteredExpectation, error)
Add registers an Expectation against the mock and returns a handle to the stored entry. The handle exposes per-stub operations (Clear, Hits, Times/AtLeast/AtMost/AnyTimes constraints checked by Verify). Discard the handle (assign to `_`) if you don't need per-stub introspection.
Returns *APIError for validation failures — known error codes are invalid_body, unknown_operation, invalid_match, invalid_request_match. The server validates response.body against the operation's response schema at registration time so test code fails on bad fixtures instead of at SDK-call time.
The Client tracks every returned handle so Verify can iterate them; Reset() clears the tracking list alongside the server-side state.
func (*ExpectationsClient) Clear ¶
func (e *ExpectationsClient) Clear(ctx context.Context) error
Clear removes every registered Expectation. Idempotent — safe to call from t.Cleanup, and safe to call before any expectations have been registered. Also empties the Client's local verification ledger so Verify after Clear doesn't carry stale handles.
func (*ExpectationsClient) ClearOp ¶
func (e *ExpectationsClient) ClearOp(ctx context.Context, method, path string) error
ClearOp removes every Expectation registered for a specific {method, path} pair — the catch-all and every request-matched variant. Forgiving: clearing an operation that was never registered is a no-op (returns nil, not an *APIError). Also prunes the Client's local verification ledger of any handles for that operation.
func (*ExpectationsClient) List ¶
func (e *ExpectationsClient) List(ctx context.Context) ([]Expectation, error)
List returns every registered Expectation in registration order (newest last). Useful for asserting that test setup actually registered what you expected.
func (*ExpectationsClient) Verify ¶
func (e *ExpectationsClient) Verify(ctx context.Context) error
Verify checks every registered expectation that has a hit-count constraint (Times / AtLeast / AtMost) against the server's actual counter. Returns a single error joining every violation; returns nil when all constraints are satisfied or no constraints were set.
Verify queries List once and matches by ID, so it's O(N) in the number of registered expectations regardless of how many have constraints. Use it as the test-end assertion:
if err := c.Expectations.Verify(ctx); err != nil {
t.Fatal(err)
}
Or use auth0mocktest.MustVerify(t, c) for the t.Fatal wrapper.
Example ¶
ExampleExpectationsClient_Verify shows the canonical verification flow: register stubs, chain hit-count constraints onto the handles they return, run the test, and check every constraint at the end.
In real test code prefer auth0mocktest.MustVerify(t, c), which t.Fatals on the error so the cleanup site reads as one line.
package main
import (
"context"
"log"
"github.com/sergiught/auth0-mock/pkg/auth0mock"
)
func main() {
c, _ := auth0mock.NewClient("http://localhost:8080")
ctx := context.Background()
// "exactly once" — typical for one-shot operations.
reg1, _ := c.ExpectGet("/api/v2/users/auth0|alice").
Respond(200).
JSON(map[string]any{"user_id": "auth0|alice"}).
Apply(ctx)
reg1.Times(1)
// "at least once" — useful when the SUT may retry.
reg2, _ := c.ExpectPost("/api/v2/users").
Respond(201).
JSON(map[string]any{"id": "new"}).
Apply(ctx)
reg2.AtLeast(1)
// "never hit" — guards against the SUT accidentally calling a
// fallback path you didn't expect.
reg3, _ := c.ExpectDelete("/api/v2/users/auth0|alice").
Respond(204).
Apply(ctx)
reg3.Times(0) // Equivalent to .AtMost(0).
// ... Exercise the system-under-test here ...
// Verify joins every violation into one error. Returns nil when
// every constraint is satisfied or no constraint was set.
if err := c.Expectations.Verify(ctx); err != nil {
log.Fatal(err)
}
}
Output:
type MFAClient ¶
type MFAClient struct {
// contains filtered or unexported fields
}
MFAClient owns the /admin0/mfa-required toggle — a single boolean that controls whether the password and password-realm grants demand an MFA step-up before issuing a token. Reach it via Client.MFA.
The toggle is global to the mock process. There's no per-user or per-connection scoping at this layer; tests that want to flip MFA mid-flow simply call SetRequired before each phase.
type Option ¶
type Option func(*Client)
Option configures a Client at construction.
func WithHTTPClient ¶
WithHTTPClient overrides the default *http.Client. Use this to plug in your own retries, transports, or timeouts — for example a client with a longer timeout when the mock is behind a slow CI network.
type PermissionsClient ¶
type PermissionsClient struct {
// contains filtered or unexported fields
}
PermissionsClient owns the /admin0/permissions surface — the per-audience permission lists that flow into the `permissions` claim of issued access tokens. Reach it via Client.Permissions.
Audiences are arbitrary strings, often Auth0-style URL audiences (e.g. "https://api.example.com/"). The SDK url.PathEscape-encodes the audience before appending it to the path, so slashes, "?", and "#" characters all round-trip safely; the server's chi wildcard route URL-decodes on the way back.
func (*PermissionsClient) All ¶
All returns the full per-audience permission map. Useful for snapshot-style assertions.
func (*PermissionsClient) Clear ¶
func (p *PermissionsClient) Clear(ctx context.Context) error
Clear removes every audience's permissions. Idempotent.
func (*PermissionsClient) Delete ¶
func (p *PermissionsClient) Delete(ctx context.Context, audience string) error
Delete clears the permissions for one audience. Idempotent — deleting a never-set audience is a no-op (no error).
type RegisteredExpectation ¶
type RegisteredExpectation struct {
// ID is the server-assigned identifier for this expectation.
ID string
// contains filtered or unexported fields
}
RegisteredExpectation is the handle returned by Expectations.Add and the fluent ResponseBuilder.Apply. It identifies a single stored stub and exposes per-stub operations (Clear, Hits, Times / AtLeast / AtMost / AnyTimes).
Lifetime ¶
The handle remains usable for as long as the underlying expectation is registered server-side. After RegisteredExpectation.Clear, Client.Reset, or any per-operation clear that drops this stub:
- RegisteredExpectation.Hits returns *APIError with statusCode 404 and errorCode "unknown_id".
- ExpectationsClient.Verify reports the handle as "cleared" and fails any non-zero Times/AtLeast constraint it carried, since "cleared before Verify ran" means the SUT never had a chance to hit the stub.
Concurrency ¶
Wire-call methods (Clear, Hits) are safe for concurrent use across goroutines. Constraint setters (Times / AtLeast / AtMost / AnyTimes) mutate the handle in place and are NOT synchronised — call them from one goroutine at registration time, not from the SUT under test.
func (*RegisteredExpectation) AnyTimes ¶
func (re *RegisteredExpectation) AnyTimes() *RegisteredExpectation
AnyTimes opts this stub out of Verify checks entirely — equivalent to leaving Times/AtLeast/AtMost unset. Useful when you've previously set a constraint and want to drop it without losing the handle.
func (*RegisteredExpectation) AtLeast ¶
func (re *RegisteredExpectation) AtLeast(n int64) *RegisteredExpectation
AtLeast sets a lower-bound expectation: Verify(ctx) will fail unless this stub was matched at least n times. Useful for retry-tolerant flows where the SUT may make extra calls.
Mutates the handle in place and returns it for chaining; overwrites any prior Times / AtMost / AtLeast — see RegisteredExpectation.Times for the last-write-wins contract.
func (*RegisteredExpectation) AtMost ¶
func (re *RegisteredExpectation) AtMost(n int64) *RegisteredExpectation
AtMost sets an upper-bound expectation: Verify(ctx) will fail unless this stub was matched at most n times. Useful for catching SUT bugs that retry too aggressively.
Mutates the handle in place and returns it for chaining; overwrites any prior Times / AtLeast / AtMost — see RegisteredExpectation.Times for the last-write-wins contract.
func (*RegisteredExpectation) Clear ¶
func (re *RegisteredExpectation) Clear(ctx context.Context) error
Clear removes this specific expectation. Idempotent — clearing an already-cleared expectation is a no-op (no error). Also removes the handle from the Client's local verification ledger so Verify after Clear doesn't report it as "cleared before Verify ran".
func (*RegisteredExpectation) Hits ¶
func (re *RegisteredExpectation) Hits(ctx context.Context) (int64, error)
Hits returns the current number of times this expectation has been matched and served by the mock. Fresh read on every call — the SDK doesn't cache. Returns *APIError if the expectation has been cleared (404).
func (*RegisteredExpectation) Method ¶
func (re *RegisteredExpectation) Method() string
Method returns the HTTP method this expectation was registered for, snapshotted at Add() time.
func (*RegisteredExpectation) Path ¶
func (re *RegisteredExpectation) Path() string
Path returns the path (concrete or template) this expectation was registered for, snapshotted at Add() time.
func (*RegisteredExpectation) RegisteredAt ¶
func (re *RegisteredExpectation) RegisteredAt() string
RegisteredAt returns the "file:line" location of the caller that invoked Add() to register this expectation, as captured by the runtime stack walk. Returns "" if the caller couldn't be resolved (shouldn't happen in practice). Useful for logging "what was this handle for?" without re-listing every server-side expectation.
func (*RegisteredExpectation) Times ¶
func (re *RegisteredExpectation) Times(n int64) *RegisteredExpectation
Times sets an exact-count expectation: Verify(ctx) will fail unless this stub was matched exactly n times. Equivalent to gomock's .Times(n). Setting n = 0 asserts the stub is never hit (equivalent to AtMost(0)); use AnyTimes() to opt out of count checks instead.
Mutates the handle in place and returns it for chaining. The constraint slot holds exactly one entry — calling Times after AtLeast / AtMost / Times overwrites the previous constraint. "Last-write-wins" so the most recent intention is the one Verify asserts.
Times is silent feedback: the constraint is only checked when [Client.Expectations.Verify] or [auth0mocktest.MustVerify] runs. A test that sets a constraint but never calls Verify will pass regardless of whether the stub was hit. Use auth0mocktest.Bracket to wire Verify into t.Cleanup automatically.
type RequestMatcher ¶
type RequestMatcher struct {
Query map[string]string `json:"query,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
Body json.RawMessage `json:"body,omitempty"`
}
RequestMatcher narrows an Expectation to requests that satisfy a subset of query parameters, headers, and/or a JSON body. Subset semantics — extra incoming fields don't disqualify a match. A nil RequestMatcher is a catch-all; a non-nil one with every field empty collapses to the same catch-all server-side.
Headers are compared case-insensitively (HTTP canonical form). Use header matchers to stub different responses based on Authorization (Bearer vs DPoP), tenant headers, Accept-Language, etc.
type ResponseBuilder ¶
type ResponseBuilder struct {
// contains filtered or unexported fields
}
ResponseBuilder is the response-side phase. Chain JSON / Body / Header to populate the canned response, then Apply to send the registration to the mock.
func (*ResponseBuilder) Apply ¶
func (r *ResponseBuilder) Apply(ctx context.Context) (*RegisteredExpectation, error)
Apply registers the assembled Expectation with the mock and returns a handle to the stored entry. Equivalent to Client.Expectations.Add composed with the fluent chain — discard the handle (assign to `_`) if you don't need per-stub operations later.
Returns:
- any marshal error captured earlier in the chain by WithBodyJSON or Respond.JSON (wrapped with the call-site name),
- or *APIError for server-side validation failures,
- or a transport error from the http.Client.
func (*ResponseBuilder) Body ¶
func (r *ResponseBuilder) Body(raw json.RawMessage) *ResponseBuilder
Body sets the response body to the pre-encoded raw bytes. Useful when you've already marshalled the body elsewhere or you want to send something other than JSON (the server doesn't enforce a content-type — it'll happily return raw bytes verbatim).
func (*ResponseBuilder) Header ¶
func (r *ResponseBuilder) Header(key, value string) *ResponseBuilder
Header sets a response header. Repeated calls add more headers; later values for the same key overwrite earlier ones.
func (*ResponseBuilder) JSON ¶
func (r *ResponseBuilder) JSON(v any) *ResponseBuilder
JSON sets the response body by marshalling v with json.Marshal. A marshal error is captured on the parent builder and surfaced by Apply(ctx) — JSON never panics on its own. For pre-encoded bytes or non-JSON payloads, use Body.
type ResponseDef ¶
type ResponseDef struct {
Status int `json:"status"`
Headers map[string]string `json:"headers,omitempty"`
Body json.RawMessage `json:"body,omitempty"`
}
ResponseDef is the canned response an Expectation returns. Status is required — POSTing with Status: 0 returns 400 invalid_body.
type SubscriberCount ¶ added in v0.229.0
SubscriberCount mirrors GET /admin0/events/subscribers.
Active is how many subscribers are connected to GET /events right now; it is eventually-consistent — the hub drops a subscriber only when the server observes its connection close, so a reading taken immediately after a client disconnects may briefly lag. Total is how many have connected since the last /admin0/reset and never decreases within a window (handy for asserting reconnection behaviour). Active and Total increment together on connect, so once Active has settled after a connection event, Total has already counted it.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package auth0mocktest provides testing.TB-aware helpers for the pkg/auth0mock SDK.
|
Package auth0mocktest provides testing.TB-aware helpers for the pkg/auth0mock SDK. |