Jizoku.Test (jizoku v0.4.0)

Copy Markdown View Source

Deterministic helpers for exercising Jizoku workflows in tests.

Each runtime owns isolated in-memory journal state and executes through the same public start, execution, and inspection paths used by a host. Execution is bounded by default, and a quiescent nonterminal run is reported as {:blocked, snapshot} without wall-clock polling.

Stop runtimes explicitly with stop_runtime/1. Their storage process also monitors the process that created them, so ExUnit process exit cleans up abandoned runtimes.

Summary

Functions

Advances the runtime's virtual time without sleeping.

Approves the runtime's paused approval step through Jizoku.approve/3.

Drains a run and returns its snapshot when expected_status matches.

Cancels the runtime's root run through Jizoku.cancel/2.

Checks universal workflow invariants against one durable inspection snapshot.

Deletes every projection checkpoint in the isolated runtime.

Drains eligible work for the target run using the same bounded semantics as execute_until_blocked/3.

Executes until a durable snapshot satisfies predicate.

Executes until the target run is terminal or no work is currently eligible.

Returns the run's structured operator explanation.

Returns a versioned, redacted golden history for compatibility assertions.

Injects one expected-revision conflict at the next matching journal append.

Inspects a run through the runtime's isolated journal.

Returns the runtime's current virtual time.

Rejects the runtime's paused approval step through Jizoku.reject/3.

Restarts the isolated runtime while preserving its durable state and clock.

Resumes the runtime's paused root run through Jizoku.resume/3.

Resumes a paused run with either command options or manual action attributes.

Resumes a paused run with manual action attributes and command options.

Starts the runtime's workflow through Jizoku.start/3.

Starts the runtime's workflow through a declared cron trigger.

Starts an isolated in-memory workflow runtime owned by the calling process.

Stops a test runtime and discards all of its in-memory state.

Returns calls recorded by a configured deterministic action stub.

Returns the run's chronological, redaction-safe operator timeline.

Types

append_target()

@type append_target() :: :run | :dispatch

assertion_diagnostics()

@type assertion_diagnostics() :: :none | :timeline

execute_until_result()

@type execute_until_result() ::
  {:reached, Jizoku.ReadModel.Inspection.Snapshot.t()} | execution_result()

execution_result()

@type execution_result() ::
  {:blocked, Jizoku.ReadModel.Inspection.Snapshot.t()}
  | {:cancelled | :completed | :continued | :failed,
     Jizoku.ReadModel.Inspection.Snapshot.t()}
  | {:error, term()}

golden_history()

@type golden_history() :: %{
  schema_version: pos_integer(),
  workflow: String.t() | nil,
  queue: String.t(),
  partition: String.t() | nil,
  status: atom(),
  terminal_status: atom() | nil,
  events: [golden_history_event()]
}

golden_history_event()

@type golden_history_event() :: %{
  :type => atom(),
  :offset_us => integer() | nil,
  :run => String.t() | :malformed,
  optional(:step) => String.t(),
  optional(:runnable) => String.t() | :malformed,
  optional(:status) => atom(),
  optional(:details) => map()
}

invariant_report()

@type invariant_report() :: %{
  version: pos_integer(),
  run_id: String.t(),
  partition: String.t() | nil,
  queue: String.t(),
  thread_revisions: %{run: non_neg_integer(), dispatch: non_neg_integer()},
  violations: [invariant_violation()]
}

invariant_violation()

@type invariant_violation() :: %{code: invariant_violation_code(), details: map()}

invariant_violation_code()

@type invariant_violation_code() ::
  :duplicate_runnable_key
  | :malformed_runnable_key
  | :pending_and_applied
  | :pending_in_multiple_views
  | :projection_anomaly
  | :terminal_state_incoherent
  | :unknown_runnable

snapshot_predicate()

@type snapshot_predicate() :: (Jizoku.ReadModel.Inspection.Snapshot.t() -> boolean())

time_unit()

@type time_unit() :: :second | :millisecond | :microsecond

Functions

advance_time(runtime, amount, unit \\ :millisecond)

@spec advance_time(Jizoku.Test.Runtime.t(), non_neg_integer(), time_unit()) ::
  {:ok, DateTime.t()}
  | {:error,
     :runtime_owner_required
     | :runtime_busy
     | :runtime_stopped
     | {:invalid_option, {:amount | :unit, :invalid}}}

Advances the runtime's virtual time without sleeping.

Only the process that created the runtime may advance its clock. Supported units are :second, :millisecond, and :microsecond.

approve(runtime, run, attrs, opts \\ [])

@spec approve(
  Jizoku.Test.Runtime.t(),
  Ecto.UUID.t() | Jizoku.ReadModel.Inspection.Snapshot.t(),
  map(),
  keyword()
) :: {:ok, Jizoku.ReadModel.Inspection.Snapshot.t()} | {:error, term()}

Approves the runtime's paused approval step through Jizoku.approve/3.

assert_status(runtime, run, expected_status, opts \\ [])

@spec assert_status(
  Jizoku.Test.Runtime.t(),
  Ecto.UUID.t() | Jizoku.ReadModel.Inspection.Snapshot.t(),
  atom(),
  keyword()
) :: Jizoku.ReadModel.Inspection.Snapshot.t() | no_return()

Drains a run and returns its snapshot when expected_status matches.

A mismatch raises ExUnit.AssertionError. Failures are concise by default; pass diagnostics: :timeline to include the versioned, redacted golden history in ExUnit output. :max_steps has the same meaning as in drain/3.

cancel(runtime, run, opts \\ [])

@spec cancel(
  Jizoku.Test.Runtime.t(),
  Ecto.UUID.t() | Jizoku.ReadModel.Inspection.Snapshot.t(),
  keyword()
) :: {:ok, Jizoku.ReadModel.Inspection.Snapshot.t()} | {:error, term()}

Cancels the runtime's root run through Jizoku.cancel/2.

The optional :idempotency_key and :metadata values are forwarded to the durable command signal. Runtime routing and occurrence time always come from the isolated test runtime.

check_invariants(runtime, run)

@spec check_invariants(
  Jizoku.Test.Runtime.t(),
  Ecto.UUID.t() | Jizoku.ReadModel.Inspection.Snapshot.t()
) ::
  {:ok, Jizoku.ReadModel.Inspection.Snapshot.t()}
  | {:error, {:invariant_violations, invariant_report()}}
  | {:error, term()}

Checks universal workflow invariants against one durable inspection snapshot.

A successful check returns the inspected snapshot. Failures contain redaction-safe structural metadata such as stable identifiers, counts, classifications, and violation codes; workflow inputs, outputs, context, and raw errors are excluded.

delete_checkpoints(runtime)

@spec delete_checkpoints(Jizoku.Test.Runtime.t()) ::
  :ok | {:error, :runtime_busy | :runtime_owner_required | :runtime_stopped}

Deletes every projection checkpoint in the isolated runtime.

Journal threads remain unchanged. The next inspection or execution rebuilds projections through the normal journal replay path. Only the runtime owner may delete checkpoints, and deletion fails while execution is active.

drain(runtime, run, opts \\ [])

@spec drain(
  Jizoku.Test.Runtime.t(),
  Ecto.UUID.t() | Jizoku.ReadModel.Inspection.Snapshot.t(),
  keyword()
) :: execution_result()

Drains eligible work for the target run using the same bounded semantics as execute_until_blocked/3.

execute_until(runtime, run, predicate, opts \\ [])

@spec execute_until(
  Jizoku.Test.Runtime.t(),
  Ecto.UUID.t() | Jizoku.ReadModel.Inspection.Snapshot.t(),
  snapshot_predicate(),
  keyword()
) :: execute_until_result()

Executes until a durable snapshot satisfies predicate.

The predicate runs after each inspection and before terminal, blocked, or execution-limit classification. A match returns {:reached, snapshot}; otherwise the helper preserves the same results as execute_until_blocked/3.

execute_until_blocked(runtime, run, opts \\ [])

@spec execute_until_blocked(
  Jizoku.Test.Runtime.t(),
  Ecto.UUID.t() | Jizoku.ReadModel.Inspection.Snapshot.t(),
  keyword()
) :: execution_result()

Executes until the target run is terminal or no work is currently eligible.

Returns a terminal-status tuple or {:blocked, snapshot}. The optional :max_steps bound defaults to the runtime's configured bound.

explain(runtime, run)

@spec explain(
  Jizoku.Test.Runtime.t(),
  Ecto.UUID.t() | Jizoku.ReadModel.Inspection.Snapshot.t()
) ::
  {:ok, Jizoku.ReadModel.Explanation.Diagnostic.t()} | {:error, term()}

Returns the run's structured operator explanation.

golden_history(runtime, run)

@spec golden_history(
  Jizoku.Test.Runtime.t(),
  Ecto.UUID.t() | Jizoku.ReadModel.Inspection.Snapshot.t()
) ::
  {:ok, golden_history()} | {:error, term()}

Returns a versioned, redacted golden history for compatibility assertions.

Generated run and runnable identifiers are replaced with encounter-order aliases, timestamps become offsets from run start, and event details use a strict structural allowlist.

inject_append_conflict(runtime, target)

@spec inject_append_conflict(Jizoku.Test.Runtime.t(), append_target()) ::
  :ok
  | {:error,
     :append_conflict_already_armed
     | :run_not_started
     | :runtime_busy
     | :runtime_owner_required
     | :runtime_stopped
     | {:invalid_option, {:target, :invalid}}}

Injects one expected-revision conflict at the next matching journal append.

The target is the runtime's root run thread or configured dispatch thread. The conflict is consumed only by that exact partitioned thread, and the runtime owner must inject it while no execution or control helper is active.

inspect(runtime, run)

@spec inspect(
  Jizoku.Test.Runtime.t(),
  Ecto.UUID.t() | Jizoku.ReadModel.Inspection.Snapshot.t()
) ::
  {:ok, Jizoku.ReadModel.Inspection.Snapshot.t()} | {:error, term()}

Inspects a run through the runtime's isolated journal.

now(runtime)

@spec now(Jizoku.Test.Runtime.t()) :: {:ok, DateTime.t()} | {:error, :runtime_stopped}

Returns the runtime's current virtual time.

reject(runtime, run, attrs, opts \\ [])

@spec reject(
  Jizoku.Test.Runtime.t(),
  Ecto.UUID.t() | Jizoku.ReadModel.Inspection.Snapshot.t(),
  map(),
  keyword()
) :: {:ok, Jizoku.ReadModel.Inspection.Snapshot.t()} | {:error, term()}

Rejects the runtime's paused approval step through Jizoku.reject/3.

restart_runtime(runtime)

@spec restart_runtime(Jizoku.Test.Runtime.t()) ::
  {:ok, Jizoku.Test.Runtime.t()}
  | {:error,
     :runtime_busy | :runtime_owner_required | :runtime_stopped | term()}

Restarts the isolated runtime while preserving its durable state and clock.

The returned runtime has a fresh worker identity and storage process. The old runtime handle is stopped. Restart is owner-only and fails while a live execution or start reservation is active. Armed deterministic append faults remain armed across the restart.

resume(runtime, run)

@spec resume(
  Jizoku.Test.Runtime.t(),
  Ecto.UUID.t() | Jizoku.ReadModel.Inspection.Snapshot.t()
) ::
  {:ok, Jizoku.ReadModel.Inspection.Snapshot.t()} | {:error, term()}

Resumes the runtime's paused root run through Jizoku.resume/3.

resume(runtime, run, opts)

@spec resume(
  Jizoku.Test.Runtime.t(),
  Ecto.UUID.t() | Jizoku.ReadModel.Inspection.Snapshot.t(),
  keyword()
) :: {:ok, Jizoku.ReadModel.Inspection.Snapshot.t()} | {:error, term()}
@spec resume(
  Jizoku.Test.Runtime.t(),
  Ecto.UUID.t() | Jizoku.ReadModel.Inspection.Snapshot.t(),
  map()
) ::
  {:ok, Jizoku.ReadModel.Inspection.Snapshot.t()} | {:error, term()}

Resumes a paused run with either command options or manual action attributes.

resume(runtime, run, attrs, opts)

@spec resume(
  Jizoku.Test.Runtime.t(),
  Ecto.UUID.t() | Jizoku.ReadModel.Inspection.Snapshot.t(),
  map(),
  keyword()
) :: {:ok, Jizoku.ReadModel.Inspection.Snapshot.t()} | {:error, term()}

Resumes a paused run with manual action attributes and command options.

start(runtime, payload)

@spec start(Jizoku.Test.Runtime.t(), map()) ::
  {:ok, Jizoku.ReadModel.Inspection.Snapshot.t()} | {:error, term()}

Starts the runtime's workflow through Jizoku.start/3.

Each runtime owns one root run. Create another runtime when a test needs an unrelated run so bounded execution cannot claim work outside the target's workflow tree. The process that created the runtime must start its root; another process receives {:error, :runtime_owner_required}.

start_cron(runtime, trigger, input, opts \\ [])

@spec start_cron(Jizoku.Test.Runtime.t(), atom() | String.t(), map(), keyword()) ::
  {:ok, Jizoku.ReadModel.Inspection.Snapshot.t()} | {:error, term()}

Starts the runtime's workflow through a declared cron trigger.

Schedule receipt time, partition, queue, and storage come from the isolated runtime. Callers may provide signal :metadata or an explicit :idempotency_key; scheduler identity may also be carried in input through signal_id or a complete intended_window.

Each test runtime still owns one root run, regardless of whether it was started manually or through cron.

start_runtime(opts)

@spec start_runtime(keyword()) :: {:ok, Jizoku.Test.Runtime.t()} | {:error, term()}

Starts an isolated in-memory workflow runtime owned by the calling process.

:workflow is required and may be a compiled workflow module or a runtime-authored workflow spec. Runtime specs may use a host-owned :action_registry, deterministic :action_stubs, and a :guardrail_registry. :queue, :partition, :now, and :max_steps configure every helper call made through the returned runtime.

stop_runtime(runtime)

@spec stop_runtime(Jizoku.Test.Runtime.t()) :: :ok

Stops a test runtime and discards all of its in-memory state.

stub_calls(runtime, action_key)

@spec stub_calls(Jizoku.Test.Runtime.t(), Jizoku.Workflow.ActionRegistry.action_key()) ::
  {:ok, [map()]}
  | {:error, :run_not_started | :runtime_stopped | :unknown_action_stub}

Returns calls recorded by a configured deterministic action stub.

Calls are ordered by execution and include the application input plus durable run, runnable, step, and attempt identity. This is explicit test data and is not a redacted diagnostic surface.

timeline(runtime, run)

@spec timeline(
  Jizoku.Test.Runtime.t(),
  Ecto.UUID.t() | Jizoku.ReadModel.Inspection.Snapshot.t()
) ::
  {:ok, Jizoku.ReadModel.Timeline.t()} | {:error, term()}

Returns the run's chronological, redaction-safe operator timeline.