use super::*;
#[tokio::test]
async fn parked_dispatch_stands_down_without_recording_even_with_retry_budget() -> TestResult {
let harness = RetryLoopHarness::seeded(FIXED_RETRY_CONFIG).await?;
let dispatcher =
ScriptedRetryDispatcher::new(vec![Err(crate::runtime::PARKED_ACTIVITY_REASON.to_owned())]);
let outcome = super::dispatch_with_retries(
&(Arc::clone(&dispatcher) as Arc<dyn ActivityDispatcher>),
&harness.seam,
&harness.request,
)
.await;
assert!(
matches!(outcome.terminal, super::RetryLoopTerminal::Parked),
"a parked dispatch must stand down, never fail or retry"
);
assert_eq!(
dispatcher.seen_attempts(),
vec![1],
"parking must not re-dispatch: no retry budget is consumed"
);
assert_eq!(
harness.history().await?.len(),
3,
"nothing may be recorded for a parked dispatch — the durable log \
must end at the dangling scheduled/started trail"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn parked_dispatch_delivers_nothing_to_the_workflow_process() -> TestResult {
let harness = RetryLoopHarness::seeded(r#"{"retry":null}"#).await?;
let runtime = Arc::new(RuntimeHandle::new(RuntimeConfig::new(Some(1)))?);
let pid = runtime.spawn_test_process()?;
let dispatcher =
ScriptedRetryDispatcher::new(vec![Err(crate::runtime::PARKED_ACTIVITY_REASON.to_owned())]);
spawn_completion_task(
&tokio::runtime::Handle::current(),
Arc::clone(&runtime),
Arc::clone(&dispatcher) as Arc<dyn ActivityDispatcher>,
super::RetryRecorderSeam {
recorder: Arc::clone(&harness.seam.recorder),
run_id: harness.seam.run_id.clone(),
},
pid,
super::correlation_id(0),
harness.request.clone(),
);
for _ in 0_u32..40 {
if dispatcher.seen_attempts().len() == 1 {
break;
}
tokio::time::sleep(std::time::Duration::from_millis(5)).await;
}
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
assert!(
runtime.take_activity_result(pid, 0)?.is_none(),
"a parked dispatch must never deliver a completion"
);
assert!(
runtime.take_activity_error(pid, 0)?.is_none(),
"the parked sentinel must never be delivered to workflow code"
);
assert_eq!(
harness.history().await?.len(),
3,
"the durable log must be untouched by a parked dispatch"
);
runtime.shutdown()?;
Ok(())
}
#[tokio::test]
async fn retry_loop_aborts_without_recording_once_the_activity_settled() -> TestResult {
let harness = RetryLoopHarness::seeded(FIXED_RETRY_CONFIG).await?;
let timeout_terminal = vec![Event::ActivityFailed {
envelope: envelope(&harness.workflow_id, 4),
activity_id: ActivityId::from_sequence_position(0),
error: aion_core::ActivityError {
kind: aion_core::ActivityErrorKind::Terminal,
message: "timeout:deadline expired".to_owned(),
details: None,
},
attempt: 1,
}];
harness
.store
.append(
WriteToken::recorder(),
&harness.workflow_id,
&timeout_terminal,
3,
)
.await?;
let dispatcher = ScriptedRetryDispatcher::new(vec![Err("retryable:stream reset".to_owned())]);
let outcome = super::dispatch_with_retries(
&(Arc::clone(&dispatcher) as Arc<dyn ActivityDispatcher>),
&harness.seam,
&harness.request,
)
.await;
assert!(
matches!(outcome.terminal, super::RetryLoopTerminal::SettledElsewhere),
"the loop must observe the recorded terminal and stand down"
);
assert_eq!(
harness.history().await?.len(),
4,
"nothing may be recorded after the ordinal's terminal"
);
Ok(())
}