| |
use radicle_oid::Oid;
|
| |
use tempfile::TempDir;
|
| |
|
| - |
use crate::storage::refs::sigrefs::{
|
| - |
read::{CheckpointReason, Latest, SignedRefsReader, Tip},
|
| - |
write::{Committer, SignedRefsWriter, Update},
|
| - |
};
|
| - |
use crate::storage::refs::Refs;
|
| + |
use crate::git;
|
| + |
use crate::storage::refs::sigrefs::VerifiedCommit;
|
| + |
use crate::storage::refs::sigrefs::read::{SignedRefsReader, Tip};
|
| + |
use crate::storage::refs::sigrefs::write::{SignedRefsWriter, Update};
|
| + |
use crate::storage::refs::{IDENTITY_ROOT, Refs};
|
| + |
|
| + |
use super::Committer;
|
| |
|
| + |
/// Newtype wrapper around [`Vec`] to keep the [`Arbitrary`] implementation
|
| + |
/// bounded to a smaller size.
|
| |
#[derive(Clone, Debug)]
|
| |
struct BoundedVec<T>(Vec<T>);
|
| |
|
| |
Committer::new(mock_author())
|
| |
}
|
| |
|
| - |
fn setup() -> (TempDir, git2::Repository) {
|
| - |
let dir = TempDir::new().unwrap();
|
| - |
let repo = git2::Repository::init_bare(dir.path()).unwrap();
|
| - |
(dir, repo)
|
| + |
/// A helper structure that sets up a minimal Radicle repository.
|
| + |
struct Repository {
|
| + |
raw: git::raw::Repository,
|
| + |
rid: RepoId,
|
| + |
root: Oid,
|
| + |
_tmp: TempDir,
|
| |
}
|
| |
|
| - |
fn mock_root() -> RepoId {
|
| - |
RepoId::from(Oid::from_sha1([1; 20]))
|
| + |
impl Repository {
|
| + |
fn new() -> Repository {
|
| + |
let dir = TempDir::new().unwrap();
|
| + |
let repo = git::raw::Repository::init_bare(dir.path()).unwrap();
|
| + |
let (root, rid) = Self::write_identity(&repo);
|
| + |
Repository {
|
| + |
raw: repo,
|
| + |
rid,
|
| + |
root,
|
| + |
_tmp: dir,
|
| + |
}
|
| + |
}
|
| + |
|
| + |
/// Writes a mock blob that represents the identity document, and creates a
|
| + |
/// commit in the repository that contains this blob.
|
| + |
fn write_identity(repo: &git::raw::Repository) -> (Oid, RepoId) {
|
| + |
let blob = repo.blob(b"identity root").unwrap();
|
| + |
let empty = {
|
| + |
let empty = repo.treebuilder(None).unwrap();
|
| + |
let tree = empty.write().unwrap();
|
| + |
repo.find_tree(tree).unwrap()
|
| + |
};
|
| + |
let tree = {
|
| + |
let mut tb = git::raw::build::TreeUpdateBuilder::new();
|
| + |
tb.upsert(
|
| + |
Path::new("embeds").join(*crate::identity::doc::PATH),
|
| + |
blob,
|
| + |
git::raw::FileMode::Blob,
|
| + |
);
|
| + |
let tree = tb.create_updated(repo, &empty).unwrap();
|
| + |
repo.find_tree(tree).unwrap()
|
| + |
};
|
| + |
let author = git::raw::Signature::now("testy", "testy@example.com").unwrap();
|
| + |
let root = repo
|
| + |
.commit(
|
| + |
Some(IDENTITY_ROOT.as_str()),
|
| + |
&author,
|
| + |
&author,
|
| + |
"identity root",
|
| + |
&tree,
|
| + |
&[],
|
| + |
)
|
| + |
.unwrap();
|
| + |
(root.into(), RepoId::from(blob))
|
| + |
}
|
| |
}
|
| |
|
| |
fn write_log(
|
| |
refs: Refs,
|
| + |
rid: RepoId,
|
| |
namespace: NodeId,
|
| |
signer: &MockSigner,
|
| - |
repo: &git2::Repository,
|
| + |
repo: &git::raw::Repository,
|
| |
) -> Update {
|
| - |
SignedRefsWriter::new(namespace, signer, repo)
|
| - |
.with_refs(refs)
|
| + |
SignedRefsWriter::new(refs, rid, namespace, repo, signer)
|
| |
.write(
|
| |
mock_committer(),
|
| |
"test commit".to_string(),
|
| |
.unwrap()
|
| |
}
|
| |
|
| - |
fn read_log(namespace: NodeId, verifier: &Verifier, repo: &git2::Repository) -> Latest {
|
| - |
SignedRefsReader::new(mock_root(), Tip::Reference(namespace), repo, verifier)
|
| + |
fn read_log(
|
| + |
rid: RepoId,
|
| + |
namespace: NodeId,
|
| + |
verifier: &Verifier,
|
| + |
repo: &git::raw::Repository,
|
| + |
) -> VerifiedCommit {
|
| + |
SignedRefsReader::new(rid, Tip::Reference(namespace), repo, verifier)
|
| |
.read()
|
| |
.unwrap()
|
| - |
.unwrap()
|
| |
}
|
| |
|
| |
#[quickcheck]
|
| - |
fn initial_commit_roundtrip(refs: Refs) -> bool {
|
| - |
let (_dir, repo) = setup();
|
| + |
fn initial_commit_roundtrip(mut refs: Refs) -> bool {
|
| + |
let Repository {
|
| + |
raw: repo,
|
| + |
rid,
|
| + |
root,
|
| + |
_tmp,
|
| + |
} = Repository::new();
|
| + |
refs.insert(IDENTITY_ROOT.to_ref_string(), root);
|
| |
let signer = MockSigner::default();
|
| |
let namespace = *signer.public_key();
|
| |
let verifier = Verifier::new(&signer);
|
| |
|
| - |
let update = write_log(refs.clone(), namespace, &signer, &repo);
|
| + |
let update = write_log(refs.clone(), rid, namespace, &signer, &repo);
|
| |
let head_oid = match update {
|
| - |
Update::Changed { ref entry } => *entry.oid(),
|
| + |
Update::Changed { ref entry, .. } => entry.oid(),
|
| |
Update::Unchanged { .. } => return false,
|
| |
};
|
| |
|
| - |
let Latest {
|
| - |
refs: expected,
|
| - |
checkpoint,
|
| - |
..
|
| - |
} = read_log(namespace, &verifier, &repo);
|
| + |
let verified_commit = read_log(rid, namespace, &verifier, &repo);
|
| + |
let head = *verified_commit.commit().oid();
|
| + |
let parent = verified_commit.commit().parent().copied();
|
| + |
let new_refs = verified_commit.into_sigrefs_at(namespace);
|
| |
|
| - |
checkpoint.head() == head_oid
|
| - |
&& checkpoint.ancestor() == head_oid
|
| - |
&& checkpoint.reason() == CheckpointReason::Root
|
| - |
&& expected == refs
|
| + |
head == head_oid && parent.is_none() && *new_refs == refs
|
| |
}
|
| |
|
| |
#[quickcheck]
|
| |
return TestResult::discard();
|
| |
}
|
| |
|
| - |
let (_dir, repo) = setup();
|
| + |
let Repository {
|
| + |
raw: repo,
|
| + |
rid,
|
| + |
root,
|
| + |
_tmp,
|
| + |
} = Repository::new();
|
| |
let signer = MockSigner::default();
|
| |
let namespace = *signer.public_key();
|
| |
let verifier = Verifier::new(&signer);
|
| |
|
| |
let mut last_changed_head = None;
|
| + |
let mut expected_parent = None;
|
| |
|
| - |
for refs in chain {
|
| - |
let update = write_log(refs.clone(), namespace, &signer, &repo);
|
| + |
for mut refs in chain {
|
| + |
refs.insert(IDENTITY_ROOT.to_ref_string(), root);
|
| + |
let update = write_log(refs.clone(), rid, namespace, &signer, &repo);
|
| |
|
| - |
if let Update::Changed { ref entry } = update {
|
| - |
last_changed_head = Some(*entry.oid());
|
| + |
if let Update::Changed { ref entry, .. } = update {
|
| + |
last_changed_head = Some(entry.oid());
|
| |
}
|
| |
|
| - |
let Latest {
|
| - |
refs: expected,
|
| - |
checkpoint,
|
| - |
..
|
| - |
} = read_log(namespace, &verifier, &repo);
|
| - |
|
| - |
if refs != expected {
|
| - |
return TestResult::failed();
|
| - |
}
|
| + |
let verified_commit = read_log(rid, namespace, &verifier, &repo);
|
| + |
let head = *verified_commit.commit().oid();
|
| + |
let parent = verified_commit.commit().parent().copied();
|
| + |
let new_refs = verified_commit.into_sigrefs_at(namespace);
|
| |
|
| - |
if checkpoint.reason() != CheckpointReason::Root {
|
| + |
if refs != *new_refs {
|
| |
return TestResult::failed();
|
| |
}
|
| |
|
| |
if let Some(expected_head) = last_changed_head {
|
| - |
if checkpoint.head() != expected_head {
|
| - |
return TestResult::failed();
|
| + |
if head != expected_head {
|
| + |
return TestResult::error(format!(
|
| + |
"expected commit to be {expected_head}, but found {head}"
|
| + |
));
|
| + |
}
|
| + |
if parent != expected_parent {
|
| + |
return TestResult::error(format!(
|
| + |
"expected parent commit to be {expected_parent:?}, but found {parent:?}"
|
| + |
));
|
| |
}
|
| |
}
|
| + |
expected_parent = Some(head);
|
| |
}
|
| |
|
| |
TestResult::passed()
|
| |
}
|
| |
|
| |
#[quickcheck]
|
| - |
fn idempotent_write(refs: Refs) -> bool {
|
| - |
let (_dir, repo) = setup();
|
| + |
fn idempotent_write(mut refs: Refs) -> bool {
|
| + |
let Repository {
|
| + |
raw: repo,
|
| + |
rid,
|
| + |
root,
|
| + |
_tmp,
|
| + |
} = Repository::new();
|
| + |
refs.insert(IDENTITY_ROOT.to_ref_string(), root);
|
| |
let signer = MockSigner::default();
|
| |
let namespace = *signer.public_key();
|
| |
|
| - |
let first = write_log(refs.clone(), namespace, &signer, &repo);
|
| + |
let first = write_log(refs.clone(), rid, namespace, &signer, &repo);
|
| |
let head_oid = match first {
|
| - |
Update::Changed { ref entry } => *entry.oid(),
|
| + |
Update::Changed { ref entry, .. } => entry.oid(),
|
| |
Update::Unchanged { .. } => return false,
|
| |
};
|
| |
|
| - |
let second = write_log(refs, namespace, &signer, &repo);
|
| - |
matches!(second, Update::Unchanged { commit, .. } if commit == head_oid)
|
| + |
let second = write_log(refs, rid, namespace, &signer, &repo);
|
| + |
matches!(second, Update::Unchanged { verified } if *verified.commit().oid() == head_oid)
|
| |
}
|