Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
radicle-cob: test parse_refstr
Fintan Halpenny committed 3 years ago
commit c620f873e5a907b5919c70c5b887a6a3a7085cb6
parent a985e067a51a9042282c6129a97375e5ecaaf7cc
6 files changed +132 -1
modified Cargo.lock
@@ -1596,6 +1596,7 @@ name = "radicle-cob"
version = "0.1.0"
dependencies = [
 "ed25519-compact",
+
 "fastrand",
 "git-commit",
 "git-ref-format",
 "git-trailers",
@@ -1603,6 +1604,7 @@ dependencies = [
 "log",
 "petgraph",
 "quickcheck",
+
 "quickcheck_macros",
 "radicle-crypto",
 "radicle-git-ext",
 "serde",
modified radicle-cob/Cargo.toml
@@ -38,8 +38,11 @@ features = ["derive"]

[dev-dependencies]
ed25519-compact = { version = "1.0.12", features = ["pem"] }
+
fastrand = { version = "1.8.0", default-features = false }
+
git-ref-format = { version = "0.1", features = ["macro"] }
tempfile = { version = "3" }
quickcheck = { version = "1", default-features = false }
+
quickcheck_macros = { version = "1", default-features = false }

[dev-dependencies.radicle-crypto]
path = "../radicle-crypto"
modified radicle-cob/src/lib.rs
@@ -71,6 +71,12 @@
//! [automerge]: https://automerge.org
//! [RFC-0662]: https://github.com/radicle-dev/radicle-link/blob/master/docs/rfc/0662-collaborative-objects.adoc

+
#[cfg(test)]
+
extern crate quickcheck;
+
#[cfg(test)]
+
#[macro_use(quickcheck)]
+
extern crate quickcheck_macros;
+

extern crate radicle_crypto as crypto;
extern crate radicle_git_ext as git_ext;

modified radicle-cob/src/test.rs
@@ -3,3 +3,5 @@ pub use identity::{Name, Person, Project, RemoteProject};

pub mod storage;
pub use storage::Storage;
+

+
pub mod arbitrary;
added radicle-cob/src/test/arbitrary.rs
@@ -0,0 +1,51 @@
+
use std::{iter, marker::PhantomData};
+

+
use quickcheck::Arbitrary;
+

+
use crate::{ObjectId, TypeName};
+

+
#[derive(Clone, Debug)]
+
pub struct Invalid<T> {
+
    pub value: String,
+
    _marker: PhantomData<T>,
+
}
+

+
impl Arbitrary for TypeName {
+
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
+
        let rng = fastrand::Rng::with_seed(u64::arbitrary(g));
+
        let mut name: Vec<String> = Vec::new();
+
        for _ in 0..rng.usize(1..5) {
+
            name.push(
+
                iter::repeat_with(|| rng.alphanumeric())
+
                    .take(rng.usize(1..16))
+
                    .collect(),
+
            );
+
        }
+
        name.join(".")
+
            .parse::<TypeName>()
+
            .expect("TypeName is valid")
+
    }
+
}
+

+
impl Arbitrary for ObjectId {
+
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
+
        let rng = fastrand::Rng::with_seed(u64::arbitrary(g));
+
        let bytes = iter::repeat_with(|| rng.u8(..))
+
            .take(20)
+
            .collect::<Vec<_>>();
+
        Self::from(git_ext::Oid::try_from(bytes.as_slice()).unwrap())
+
    }
+
}
+

+
impl Arbitrary for Invalid<ObjectId> {
+
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
+
        let rng = fastrand::Rng::with_seed(u64::arbitrary(g));
+
        let value = iter::repeat_with(|| rng.alphanumeric())
+
            .take(rng.usize(21..50))
+
            .collect();
+
        Invalid {
+
            value,
+
            _marker: PhantomData,
+
        }
+
    }
+
}
modified radicle-cob/src/tests.rs
@@ -1,10 +1,14 @@
use std::ops::ControlFlow;

use crypto::test::signer::MockSigner;
+
use git_ref_format::{refname, Component, RefString};
use quickcheck::Arbitrary;
use radicle_crypto::Signer;

-
use crate::{create, get, history, list, update, Create, ObjectId, TypeName, Update};
+
use crate::{
+
    create, get, history, list, object, test::arbitrary::Invalid, update, Create, ObjectId,
+
    TypeName, Update,
+
};

use super::test;

@@ -223,6 +227,69 @@ fn traverse_cobs() {
    assert_eq!(contents, vec![b"issue 1".to_vec(), b"issue 2".to_vec()]);
}

+
#[quickcheck]
+
fn parse_refstr(oid: ObjectId, typename: TypeName) {
+
    let suffix = refname!("refs/cobs")
+
        .and(Component::from(&typename))
+
        .and(Component::from(&oid));
+

+
    // refs/cobs/<typename>/<object_id> gives back the <typename> and <object_id>
+
    assert_eq!(object::parse_refstr(&suffix), Some((typename.clone(), oid)));
+

+
    // strips a single namespace
+
    assert_eq!(
+
        object::parse_refstr(&refname!("refs/namespaces/a").join(&suffix)),
+
        Some((typename.clone(), oid))
+
    );
+

+
    // strips multiple namespaces
+
    assert_eq!(
+
        object::parse_refstr(&refname!("refs/namespaces/a/refs/namespaces/b").join(&suffix)),
+
        Some((typename.clone(), oid))
+
    );
+

+
    // ignores the extra path
+
    assert_eq!(
+
        object::parse_refstr(
+
            &refname!("refs/namespaces/a/refs/namespaces/b")
+
                .join(suffix)
+
                .and(refname!("more/paths"))
+
        ),
+
        Some((typename, oid))
+
    );
+
}
+

+
/// Note: an invalid type name is also an invalid reference string, it
+
/// cannot start or end with a '.', and cannot have '..'.
+
#[quickcheck]
+
fn invalid_parse_refstr(oid: Invalid<ObjectId>, typename: TypeName) {
+
    let oid = RefString::try_from(oid.value).unwrap();
+
    let typename = Component::from(&typename);
+
    let suffix = refname!("refs/cobs").and(typename).and(oid);
+

+
    // All parsing will fail because `oid` is not a valid ObjectId
+
    assert_eq!(object::parse_refstr(&suffix), None);
+

+
    assert_eq!(
+
        object::parse_refstr(&refname!("refs/namespaces/a").join(&suffix)),
+
        None
+
    );
+

+
    assert_eq!(
+
        object::parse_refstr(&refname!("refs/namespaces/a/refs/namespaces/b").join(&suffix)),
+
        None
+
    );
+

+
    assert_eq!(
+
        object::parse_refstr(
+
            &refname!("refs/namespaces/a/refs/namespaces/b")
+
                .join(suffix)
+
                .and(refname!("more/paths"))
+
        ),
+
        None
+
    );
+
}
+

fn gen<T: Arbitrary>(size: usize) -> T {
    let mut gen = quickcheck::Gen::new(size);