Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
heartwood crates radicle-cli tests commands cob.rs
use std::path::Path;

use crate::util::environment::Environment;
use crate::{program_reports_version, test};
use radicle::cob::store::access::{ReadOnly, WriteAs};
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, WriteAs::new(&bob.signer)).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,
        )
        .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, ReadOnly).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, &alice.signer).unwrap();
    let issue = alice_issues
        .create(
            radicle::cob::Title::new("Something's fishy").unwrap(),
            "I don't know what it is",
            &[],
            &[],
            [],
        )
        .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, ReadOnly).unwrap();
    assert!(bob_issues.get(issue_id).unwrap().is_some());

    let mut alice_issues =
        radicle::cob::issue::Cache::no_cache(&alice_repo, &alice.signer).unwrap();
    alice_issues.remove(issue_id).unwrap();

    log::debug!(target: "test", "Removing issue..");

    radicle::assert_matches!(
        bob.handle
            .fetch(rid, alice.id, radicle::node::DEFAULT_TIMEOUT, None)
            .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, ReadOnly).unwrap();
    assert!(bob_issues.get(issue_id).unwrap().is_none());
}