radicle/cob/identity: Add concurrent terminal state test

The new identity evaluation logic was updated to ignore `Accept` or `Reject`
actions if the revision has already reached a terminal state (`Accepted` or
`Rejected`), however this handling was not explicitly covered by the test suite.

This test ensures that the state machine correctly short-circuits the
late-arriving vote, proving that the new logic succesfully ensures
terminal states remain immutable under concurrent network conditions.
This commit is contained in:
Adrian Duke 2026-05-22 14:15:03 +01:00 committed by Fintan Halpenny
parent ba4a79af90
commit 489360d0e5
1 changed files with 76 additions and 0 deletions

View File

@ -1831,6 +1831,82 @@ mod test {
);
}
#[test]
fn terminal_states_concurrent() {
let network = Network::default();
let alice = &network.alice;
let bob = &network.bob;
let eve = &network.eve;
let mut alice_identity = Identity::load_mut(&*alice.repo, &alice.signer).unwrap();
let mut alice_doc = alice_identity.doc().clone().edit();
alice_doc.delegate(bob.signer.public_key().into());
alice_doc.delegate(eve.signer.public_key().into());
let a1 = alice_identity
.update(
cob::Title::new("Add Bob and Eve").unwrap(),
"",
&alice_doc.verified().unwrap(),
)
.unwrap();
bob.repo.fetch(alice);
eve.repo.fetch(alice);
let mut bob_identity = Identity::load_mut(&*bob.repo, &bob.signer).unwrap();
let mut eve_identity = Identity::load_mut(&*eve.repo, &eve.signer).unwrap();
bob_identity.accept(&a1).unwrap();
eve_identity.accept(&a1).unwrap();
alice.repo.fetch(bob);
alice_identity.reload().unwrap();
assert_eq!(alice_identity.revision(&a1).unwrap().state, State::Accepted);
alice.repo.fetch(eve);
alice_identity.reload().unwrap();
assert_eq!(alice_identity.revision(&a1).unwrap().state, State::Accepted);
let mut alice_doc2 = alice_identity.doc().clone().edit();
alice_doc2.visibility = Visibility::private([]);
let a2 = alice_identity
.update(
cob::Title::new("A2").unwrap(),
"",
&alice_doc2.verified().unwrap(),
)
.unwrap();
bob.repo.fetch(alice);
eve.repo.fetch(alice);
bob_identity.reload().unwrap();
eve_identity.reload().unwrap();
bob_identity.reject(a2).unwrap();
eve_identity.reject(a2).unwrap();
alice.repo.fetch(bob);
alice.repo.fetch(eve);
alice_identity.reload().unwrap();
assert_eq!(
alice_identity.revision(&a2).unwrap().state,
State::Rejected(RejectedBy::Vote)
);
// a2 (Propose "A2") 1/3 (Rejected by Bob and Eve)
// |
// a1 (Add Bob and Eve) 3/3 (Accepted by Alice, Bob, Eve)
// |
// a0
// Alice tries to accept the rejected revision
alice_identity.accept(&a2).unwrap();
assert_eq!(
alice_identity.revision(&a2).unwrap().state,
State::Rejected(RejectedBy::Vote)
);
}
#[test]
fn test_valid_identity() {
let tempdir = tempfile::tempdir().unwrap();