From 7d2f0e387c66f3f24b94673a2de9589407e2f946 Mon Sep 17 00:00:00 2001 From: Fintan Halpenny Date: Wed, 20 Aug 2025 09:49:07 +0100 Subject: [PATCH] node: e2e test for canonical ref update Adds a test to ensure that canonical reference updates are received on the node events stream. Added a helper to `NodeHandle` for perfoming a commit to a reference and signing the nodes references, to help with this use case. --- crates/radicle-node/src/test/node.rs | 66 ++++++++++++++++++++++++++++ crates/radicle-node/src/tests/e2e.rs | 40 +++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/crates/radicle-node/src/test/node.rs b/crates/radicle-node/src/test/node.rs index 25b6c5e4..95e6f916 100644 --- a/crates/radicle-node/src/test/node.rs +++ b/crates/radicle-node/src/test/node.rs @@ -352,6 +352,72 @@ impl + cyphernet::Ecdh> NodeHandle { .unwrap() .id() } + + /// Perform a commit to `refname` by generating a blob of random data to a + /// random path in a new tree. + /// + /// If the reference does not exist, a new one will be created with the new + /// commit as its target. + /// + /// If the reference already exists, then its target is used as the parent + /// of the new commit, and the reference will be updated. + /// + /// The `rad/sigrefs` are then updated to reflect the new change. + pub fn commit_to(&self, rid: RepoId, refname: impl AsRef) { + use radicle::test::arbitrary; + + let refname = refname.as_ref(); + let repo = self.storage.repository(rid).unwrap(); + let raw = &repo.backend; + + let info = self.storage.info(); + let author = git::raw::Signature::now(&info.name(), &info.email()).unwrap(); + + let tree = { + let mut tb = raw.treebuilder(None).unwrap(); + let blob = raw.blob(&arbitrary::vec::(100)).unwrap(); + tb.insert( + arbitrary::alphanumeric(10), + blob, + git::raw::FileMode::Blob.into(), + ) + .unwrap(); + let oid = tb.write().unwrap(); + raw.find_tree(oid).unwrap() + }; + let parent = { + let target = raw + .find_reference(refname.as_str()) + .ok() + .and_then(|r| r.target()); + target.and_then(|oid| raw.find_commit(oid).ok()) + }; + match parent { + None => repo + .backend + .commit( + Some(refname.as_str()), + &author, + &author, + "New commit", + &tree, + &[], + ) + .unwrap(), + Some(parent) => repo + .backend + .commit( + Some(refname.as_str()), + &author, + &author, + "New commit", + &tree, + &[&parent], + ) + .unwrap(), + }; + repo.sign_refs(&self.signer).unwrap(); + } } impl Node { diff --git a/crates/radicle-node/src/tests/e2e.rs b/crates/radicle-node/src/tests/e2e.rs index c82e656d..1777e85e 100644 --- a/crates/radicle-node/src/tests/e2e.rs +++ b/crates/radicle-node/src/tests/e2e.rs @@ -1504,3 +1504,43 @@ fn test_channel_reader_limit() { "actual: {reason}" ); } + +#[test] +fn test_fetch_emits_canonical_ref_update() { + let tmp = tempfile::tempdir().unwrap(); + let scale = config::scale(); + let mut alice = Node::init(tmp.path(), config::relay("alice")); + let bob = Node::init(tmp.path(), config::relay("bob")); + + let (repo, _) = fixtures::repository(tmp.path()); + fixtures::populate(&repo, scale.max(3)); + + let rid = alice.project_from("acme", "", &repo); + + let mut alice = alice.spawn(); + let mut bob = bob.spawn(); + let bob_events = bob.handle.events(); + + bob.handle.seed(rid, Scope::All).unwrap(); + alice.connect(&bob); + + let result = bob.handle.fetch(rid, alice.id, DEFAULT_TIMEOUT).unwrap(); + assert!(result.is_success()); + + let default_branch: git::Qualified = { + let repo = alice.storage.repository(rid).unwrap(); + let proj = repo.project().unwrap(); + git::lit::refs_heads(proj.default_branch()).into() + }; + alice.commit_to(rid, &default_branch); + + bob_events + .wait( + |e| { + matches!(e, Event::CanonicalRefUpdated { refname, .. } if *refname == default_branch) + .then_some(()) + }, + time::Duration::from_secs(9 * scale as u64), + ) + .unwrap(); +}