fn two_capability_batch() -> Vec<Proposal> {
vec![
capability_proposal("actor-a", "tool-a", CapabilityMode::Auto),
capability_proposal("actor-b", "tool-b", CapabilityMode::Auto),
]
}
#[test]
fn test_checked_batch_commit_retry_is_noop() {
let dir = tempfile::tempdir().unwrap();
let rules = test_rules();
let mut writer = LogWriter::open(dir.path(), &rules).unwrap();
let mut state = State::default();
assert_eq!(writer.head(), EMPTY_HEAD);
let first = writer
.checked_batch_commit(EMPTY_HEAD, two_capability_batch(), &rules, &mut state)
.unwrap();
assert_eq!(first.replayed, 0);
assert_eq!(first.head, writer.head());
let len_after = writer.records().len();
assert_eq!(len_after, 4);
drop(writer);
let mut writer = LogWriter::open(dir.path(), &rules).unwrap();
let mut state = build_state_unchecked(writer.records()).unwrap();
let retry = writer
.checked_batch_commit(EMPTY_HEAD, two_capability_batch(), &rules, &mut state)
.unwrap();
assert_eq!(retry.head, first.head);
assert_eq!(retry.results, first.results);
assert_eq!(retry.replayed, 2);
assert_eq!(writer.records().len(), len_after);
let report = verify_log(writer.records(), &rules, None);
assert_eq!(report.result, VerdictResult::Accept);
}
#[test]
fn test_checked_batch_commit_retry_after_later_appends() {
let dir = tempfile::tempdir().unwrap();
let rules = test_rules();
let mut writer = LogWriter::open(dir.path(), &rules).unwrap();
let mut state = State::default();
let first = writer
.checked_batch_commit(EMPTY_HEAD, two_capability_batch(), &rules, &mut state)
.unwrap();
writer
.commit(request_proposal(), &rules, &mut state)
.unwrap();
let retry = writer
.checked_batch_commit(EMPTY_HEAD, two_capability_batch(), &rules, &mut state)
.unwrap();
assert_eq!(retry.head, first.head);
assert_ne!(retry.head, writer.head());
assert_eq!(retry.replayed, 2);
}
#[test]
fn test_checked_batch_commit_resumes_partial_batch() {
let dir = tempfile::tempdir().unwrap();
let rules = test_rules();
let mut writer = LogWriter::open(dir.path(), &rules).unwrap();
let mut state = State::default();
let batch = two_capability_batch();
let mut ordered = batch.clone();
ordered.sort_unstable_by(|a, b| {
sha256_canonical(a)
.unwrap()
.cmp(&sha256_canonical(b).unwrap())
});
writer
.commit(ordered[0].clone(), &rules, &mut state)
.unwrap();
assert_eq!(writer.records().len(), 2);
let outcome = writer
.checked_batch_commit(EMPTY_HEAD, batch, &rules, &mut state)
.unwrap();
assert_eq!(outcome.replayed, 1);
assert_eq!(outcome.results.len(), 2);
assert_eq!(writer.records().len(), 4);
assert_eq!(outcome.head, writer.head());
let report = verify_log(writer.records(), &rules, None);
assert_eq!(report.result, VerdictResult::Accept);
}
#[test]
fn test_checked_batch_commit_head_conflict() {
let dir = tempfile::tempdir().unwrap();
let rules = test_rules();
let mut writer = LogWriter::open(dir.path(), &rules).unwrap();
let mut state = State::default();
writer
.commit(request_proposal(), &rules, &mut state)
.unwrap();
let len_before = writer.records().len();
let result =
writer.checked_batch_commit(EMPTY_HEAD, two_capability_batch(), &rules, &mut state);
assert!(matches!(result, Err(LogError::HeadConflict { .. })));
assert_eq!(writer.records().len(), len_before);
let result = writer.checked_batch_commit([9u8; 32], two_capability_batch(), &rules, &mut state);
assert!(matches!(result, Err(LogError::HeadConflict { .. })));
}