Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
heartwood crates radicle src storage refs sigrefs write test signed_refs_writer.rs
use radicle_oid::Oid;

use crate::storage::refs::sigrefs::git::Committer;
use crate::storage::refs::sigrefs::write::{SignedRefsWriter, Update, error};
use crate::storage::refs::{FeatureLevel, IDENTITY_ROOT, Refs, SIGREFS_BRANCH};

use super::mock;
use super::mock::MockRepository;

fn some_refs(identity_root: Oid) -> Refs {
    Refs::from(
        [
            (mock::refs_heads_main(), mock::oid(10)),
            (IDENTITY_ROOT.to_ref_string(), identity_root),
        ]
        .into_iter(),
    )
}

fn other_refs() -> Refs {
    Refs::from(
        [
            (mock::refs_heads_main(), mock::oid(20)),
            (IDENTITY_ROOT.to_ref_string(), mock::oid(99)),
        ]
        .into_iter(),
    )
}

fn refs_with_rad_sigrefs() -> Refs {
    Refs::from(
        [
            (mock::refs_heads_main(), mock::oid(10)),
            (IDENTITY_ROOT.to_ref_string(), mock::oid(99)),
            (SIGREFS_BRANCH.to_ref_string(), mock::oid(20)),
        ]
        .into_iter(),
    )
}

fn write(refs: Refs, repo: &MockRepository) -> Result<Update, error::Write> {
    SignedRefsWriter::new(refs, mock::rid(), mock::node_id(), repo, &mock::AlwaysSign).write(
        Committer::new(mock::author()),
        "msg".into(),
        "reflog".into(),
    )
}

fn force_write(refs: Refs, repo: &MockRepository) -> Result<Update, error::Write> {
    SignedRefsWriter::new(refs, mock::rid(), mock::node_id(), repo, &mock::AlwaysSign).force_write(
        Committer::new(mock::author()),
        "msg".into(),
        "reflog".into(),
    )
}

#[test]
fn head_error() {
    let repo = MockRepository::new().with_rad_sigrefs_error(&mock::node_id());
    assert!(matches!(
        write(some_refs(mock::oid(99)), &repo),
        Err(error::Write::Head(_))
    ));
}

#[test]
fn unchanged() {
    let head = mock::oid(1);
    let refs = some_refs(mock::oid(99));
    let repo = MockRepository::new()
        .with_rad_sigrefs(&mock::node_id(), head)
        .with_commit(head, mock::commit_data([]))
        .with_refs(head, refs.clone())
        .with_signature(head, 1);

    match write(refs.clone(), &repo).unwrap() {
        Update::Unchanged { verified } => {
            assert_eq!(verified.commit().oid(), &head);
            assert_eq!(verified.level(), FeatureLevel::Root);
        }
        Update::Changed { .. } => unreachable!(),
    }
}

#[test]
fn unchanged_force_writes_new_commit() {
    let head = mock::oid(1);
    let commit_oid = mock::oid(42);
    let refs = some_refs(mock::oid(99));
    let repo = MockRepository::new()
        .with_rad_sigrefs(&mock::node_id(), head)
        .with_commit(head, mock::commit_data([]))
        .with_refs(head, refs.clone())
        .with_signature(head, 1)
        .with_write_tree_ok(mock::oid(99))
        .with_write_commit_ok(commit_oid)
        .with_write_reference_ok();
    let update = force_write(refs.clone(), &repo).unwrap();
    let Update::Changed { entry, .. } = update else {
        panic!("expected Update::Changed, got {update:?}");
    };
    assert_eq!(entry.parent, Some(head));
    assert_eq!(entry.oid, commit_oid);
    assert_eq!(entry.into_refs(), refs);
}

#[test]
fn commit_error() {
    let repo = MockRepository::new()
        .with_missing_rad_sigrefs(&mock::node_id())
        .with_write_tree_ok(mock::oid(99));
    assert!(matches!(
        write(some_refs(mock::oid(99)), &repo),
        Err(error::Write::Commit(_))
    ));
}

#[test]
fn reference_error() {
    let commit_oid = mock::oid(42);
    let repo = MockRepository::new()
        .with_missing_rad_sigrefs(&mock::node_id())
        .with_write_tree_ok(mock::oid(99))
        .with_write_commit_ok(commit_oid)
        .with_write_reference_error();
    assert!(matches!(
        write(some_refs(mock::oid(99)), &repo),
        Err(error::Write::Reference(_))
    ));
}

#[test]
fn write_root_ok() {
    let commit_oid = mock::oid(42);
    let repo = MockRepository::new()
        .with_missing_rad_sigrefs(&mock::node_id())
        .with_write_tree_ok(mock::oid(99))
        .with_write_commit_ok(commit_oid)
        .with_write_reference_ok();
    let refs = some_refs(mock::oid(99));
    let update = write(refs.clone(), &repo).unwrap();
    let Update::Changed { entry, .. } = update else {
        panic!("expected Update::Changed, got {update:?}");
    };
    assert_eq!(entry.parent, None);
    assert_eq!(entry.oid, commit_oid);
    assert_eq!(entry.into_refs(), refs);
}

#[test]
fn write_with_parent_ok() {
    let head = mock::oid(1);
    let commit_oid = mock::oid(42);
    let repo = MockRepository::new()
        .with_rad_sigrefs(&mock::node_id(), head)
        .with_commit(head, mock::commit_data([]))
        .with_refs(head, other_refs())
        .with_signature(head, 1)
        .with_write_tree_ok(mock::oid(99))
        .with_write_commit_ok(commit_oid)
        .with_write_reference_ok();
    let refs = some_refs(mock::oid(99));
    let update = write(refs.clone(), &repo).unwrap();
    let Update::Changed { entry, .. } = update else {
        panic!("expected Update::Changed, got {update:?}");
    };
    assert_eq!(entry.parent, Some(head));
    assert_eq!(entry.oid, commit_oid);
    assert_eq!(entry.into_refs(), refs);
}

// TODO: We should error on empty `Refs` writes
#[test]
fn write_empty_refs() {
    let refs = Refs::from([(IDENTITY_ROOT.to_ref_string(), mock::oid(99))].into_iter());
    let commit_oid = mock::oid(42);
    let repo = MockRepository::new()
        .with_missing_rad_sigrefs(&mock::node_id())
        .with_write_tree_ok(mock::oid(99))
        .with_write_commit_ok(commit_oid)
        .with_write_reference_ok();
    let update = write(refs.clone(), &repo).unwrap();
    let Update::Changed { entry, .. } = update else {
        panic!("expected Update::Changed, got {update:?}");
    };
    assert_eq!(entry.parent, None);
    assert_eq!(entry.oid, commit_oid);
    assert_eq!(entry.into_refs(), refs);
}

#[test]
fn never_write_rad_sigrefs() {
    let commit_oid = mock::oid(42);
    let repo = MockRepository::new()
        .with_missing_rad_sigrefs(&mock::node_id())
        .with_write_tree_ok(mock::oid(99))
        .with_write_commit_ok(commit_oid)
        .with_write_reference_ok();
    let mut refs = refs_with_rad_sigrefs();
    let update = write(refs.clone(), &repo).unwrap();
    let Update::Changed { entry, .. } = update else {
        panic!("expected Update::Changed, got {update:?}");
    };
    assert_eq!(entry.parent, None);
    assert_eq!(entry.oid, commit_oid);

    refs.remove_sigrefs();
    assert_eq!(entry.into_refs(), refs);
}