dodo 0.3.1

Basic persistence library designed to be a quick and easy way to create a persistent storage.
Documentation
use std::io::{Read, Write};

use uuid::Uuid;

use dodo::storage::{Directory, Result, Storage};

#[test]
fn can_create_directory_storage() {
    let folder = fixture::create_folder();

    let storage = Directory::new(&folder);

    assert!(storage.is_ok(), "storage should have been created");
}

#[test]
fn should_create_sub_folders() {
    let folder = fixture::create_folder().path().join("sub/folder");

    let _storage = Directory::new(&folder).expect("could not create storage");

    assert!(folder.exists(), "storage should have created folder and sub folders");
}

#[test]
fn cant_use_file_as_directory_storage() {
    let folder = fixture::create_folder();
    let file = fixture::create_file_in(&folder, "file", "content");

    let storage = Directory::new(&file);

    assert!(storage.expect_err("shouldn't be able to create storage in a file").is_other(), "shouldn't be able to create storage in a file");
}

#[test]
fn can_create_new_entry() {
    let folder = fixture::create_folder();
    let mut storage = Directory::new(&folder).expect("could not create storage");

    let id = {
        let (id, mut write) = storage.new().expect("could not create new entry");
        write!(&mut write, "{}", "content").expect("could not write to new entry");
        id
    };

    fixture::assert_entry_equals(&folder, id, "content");
}

#[test]
fn can_read_existing_entry() {
    let folder = fixture::create_folder();
    let id = Uuid::parse_str("78190929-3d84-4735-9e40-80e3cd5530e9").unwrap();
    fixture::create_entry_in(&folder, id, "content");
    let storage = Directory::new(&folder).expect("could not create storage");

    let bytes = {
        let mut read = storage.read(id).expect("could not read storage");
        let mut buffer = String::new();
        read.read_to_string(&mut buffer).expect("could not read entry");
        buffer
    };

    assert_eq!(bytes, "content", "entry was read incorrectly");
}

#[test]
fn cant_read_absent_entry() {
    let folder = fixture::create_folder();
    let storage = Directory::new(&folder).expect("could not create storage");

    let entry = storage.read(Uuid::parse_str("78190929-3d84-4735-9e40-80e3cd5530e9").unwrap());

    assert!(entry.expect_err("entry should not exist").is_not_found(), "could not read entry");
}

#[test]
fn can_write_to_absent_entry() {
    let folder = fixture::create_folder();
    let id = Uuid::parse_str("78190929-3d84-4735-9e40-80e3cd5530e9").unwrap();
    let mut storage = Directory::new(&folder).expect("could not create storage");

    {
        let mut write = storage.write(id).expect("could not write to storage");
        write!(&mut write, "{}", "content").expect("could not write entry");
    };

    fixture::assert_entry_equals(&folder, id, "content");
}

#[test]
fn can_write_to_existing_entry() {
    let folder = fixture::create_folder();
    let id = Uuid::parse_str("78190929-3d84-4735-9e40-80e3cd5530e9").unwrap();
    fixture::create_entry_in(&folder, id, "content");
    let mut storage = Directory::new(&folder).expect("could not create storage");

    {
        let mut write = storage.write(id).expect("could not write to storage");
        write!(&mut write, "{}", "new content").expect("could not write entry");
    };

    fixture::assert_entry_equals(&folder, id, "new content");
}

#[test]
fn can_overwrite_existing_entry() {
    let folder = fixture::create_folder();
    let id = Uuid::parse_str("78190929-3d84-4735-9e40-80e3cd5530e9").unwrap();
    fixture::create_entry_in(&folder, id, "content");
    let mut storage = Directory::new(&folder).expect("could not create storage");

    {
        let mut write = storage.overwrite(id).expect("could not write to storage");
        write!(&mut write, "{}", "new content").expect("could not write entry");
    };

    fixture::assert_entry_equals(&folder, id, "new content");
}

#[test]
fn cant_overwrite_absent_entry() {
    let folder = fixture::create_folder();
    let id = Uuid::parse_str("78190929-3d84-4735-9e40-80e3cd5530e9").unwrap();
    let mut storage = Directory::new(&folder).expect("could not create storage");

    let entry = storage.overwrite(id);

    assert!(entry.expect_err("entry should not have existed").is_not_found(), "entry should not have been writen");
}

#[test]
fn can_delete_existing_entry() {
    let folder = fixture::create_folder();
    let id = Uuid::parse_str("78190929-3d84-4735-9e40-80e3cd5530e9").unwrap();
    fixture::create_entry_in(&folder, id, "content");
    let mut storage = Directory::new(&folder).expect("could not create storage");

    let deleted = storage.delete(id).expect("could not delete from storage");

    assert!(deleted, "something should have been deleted");
    fixture::assert_entry_not_exists(&folder, id);
}

#[test]
fn can_delete_absent_entry() {
    let folder = fixture::create_folder();
    let id = Uuid::parse_str("78190929-3d84-4735-9e40-80e3cd5530e9").unwrap();
    let mut storage = Directory::new(&folder).expect("could not create storage");

    let deleted = storage.delete(id).expect("could not delete from storage");

    assert!(!deleted, "nothing should have been deleted");
    fixture::assert_entry_not_exists(&folder, id);
}

#[test]
fn can_clear_storage() {
    let folder = fixture::create_folder();
    let id1 = Uuid::parse_str("78190929-3d84-4735-9e40-80e3cd5530e9").unwrap();
    let id2 = Uuid::parse_str("e91b1660-30ee-4db2-bf3d-244a0386c10f").unwrap();
    fixture::create_entry_in(&folder, id1, "content1");
    fixture::create_entry_in(&folder, id2, "content2");
    let mut storage = Directory::new(&folder).expect("could not create storage");

    storage.clear().expect("could not clear storage");

    fixture::assert_entry_not_exists(&folder, id1);
    fixture::assert_entry_not_exists(&folder, id2);
}

#[test]
fn can_clear_empty_storage() {
    let folder = fixture::create_folder();
    let mut storage = Directory::new(&folder).expect("could not create storage");

    storage.clear().expect("could not clear storage");
}

#[test]
fn can_list_existing_entries() {
    let folder = fixture::create_folder();
    let id1 = Uuid::parse_str("78190929-3d84-4735-9e40-80e3cd5530e9").unwrap();
    let id2 = Uuid::parse_str("e91b1660-30ee-4db2-bf3d-244a0386c10f").unwrap();
    fixture::create_entry_in(&folder, id1, "content1");
    fixture::create_entry_in(&folder, id2, "content2");
    let storage = Directory::new(&folder).expect("could not create storage");

    let entries: Vec<Uuid> = storage
        .iter().expect("could not read storage")
        .collect::<Result<Vec<Uuid>>>().expect("could not read storage");

    assert!(entries.contains(&id1), "entry 1 should exist");
    assert!(entries.contains(&id2), "entry 2 should exist.");
}

mod fixture {
    use std::{fs, path::{Path, PathBuf}};

    use uuid::Uuid;

    pub fn create_folder() -> tempfile::TempDir {
        tempfile::tempdir().expect("could not create folder")
    }

    pub fn create_file_in<P: AsRef<Path>>(folder: P, name: &str, content: &str) -> PathBuf {
        let path = folder.as_ref().join(name);
        fs::write(&path, content).expect("could not create file");
        path
    }

    pub fn create_entry_in<P: AsRef<Path>>(folder: P, id: Uuid, content: &str) {
        create_file_in(folder, &id.to_string(), content);
    }

    pub fn assert_entry_equals<P: AsRef<Path>>(folder: P, id: Uuid, expected_content: &str) {
        let path = folder.as_ref().join(id.to_string());
        assert!(path.exists(), "entry does not exist");

        let actual_content = fs::read_to_string(&path).expect("entry does not exist");
        assert_eq!(expected_content, actual_content, "entry has the wrong contents");
    }

    pub fn assert_entry_not_exists<P: AsRef<Path>>(folder: P, id: Uuid) {
        let path = folder.as_ref().join(id.to_string());
        assert!(!path.exists(), "entry should have been deleted");
    }
}