-module(aion@testing).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/aion/testing.gleam").
-export([new/0, process_key/1, run/1, advance/2, current_time_milliseconds/1, mock_activity/3, mock_child/6, observations/1, clear_observations/1, assert_replay/2]).
-export_type([test_env/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Pure Gleam workflow test harness.\n"
"\n"
" `aion/testing` is the recommended way to test workflow author code. It runs\n"
" under `gleam test` with no engine, beamr, store, external services, or Rust\n"
" NIFs. Test code initialises a process-scoped `TestEnv`; the test-only\n"
" Erlang module `test/aion_flow_ffi.erl` occupies the same production FFI\n"
" namespace so workflow code and `@external` declarations are byte-identical\n"
" in tests and production.\n"
).
-opaque test_env() :: {test_env, binary()}.
-file("src/aion/testing.gleam", 32).
?DOC(
" Build a fresh `TestEnv` for the current test process.\n"
"\n"
" The simulated clock, activity mock registry, child/query/signal fixtures, and\n"
" observation capture are reset for the current process only.\n"
).
-spec new() -> {ok, test_env()} | {error, aion@error:engine_error()}.
new() ->
case aion_flow_ffi:testing_reset() of
{ok, Process_key} ->
{ok, {test_env, Process_key}};
{error, Raw_error} ->
{error, {engine_failure, Raw_error}}
end.
-file("src/aion/testing.gleam", 40).
?DOC(" Return the process key assigned by the test FFI double.\n").
-spec process_key(test_env()) -> binary().
process_key(Env) ->
erlang:element(2, Env).
-file("src/aion/testing.gleam", 45).
?DOC(" Run a workflow thunk under a fresh process-scoped test environment.\n").
-spec run(fun((test_env()) -> ERE)) -> {ok, ERE} |
{error, aion@error:engine_error()}.
run(Workflow) ->
case new() of
{ok, Env} ->
{ok, Workflow(Env)};
{error, Engine_error} ->
{error, Engine_error}
end.
-file("src/aion/testing.gleam", 53).
?DOC(" Advance the simulated test clock by a canonical duration.\n").
-spec advance(test_env(), aion@duration:duration()) -> {ok, test_env()} |
{error, aion@error:engine_error()}.
advance(Env, By) ->
aion@testing@clock:advance(Env, By).
-file("src/aion/testing.gleam", 61).
?DOC(" Return the current simulated clock value in milliseconds.\n").
-spec current_time_milliseconds(test_env()) -> {ok, integer()} |
{error, aion@error:engine_error()}.
current_time_milliseconds(Env) ->
aion@testing@clock:current_time_milliseconds(Env).
-file("src/aion/testing.gleam", 68).
?DOC(" Register a typed activity mock for the current test process.\n").
-spec mock_activity(
test_env(),
aion@activity:activity(ERL, ERM),
fun((ERL) -> {ok, ERM} | {error, aion@error:activity_error()})
) -> {ok, test_env()} | {error, aion@error:engine_error()}.
mock_activity(Env, Activity_value, Handler) ->
aion@testing@mock:activity(Env, Activity_value, Handler).
-file("src/aion/testing.gleam", 82).
?DOC(
" Register a typed child-workflow double for the current test process.\n"
"\n"
" `workflow.spawn_and_wait` calls with the same child name run `handler`\n"
" in-process and record its typed result as the child terminal. Register the\n"
" child module's real `execute` function to exercise full parent-child\n"
" composition under `gleam test`.\n"
).
-spec mock_child(
test_env(),
binary(),
aion@codec:codec(ERT),
aion@codec:codec(ERV),
aion@codec:codec(ERX),
fun((ERT) -> {ok, ERV} | {error, ERX})
) -> {ok, test_env()} | {error, aion@error:engine_error()}.
mock_child(Env, Name, Input_codec, Output_codec, Error_codec, Handler) ->
aion@testing@mock:child(
Env,
Name,
Input_codec,
Output_codec,
Error_codec,
Handler
).
-file("src/aion/testing.gleam", 94).
?DOC(" Capture the current observation sequence emitted by the test FFI double.\n").
-spec observations(test_env()) -> {ok, binary()} |
{error, aion@error:engine_error()}.
observations(_) ->
case aion_flow_ffi:testing_observations() of
{ok, Raw} ->
{ok, Raw};
{error, Raw_error} ->
{error, {engine_failure, Raw_error}}
end.
-file("src/aion/testing.gleam", 102).
?DOC(" Clear the observation sequence for the current process.\n").
-spec clear_observations(test_env()) -> {ok, test_env()} |
{error, aion@error:engine_error()}.
clear_observations(Env) ->
case aion_flow_ffi:testing_clear_observations() of
{ok, _} ->
{ok, Env};
{error, Raw_error} ->
{error, {engine_failure, Raw_error}}
end.
-file("src/aion/testing.gleam", 114).
?DOC(
" Assert that a workflow emits the same observation sequence on a second run.\n"
"\n"
" This mirrors AD's production non-determinism detection in a lightweight test\n"
" harness: if replay emits different observable commands, the helper returns a\n"
" clear `ReplayError` diagnostic instead of requiring a live engine.\n"
).
-spec assert_replay(test_env(), fun(() -> {ok, ESH} | {error, ESI})) -> {ok,
ESH} |
{error, aion@testing@replay:replay_error(ESI)}.
assert_replay(Env, Workflow) ->
aion@testing@replay:assert_replay(Env, Workflow).