use aion_core::{Event, EventEnvelope, Payload, RunId, WorkflowId, WorkflowStatus};
use serde_json::json;
use uuid::Uuid;
use super::StaleActiveListStore;
use crate::{ReadableEventStore, WritableEventStore, WriteToken};
type TestResult = Result<(), Box<dyn std::error::Error>>;
type TestResult2<T> = Result<T, Box<dyn std::error::Error>>;
fn workflow_id(value: u128) -> WorkflowId {
WorkflowId::new(Uuid::from_u128(value))
}
fn envelope(seq: u64, workflow_id: &WorkflowId) -> TestResult2<EventEnvelope> {
Ok(EventEnvelope {
seq,
recorded_at: chrono::DateTime::from_timestamp(1_700_000_000 + i64::try_from(seq)?, 0)
.ok_or("timestamp out of range")?,
workflow_id: workflow_id.clone(),
})
}
fn started(seq: u64, workflow_id: &WorkflowId) -> TestResult2<Event> {
Ok(Event::WorkflowStarted {
envelope: envelope(seq, workflow_id)?,
workflow_type: String::from("probe"),
input: Payload::from_json(&json!({}))?,
run_id: RunId::new(Uuid::from_u128(1)),
parent_run_id: None,
parent_workflow_id: None,
package_version: aion_core::PackageVersion::new("a".repeat(64)),
})
}
fn completed(seq: u64, workflow_id: &WorkflowId) -> TestResult2<Event> {
Ok(Event::WorkflowCompleted {
envelope: envelope(seq, workflow_id)?,
result: Payload::from_json(&json!({}))?,
})
}
fn paused(seq: u64, workflow_id: &WorkflowId) -> TestResult2<Event> {
Ok(Event::WorkflowPaused {
envelope: envelope(seq, workflow_id)?,
run_id: RunId::new(Uuid::from_u128(1)),
reason: None,
operator: None,
})
}
async fn seed(store: &StaleActiveListStore) -> TestResult2<(WorkflowId, WorkflowId)> {
let running = workflow_id(1);
let finished = workflow_id(2);
store
.append(
WriteToken::recorder(),
&running,
&[started(1, &running)?],
0,
)
.await?;
store
.append(
WriteToken::recorder(),
&finished,
&[started(1, &finished)?, completed(2, &finished)?],
0,
)
.await?;
Ok((running, finished))
}
#[tokio::test]
async fn a_forced_terminal_id_is_reported_active_while_its_history_still_says_terminal()
-> TestResult {
let store = StaleActiveListStore::new();
let (running, finished) = seed(&store).await?;
let honest = store.list_active().await?;
assert!(
honest.contains(&running),
"the Running run must be listed before forcing, or the double is not delegating"
);
assert!(
!honest.contains(&finished),
"the completed run must be EXCLUDED before forcing — this is the inner \
filter the instrument exists to defeat, and if it were already absent \
the forcing below would prove nothing"
);
store.force_active(finished.clone());
let forced = store.list_active().await?;
assert!(
forced.contains(&finished),
"forcing must defeat the inner `== Running` filter"
);
assert!(
forced.contains(&running),
"forcing one id must not displace the honest answer"
);
let status = aion_core::status_from_events(&store.read_history(&finished).await?);
assert_eq!(
status,
WorkflowStatus::Completed,
"the forced run's history must still project terminal; a double that also \
rewrote history would hide the very inconsistency being injected"
);
Ok(())
}
#[tokio::test]
async fn a_forced_paused_id_is_reported_active_and_still_projects_paused() -> TestResult {
let store = StaleActiveListStore::new();
let (_running, _finished) = seed(&store).await?;
let held = workflow_id(3);
store
.append(
WriteToken::recorder(),
&held,
&[started(1, &held)?, paused(2, &held)?],
0,
)
.await?;
assert!(
!store.list_active().await?.contains(&held),
"a paused run must be excluded before forcing"
);
assert!(
store.list_paused().await?.contains(&held),
"list_paused must still be honest — the double lies in exactly one place"
);
store.force_active(held.clone());
assert!(
store.list_active().await?.contains(&held),
"forcing must defeat the filter for Paused as well as terminal"
);
let status = aion_core::status_from_events(&store.read_history(&held).await?);
assert_eq!(
status,
WorkflowStatus::Paused,
"the forced run must still project Paused, non-terminal — which is exactly \
why an `is_terminal` check alone does not stop it"
);
Ok(())
}
#[tokio::test]
async fn forcing_nothing_leaves_the_answer_identical_to_the_inner_store() -> TestResult {
let store = StaleActiveListStore::new();
let (running, finished) = seed(&store).await?;
let mine = store.list_active().await?;
assert_eq!(
mine,
vec![running],
"with nothing forced the answer must be exactly the honest one — a test \
that forgets to call force_active must get truth, not a silent lie"
);
assert!(!mine.contains(&finished));
Ok(())
}
#[tokio::test]
async fn forcing_an_already_active_id_does_not_duplicate_it() -> TestResult {
let store = StaleActiveListStore::new();
let (running, _finished) = seed(&store).await?;
store.force_active(running.clone());
let active = store.list_active().await?;
assert_eq!(
active.iter().filter(|id| **id == running).count(),
1,
"the honest answer and the forced set must be unioned, not concatenated"
);
Ok(())
}
#[tokio::test]
async fn multiple_forced_ids_are_appended_in_the_order_they_were_forced() -> TestResult {
let store = StaleActiveListStore::new();
let (running, finished) = seed(&store).await?;
let held = workflow_id(3);
store
.append(
WriteToken::recorder(),
&held,
&[started(1, &held)?, paused(2, &held)?],
0,
)
.await?;
store.force_active(held.clone());
store.force_active(finished.clone());
assert_eq!(
store.list_active().await?,
vec![running, held, finished],
"honest answer first, then each forced id in the order it was forced"
);
Ok(())
}