From d39e485c13c34203896936b1beda98fa726d0a74 Mon Sep 17 00:00:00 2001 From: Adrian Duke Date: Tue, 3 Mar 2026 15:42:45 +0000 Subject: [PATCH] cli/tests: Refactor cob command tests --- crates/radicle-cli/tests/commands.rs | 221 +--------------------- crates/radicle-cli/tests/commands/cob.rs | 230 +++++++++++++++++++++++ 2 files changed, 232 insertions(+), 219 deletions(-) create mode 100644 crates/radicle-cli/tests/commands/cob.rs diff --git a/crates/radicle-cli/tests/commands.rs b/crates/radicle-cli/tests/commands.rs index 12540837..76b1762a 100644 --- a/crates/radicle-cli/tests/commands.rs +++ b/crates/radicle-cli/tests/commands.rs @@ -3,7 +3,6 @@ use std::path::Path; use std::str::FromStr; use std::{fs, net, thread, time}; -use radicle::cob; use radicle::git; use radicle::node; use radicle::node::address::Store as _; @@ -16,7 +15,7 @@ use radicle::node::{Address, Alias, Config, Handle as _, DEFAULT_TIMEOUT}; use radicle::prelude::RepoId; use radicle::profile; use radicle::profile::Home; -use radicle::storage::{ReadStorage, RefUpdate, RemoteRepository}; +use radicle::storage::{ReadStorage, RemoteRepository}; use radicle::test::fixtures; use radicle_localtime::LocalTime; @@ -32,6 +31,7 @@ use util::formula::formula; mod commands { mod checkout; mod clone; + mod cob; } /// Run a CLI test file. @@ -130,101 +130,6 @@ fn rad_issue_list() { Environment::alice(["rad-init", "rad-issue", "rad-issue-list"]); } -#[test] -fn rad_cob_update() { - Environment::alice(["rad-init", "rad-cob-log"]); -} - -#[test] -fn rad_cob_update_identity() { - let mut environment = Environment::new(); - let profile = environment.profile("alice"); - let working = environment.tempdir().join("working"); - let home = &profile.home; - - let base = Path::new(env!("CARGO_MANIFEST_DIR")); - - std::fs::create_dir_all(base).unwrap(); - std::fs::create_dir_all(working.clone()).unwrap(); - - // Setup a test repository. - fixtures::repository(&working); - - test("examples/rad-init.md", &working, Some(home), []).unwrap(); - test( - "examples/rad-cob-update-identity.md", - &working, - Some(home), - [], - ) - .unwrap(); -} - -#[test] -fn rad_cob_multiset() { - // `rad-cob-multiset` is a `jq` script, which requires `jq` to be installed. - // We test whether `jq` is installed, and have this test succeed if it is not. - // Programmatic skipping of tests is not supported as of 2024-08. - if !program_reports_version("jq") { - return; - } - - let mut environment = Environment::new(); - let profile = environment.profile("alice"); - let home = &profile.home; - let working = environment.tempdir().join("working"); - - let base = Path::new(env!("CARGO_MANIFEST_DIR")); - std::fs::create_dir_all(base).unwrap(); - std::fs::create_dir_all(working.clone()).unwrap(); - - // Copy over the script that implements the multiset COB. - std::fs::copy( - base.join("examples").join("rad-cob-multiset"), - working.join("rad-cob-multiset"), - ) - .unwrap(); - - // Setup a test repository. - fixtures::repository(&working); - - test("examples/rad-init.md", &working, Some(home), []).unwrap(); - test("examples/rad-cob-multiset.md", &working, Some(home), []).unwrap(); -} - -#[test] -fn rad_cob_log() { - Environment::alice(["rad-init", "rad-cob-log"]); -} - -#[test] -fn rad_cob_show() { - Environment::alice(["rad-init", "rad-cob-show"]); -} - -#[test] -fn rad_cob_migrate() { - let mut environment = Environment::new(); - let profile = environment.profile("alice"); - let home = &profile.home; - - home.cobs_db_mut() - .unwrap() - .raw_query(|conn| conn.execute("PRAGMA user_version = 0")) - .unwrap(); - - environment.repository(&profile); - - environment - .tests(["rad-init", "rad-cob-migrate"], &profile) - .unwrap(); -} - -#[test] -fn rad_cob_operations() { - Environment::alice(["rad-init", "rad-cob-operations"]); -} - #[test] #[ignore = "part of many other tests"] fn rad_init() { @@ -1463,128 +1368,6 @@ fn rad_diff() { test("examples/rad-diff.md", tmp, None, []).unwrap(); } -#[test] -fn test_cob_replication() { - let mut environment = Environment::new(); - let working = tempfile::tempdir().unwrap(); - let mut alice = environment.node("alice"); - let bob = environment.node("bob"); - - let rid = alice.project("heartwood", ""); - - let mut alice = alice.spawn(); - let mut bob = bob.spawn(); - let events = alice.handle.events(); - - alice.handle.follow(bob.id, None).unwrap(); - alice.connect(&bob); - - bob.routes_to(&[(rid, alice.id)]); - bob.fork(rid, working.path()).unwrap(); - - // Wait for Alice to fetch the clone refs. - events - .wait( - |e| { - matches!( - e, - Event::RefsFetched { updated, .. } - if updated.iter().any(|u| matches!(u, RefUpdate::Created { .. })) - ) - .then_some(()) - }, - time::Duration::from_secs(6), - ) - .unwrap(); - - let bob_repo = bob.storage.repository(rid).unwrap(); - let mut bob_issues = radicle::cob::issue::Issues::open(&bob_repo).unwrap(); - let mut bob_cache = radicle::cob::cache::InMemory::default(); - let issue = bob_issues - .create( - cob::Title::new("Something's fishy").unwrap(), - "I don't know what it is", - &[], - &[], - [], - &mut bob_cache, - &bob.signer, - ) - .unwrap(); - log::debug!(target: "test", "Issue {} created", issue.id()); - - // Make sure that Bob's issue refs announcement has a different timestamp than his fork's - // announcement, otherwise Alice will consider it stale. - thread::sleep(time::Duration::from_millis(3)); - - bob.handle.announce_refs_for(rid, [bob.id]).unwrap(); - - // Wait for Alice to fetch the issue refs. - events - .iter() - .find(|e| matches!(e, Event::RefsFetched { .. })) - .unwrap(); - - let alice_repo = alice.storage.repository(rid).unwrap(); - let alice_issues = radicle::cob::issue::Issues::open(&alice_repo).unwrap(); - let alice_issue = alice_issues.get(issue.id()).unwrap().unwrap(); - - assert_eq!(alice_issue.title(), "Something's fishy"); -} - -#[test] -fn test_cob_deletion() { - let mut environment = Environment::new(); - let working = tempfile::tempdir().unwrap(); - let mut alice = environment.node("alice"); - let bob = environment.node("bob"); - - let rid = alice.project("heartwood", ""); - - let mut alice = alice.spawn(); - let mut bob = bob.spawn(); - - alice.handle.seed(rid, Scope::All).unwrap(); - bob.handle.seed(rid, Scope::All).unwrap(); - alice.connect(&bob); - bob.routes_to(&[(rid, alice.id)]); - - let alice_repo = alice.storage.repository(rid).unwrap(); - let mut alice_issues = radicle::cob::issue::Cache::no_cache(&alice_repo).unwrap(); - let issue = alice_issues - .create( - cob::Title::new("Something's fishy").unwrap(), - "I don't know what it is", - &[], - &[], - [], - &alice.signer, - ) - .unwrap(); - let issue_id = issue.id(); - log::debug!(target: "test", "Issue {issue_id} created"); - - bob.rad("clone", &[rid.to_string().as_str()], working.path()) - .unwrap(); - - let bob_repo = bob.storage.repository(rid).unwrap(); - let bob_issues = radicle::cob::issue::Issues::open(&bob_repo).unwrap(); - assert!(bob_issues.get(issue_id).unwrap().is_some()); - - let mut alice_issues = radicle::cob::issue::Cache::no_cache(&alice_repo).unwrap(); - alice_issues.remove(issue_id, &alice.signer).unwrap(); - - log::debug!(target: "test", "Removing issue.."); - - radicle::assert_matches!( - bob.handle.fetch(rid, alice.id, DEFAULT_TIMEOUT).unwrap(), - radicle::node::FetchResult::Success { .. } - ); - let bob_repo = bob.storage.repository(rid).unwrap(); - let bob_issues = radicle::cob::issue::Issues::open(&bob_repo).unwrap(); - assert!(bob_issues.get(issue_id).unwrap().is_none()); -} - #[test] fn rad_sync() { let mut environment = Environment::new(); diff --git a/crates/radicle-cli/tests/commands/cob.rs b/crates/radicle-cli/tests/commands/cob.rs new file mode 100644 index 00000000..b4fe0cba --- /dev/null +++ b/crates/radicle-cli/tests/commands/cob.rs @@ -0,0 +1,230 @@ +use std::path::Path; + +use crate::util::environment::Environment; +use crate::{program_reports_version, test}; +use radicle::node::Handle; +use radicle::test::fixtures; + +#[test] +fn rad_cob_update() { + Environment::alice(["rad-init", "rad-cob-log"]); +} + +#[test] +fn rad_cob_update_identity() { + let mut environment = Environment::new(); + let profile = environment.profile("alice"); + let working = environment.tempdir().join("working"); + let home = &profile.home; + + let base = Path::new(env!("CARGO_MANIFEST_DIR")); + + std::fs::create_dir_all(base).unwrap(); + std::fs::create_dir_all(working.clone()).unwrap(); + + // Setup a test repository. + fixtures::repository(&working); + + test("examples/rad-init.md", &working, Some(home), []).unwrap(); + test( + "examples/rad-cob-update-identity.md", + &working, + Some(home), + [], + ) + .unwrap(); +} + +#[test] +fn rad_cob_multiset() { + // `rad-cob-multiset` is a `jq` script, which requires `jq` to be installed. + // We test whether `jq` is installed, and have this test succeed if it is not. + // Programmatic skipping of tests is not supported as of 2024-08. + if !program_reports_version("jq") { + return; + } + + let mut environment = Environment::new(); + let profile = environment.profile("alice"); + let home = &profile.home; + let working = environment.tempdir().join("working"); + + let base = Path::new(env!("CARGO_MANIFEST_DIR")); + std::fs::create_dir_all(base).unwrap(); + std::fs::create_dir_all(working.clone()).unwrap(); + + // Copy over the script that implements the multiset COB. + std::fs::copy( + base.join("examples").join("rad-cob-multiset"), + working.join("rad-cob-multiset"), + ) + .unwrap(); + + // Setup a test repository. + fixtures::repository(&working); + + test("examples/rad-init.md", &working, Some(home), []).unwrap(); + test("examples/rad-cob-multiset.md", &working, Some(home), []).unwrap(); +} + +#[test] +fn rad_cob_log() { + Environment::alice(["rad-init", "rad-cob-log"]); +} + +#[test] +fn rad_cob_show() { + Environment::alice(["rad-init", "rad-cob-show"]); +} + +#[test] +fn rad_cob_migrate() { + let mut environment = Environment::new(); + let profile = environment.profile("alice"); + let home = &profile.home; + + home.cobs_db_mut() + .unwrap() + .raw_query(|conn| conn.execute("PRAGMA user_version = 0")) + .unwrap(); + + environment.repository(&profile); + + environment + .tests(["rad-init", "rad-cob-migrate"], &profile) + .unwrap(); +} + +#[test] +fn rad_cob_operations() { + Environment::alice(["rad-init", "rad-cob-operations"]); +} + +#[test] +fn test_cob_replication() { + let mut environment = Environment::new(); + let working = tempfile::tempdir().unwrap(); + let mut alice = environment.node("alice"); + let bob = environment.node("bob"); + + let rid = alice.project("heartwood", ""); + + let mut alice = alice.spawn(); + let mut bob = bob.spawn(); + let events = alice.handle.events(); + + alice.handle.follow(bob.id, None).unwrap(); + alice.connect(&bob); + + bob.routes_to(&[(rid, alice.id)]); + bob.fork(rid, working.path()).unwrap(); + + // Wait for Alice to fetch the clone refs. + events + .wait( + |e| { + matches!( + e, + radicle::node::Event::RefsFetched { updated, .. } + if updated.iter().any(|u| matches!(u, radicle::storage::RefUpdate::Created { .. })) + ) + .then_some(()) + }, + std::time::Duration::from_secs(6), + ) + .unwrap(); + + let bob_repo = radicle::storage::ReadStorage::repository(&bob.storage, rid).unwrap(); + let mut bob_issues = radicle::cob::issue::Issues::open(&bob_repo).unwrap(); + let mut bob_cache = radicle::cob::cache::InMemory::default(); + let issue = bob_issues + .create( + radicle::cob::Title::new("Something's fishy").unwrap(), + "I don't know what it is", + &[], + &[], + [], + &mut bob_cache, + &bob.signer, + ) + .unwrap(); + log::debug!(target: "test", "Issue {} created", issue.id()); + + // Make sure that Bob's issue refs announcement has a different timestamp than his fork's + // announcement, otherwise Alice will consider it stale. + std::thread::sleep(std::time::Duration::from_millis(3)); + + bob.handle.announce_refs_for(rid, [bob.id]).unwrap(); + + // Wait for Alice to fetch the issue refs. + events + .iter() + .find(|e| matches!(e, radicle::node::Event::RefsFetched { .. })) + .unwrap(); + + let alice_repo = radicle::storage::ReadStorage::repository(&alice.storage, rid).unwrap(); + let alice_issues = radicle::cob::issue::Issues::open(&alice_repo).unwrap(); + let alice_issue = alice_issues.get(issue.id()).unwrap().unwrap(); + + assert_eq!(alice_issue.title(), "Something's fishy"); +} + +#[test] +fn test_cob_deletion() { + let mut environment = Environment::new(); + let working = tempfile::tempdir().unwrap(); + let mut alice = environment.node("alice"); + let bob = environment.node("bob"); + + let rid = alice.project("heartwood", ""); + + let mut alice = alice.spawn(); + let mut bob = bob.spawn(); + + alice + .handle + .seed(rid, radicle::node::policy::Scope::All) + .unwrap(); + bob.handle + .seed(rid, radicle::node::policy::Scope::All) + .unwrap(); + alice.connect(&bob); + bob.routes_to(&[(rid, alice.id)]); + + let alice_repo = radicle::storage::ReadStorage::repository(&alice.storage, rid).unwrap(); + let mut alice_issues = radicle::cob::issue::Cache::no_cache(&alice_repo).unwrap(); + let issue = alice_issues + .create( + radicle::cob::Title::new("Something's fishy").unwrap(), + "I don't know what it is", + &[], + &[], + [], + &alice.signer, + ) + .unwrap(); + let issue_id = issue.id(); + log::debug!(target: "test", "Issue {issue_id} created"); + + bob.rad("clone", &[rid.to_string().as_str()], working.path()) + .unwrap(); + + let bob_repo = radicle::storage::ReadStorage::repository(&bob.storage, rid).unwrap(); + let bob_issues = radicle::cob::issue::Issues::open(&bob_repo).unwrap(); + assert!(bob_issues.get(issue_id).unwrap().is_some()); + + let mut alice_issues = radicle::cob::issue::Cache::no_cache(&alice_repo).unwrap(); + alice_issues.remove(issue_id, &alice.signer).unwrap(); + + log::debug!(target: "test", "Removing issue.."); + + radicle::assert_matches!( + bob.handle + .fetch(rid, alice.id, radicle::node::DEFAULT_TIMEOUT) + .unwrap(), + radicle::node::FetchResult::Success { .. } + ); + let bob_repo = radicle::storage::ReadStorage::repository(&bob.storage, rid).unwrap(); + let bob_issues = radicle::cob::issue::Issues::open(&bob_repo).unwrap(); + assert!(bob_issues.get(issue_id).unwrap().is_none()); +}