use turnframe_core::error::ExecutionError;
use turnframe_core::flow::WorkflowExecutor;
use super::fixtures::{
batch, ensure, ensure_commit, ensure_eq, ensure_refused, ensure_unchanged, seed, snapshot,
};
use super::{ConformanceFailure, ExecutorFactory};
pub async fn check_stale_expected_revision_is_a_conflict<F: ExecutorFactory>(
factory: &F,
) -> Result<(), ConformanceFailure> {
const CHECK: &str = "check_stale_expected_revision_is_a_conflict";
let seeded = seed(CHECK, factory).await?;
let seeded_at = seeded.case_ref.expected_revision;
let first = batch::<F>(
CHECK,
&seeded,
"revision.first",
&seeded.case_ref,
std::slice::from_ref(&seeded.first),
)?;
ensure_commit(
CHECK,
"the first batch, planned against the current revision",
seeded.executor.execute(first).await,
)?;
let committed = snapshot(CHECK, "after the first batch", &seeded).await?;
ensure(
CHECK,
committed.1 != seeded_at,
format!("a committed batch must move the case off revision {seeded_at}"),
)?;
let stale = batch::<F>(
CHECK,
&seeded,
"revision.stale",
&seeded.case_ref,
std::slice::from_ref(&seeded.second),
)?;
let refused = ensure_refused(
CHECK,
"a batch planned against the superseded revision",
seeded.executor.execute(stale).await,
)?;
match refused {
ExecutionError::RevisionConflict(conflict) => {
ensure_eq(
CHECK,
"the revision the conflict reports as current",
&conflict.current_revision,
&committed.1,
)?;
ensure_eq(
CHECK,
"the revision the conflict says was expected",
&conflict.expected.expected_revision,
&seeded_at,
)?;
}
other => {
return Err(ConformanceFailure::new(
CHECK,
format!("a stale revision must be a RevisionConflict, got {other:?}"),
));
}
}
ensure_unchanged(
CHECK,
"after the stale batch was refused",
&seeded,
&committed,
)
.await?;
let ahead_ref = seeded.case_ref.with_revision(committed.1.next());
let ahead = batch::<F>(
CHECK,
&seeded,
"revision.ahead",
&ahead_ref,
std::slice::from_ref(&seeded.second),
)?;
let refused = ensure_refused(
CHECK,
"a batch planned against a revision the case never reached",
seeded.executor.execute(ahead).await,
)?;
ensure(
CHECK,
matches!(refused, ExecutionError::RevisionConflict(_)),
format!("a revision ahead of the current one must be a RevisionConflict, got {refused:?}"),
)?;
ensure_unchanged(
CHECK,
"after the ahead batch was refused",
&seeded,
&committed,
)
.await
}
pub async fn check_commit_reports_the_revision_it_reached<F: ExecutorFactory>(
factory: &F,
) -> Result<(), ConformanceFailure> {
const CHECK: &str = "check_commit_reports_the_revision_it_reached";
let seeded = seed(CHECK, factory).await?;
let before = snapshot(CHECK, "before anything ran", &seeded).await?;
ensure_eq(
CHECK,
"the revision the factory says it seeded",
&before.1,
&seeded.case_ref.expected_revision,
)?;
let first = batch::<F>(
CHECK,
&seeded,
"reported.first",
&seeded.case_ref,
std::slice::from_ref(&seeded.first),
)?;
let commit = ensure_commit(
CHECK,
"the first batch",
seeded.executor.execute(first).await,
)?;
ensure(
CHECK,
commit.new_revision != before.1,
format!(
"a batch that committed must report a revision other than {}, reported {}",
before.1, commit.new_revision
),
)?;
let loaded = snapshot(CHECK, "after the first batch", &seeded).await?;
ensure_eq(
CHECK,
"the revision `load` reports against the one the commit reported",
&loaded.1,
&commit.new_revision,
)?;
let next_ref = seeded.case_ref.with_revision(commit.new_revision);
let second = batch::<F>(
CHECK,
&seeded,
"reported.second",
&next_ref,
std::slice::from_ref(&seeded.second),
)?;
let second_commit = ensure_commit(
CHECK,
"a batch planned against the revision the first commit reported",
seeded.executor.execute(second).await,
)?;
let after = snapshot(CHECK, "after the second batch", &seeded).await?;
ensure_eq(
CHECK,
"the revision `load` reports after the second batch",
&after.1,
&second_commit.new_revision,
)
}