Skip to main content

src/aion/testing/replay.gleam

//// Replay assertions for `aion/testing`.
////
//// The helper runs a workflow twice in the same process-scoped harness, compares
//// the captured observation sequences, and reports mismatches as typed data. This
//// is a test-time simulation of AD's non-determinism detection: production replay
//// remains the responsibility of AD, but workflow authors can catch accidental
//// unrecorded branching in ordinary `gleam test` suites.

import aion/error
import aion/internal/ffi

/// A replay assertion failure.
pub type ReplayError(workflow_error) {
  /// The workflow returned an error during either the first run or replay.
  WorkflowFailed(workflow_error)

  /// The two runs completed but emitted different observable command sequences.
  ObservationMismatch(recorded: String, replayed: String)

  /// The test harness itself failed while capturing or resetting observations.
  ReplayHarnessFailure(error.EngineError)
}

/// Run a workflow twice and require identical observations.
pub fn assert_replay(
  _env: env,
  workflow: fn() -> Result(value, workflow_error),
) -> Result(value, ReplayError(workflow_error)) {
  case clear() {
    Error(harness_error) -> Error(harness_error)
    Ok(_) ->
      case workflow() {
        Error(workflow_error) -> Error(WorkflowFailed(workflow_error))
        Ok(value) ->
          case capture() {
            Error(harness_error) -> Error(harness_error)
            Ok(recorded) -> compare_replay(value, recorded, workflow)
          }
      }
  }
}

fn compare_replay(
  value: value,
  recorded: String,
  workflow: fn() -> Result(value, workflow_error),
) -> Result(value, ReplayError(workflow_error)) {
  case clear() {
    Error(harness_error) -> Error(harness_error)
    Ok(_) ->
      case workflow() {
        Error(workflow_error) -> Error(WorkflowFailed(workflow_error))
        Ok(_) ->
          case capture() {
            Error(harness_error) -> Error(harness_error)
            Ok(replayed) ->
              case recorded == replayed {
                True -> Ok(value)
                False ->
                  Error(ObservationMismatch(
                    recorded: recorded,
                    replayed: replayed,
                  ))
              }
          }
      }
  }
}

fn clear() -> Result(Nil, ReplayError(workflow_error)) {
  case ffi.testing_clear_observations() {
    Ok(_) -> Ok(Nil)
    Error(raw_error) ->
      Error(ReplayHarnessFailure(error.EngineFailure(raw_error)))
  }
}

fn capture() -> Result(String, ReplayError(workflow_error)) {
  case ffi.testing_observations() {
    Ok(observations) -> Ok(observations)
    Error(raw_error) ->
      Error(ReplayHarnessFailure(error.EngineFailure(raw_error)))
  }
}