link-cli 0.2.6

A CLI tool and reusable library for links manipulation backed by a LiNo-notation doublet storage engine.
Documentation
use anyhow::Result;
use link_cli::{external_reference, LinkStorage, NamedLinks, UnicodeStringStorage};
use tempfile::NamedTempFile;

fn with_storage(test: impl FnOnce(&mut UnicodeStringStorage<'_>) -> Result<()>) -> Result<()> {
    let temp_file = NamedTempFile::new()?;
    let db_path = temp_file.path().to_str().unwrap();
    let mut links = LinkStorage::new(db_path, false)?;
    let mut storage = UnicodeStringStorage::new(&mut links)?;
    test(&mut storage)
}

#[test]
fn create_and_retrieve_empty_string() -> Result<()> {
    with_storage(|storage| {
        let empty = storage.create_string("")?;
        assert_eq!("", storage.get_string(empty)?);
        Ok(())
    })
}

#[test]
fn create_and_retrieve_simple_string() -> Result<()> {
    with_storage(|storage| {
        let hello = storage.create_string("Hello")?;
        assert_eq!("Hello", storage.get_string(hello)?);
        Ok(())
    })
}

#[test]
fn create_and_retrieve_multiple_strings() -> Result<()> {
    with_storage(|storage| {
        let first = storage.create_string("First")?;
        let second = storage.create_string("Second")?;

        assert_eq!("First", storage.get_string(first)?);
        assert_eq!("Second", storage.get_string(second)?);
        Ok(())
    })
}

#[test]
fn create_and_retrieve_unicode_string_as_utf16_sequence() -> Result<()> {
    with_storage(|storage| {
        let content = "Hello, 世界! Привет, мир! 😀";
        let link = storage.create_string(content)?;

        assert_eq!(content, storage.get_string(link)?);
        assert!(storage.unicode_sequence_code_units(link)?.len() > content.chars().count());
        Ok(())
    })
}

#[test]
fn pinned_types_are_created_and_named() -> Result<()> {
    with_storage(|storage| {
        assert_eq!(1, storage.type_type());
        assert_eq!(2, storage.unicode_symbol_type());
        assert_eq!(3, storage.unicode_sequence_type());
        assert_eq!(4, storage.string_type());
        assert_eq!(5, storage.empty_string_type());
        assert_eq!(6, storage.name_type());

        for (id, name) in [
            (storage.type_type(), "Type"),
            (storage.unicode_symbol_type(), "UnicodeSymbol"),
            (storage.unicode_sequence_type(), "UnicodeSequence"),
            (storage.string_type(), "String"),
            (storage.empty_string_type(), "EmptyString"),
            (storage.name_type(), "Name"),
        ] {
            assert_eq!(Some(id), storage.get_by_name(name)?);
            assert_eq!(Some(name.to_string()), storage.get_name(id)?);
        }

        Ok(())
    })
}

#[test]
fn create_and_retrieve_user_defined_type() -> Result<()> {
    with_storage(|storage| {
        let user_type = storage.get_or_create_type("UserType")?;
        assert_eq!(Some(user_type), storage.get_by_name("UserType")?);
        assert_eq!(Some("UserType".to_string()), storage.get_name(user_type)?);
        Ok(())
    })
}

#[test]
fn name_external_reference_matches_csharp_hybrid_encoding() -> Result<()> {
    with_storage(|storage| {
        assert_eq!(u32::MAX, external_reference(1));

        storage.set_name_for_external_reference(1, "MyExternalReference")?;

        assert_eq!(
            Some("MyExternalReference".to_string()),
            storage.get_name_by_external_reference(1)?
        );
        assert_eq!(
            Some(1),
            storage.get_external_reference_by_name("MyExternalReference")?
        );
        assert_eq!(
            Some("MyExternalReference".to_string()),
            storage.get_name(external_reference(1))?
        );

        Ok(())
    })
}

#[test]
fn name_is_removed_when_link_is_deleted() -> Result<()> {
    with_storage(|storage| {
        let link = storage.links_mut().create(0, 0);
        storage.set_name(link, "TestName")?;
        assert_eq!(Some("TestName".to_string()), storage.get_name(link)?);
        assert_eq!(Some(link), storage.get_by_name("TestName")?);

        storage.links_mut().delete(link)?;
        storage.remove_name(link)?;

        assert_eq!(None, storage.get_by_name("TestName")?);
        assert_eq!(None, storage.get_name(link)?);
        Ok(())
    })
}

#[test]
fn deleting_non_named_link_does_not_affect_other_names() -> Result<()> {
    with_storage(|storage| {
        let named_link = storage.links_mut().create(0, 0);
        storage.set_name(named_link, "Named")?;
        let unnamed_link = storage.links_mut().create(0, 0);

        storage.links_mut().delete(unnamed_link)?;

        assert_eq!(Some(named_link), storage.get_by_name("Named")?);
        assert_eq!(Some("Named".to_string()), storage.get_name(named_link)?);
        Ok(())
    })
}

#[test]
fn name_is_removed_when_external_reference_is_deleted() -> Result<()> {
    with_storage(|storage| {
        storage.set_name_for_external_reference(123, "ExternalName")?;
        assert_eq!(
            Some("ExternalName".to_string()),
            storage.get_name_by_external_reference(123)?
        );
        assert_eq!(
            Some(123),
            storage.get_external_reference_by_name("ExternalName")?
        );

        storage.remove_name_by_external_reference(123)?;

        assert_eq!(
            None,
            storage.get_external_reference_by_name("ExternalName")?
        );
        assert_eq!(None, storage.get_name_by_external_reference(123)?);
        Ok(())
    })
}

#[test]
fn named_links_facade_matches_csharp_named_links_role() -> Result<()> {
    let temp_file = NamedTempFile::new()?;
    let db_path = temp_file.path().to_str().unwrap();
    let mut links = LinkStorage::new(db_path, false)?;
    let mut named_links = NamedLinks::new(&mut links)?;

    let link = named_links.unicode_storage_mut().links_mut().create(0, 0);
    named_links.set_name(link, "FacadeName")?;

    assert_eq!(Some(link), named_links.get_by_name("FacadeName")?);
    assert_eq!(Some("FacadeName".to_string()), named_links.get_name(link)?);
    Ok(())
}