active-storage 0.1.1

Active Storage facilitates uploading files to a cloud storage
Documentation
use std::{
    collections::HashMap,
    fs,
    path::{Path, PathBuf},
};

use active_storage::{
    drivers,
    multi_store::{MultiStore, Policy},
    StoreConfig,
};
use insta::assert_debug_snapshot;
use rstest::rstest;

fn create_temp_folder() -> PathBuf {
    tree_fs::from_yaml_str(
        r"
        files:
        ",
    )
    .unwrap()
}

async fn init_multi_store(root_path_store_1: &Path, root_path_store_2: &Path) -> MultiStore {
    let config = drivers::disk::Config {
        location: root_path_store_1.join("store-1"),
    };

    let store_one = StoreConfig::Disk(config).build().await.unwrap();

    let config = drivers::disk::Config {
        location: root_path_store_2.join("store-2"),
    };
    let secondary_store = StoreConfig::Disk(config).build().await.unwrap();

    let mut multi_store = MultiStore::new(store_one);
    multi_store.add_stores(HashMap::from([("store-2", secondary_store)]));

    multi_store
}

#[tokio::test]
async fn can_write_with_mirror_from_master() {
    let root_location_first_store = create_temp_folder();
    let root_location_second_store = create_temp_folder();

    let multi_store =
        init_multi_store(&root_location_first_store, &root_location_second_store).await;

    assert!(multi_store
        .mirror_stores_from_primary()
        .write(PathBuf::from("test").as_path(), b"content")
        .await
        .is_ok());

    let store_1_file_path = root_location_first_store.join("store-1").join("test");
    let store_2_file_path = root_location_second_store.join("store-2").join("test");

    assert!(store_1_file_path.exists());

    assert!(store_2_file_path.exists());

    assert_eq!(
        fs::read_to_string(store_1_file_path).unwrap(),
        fs::read_to_string(store_2_file_path).unwrap()
    );
}

#[tokio::test]
async fn can_delete_with_mirror_from_master() {
    let root_location_first_store = create_temp_folder();
    let root_location_second_store = create_temp_folder();

    let multi_store =
        init_multi_store(&root_location_first_store, &root_location_second_store).await;

    assert!(multi_store
        .mirror_stores_from_primary()
        .write(PathBuf::from("test").as_path(), b"content")
        .await
        .is_ok());

    let store_1_file_path = root_location_first_store.join("store-1").join("test");
    let store_2_file_path = root_location_second_store.join("store-2").join("test");

    assert!(store_1_file_path.exists());
    assert!(store_2_file_path.exists());

    assert!(multi_store
        .mirror_stores_from_primary()
        .delete(PathBuf::from("test").as_path())
        .await
        .is_ok());

    assert!(!store_1_file_path.exists());
    assert!(!store_2_file_path.exists());
}

#[tokio::test]
async fn can_delete_directory_with_mirror_from_master() {
    let root_location_first_store = create_temp_folder();
    let root_location_second_store = create_temp_folder();

    let multi_store =
        init_multi_store(&root_location_first_store, &root_location_second_store).await;

    assert!(multi_store
        .mirror_stores_from_primary()
        .write(
            PathBuf::from("delete-folder").join("test").as_path(),
            b"content"
        )
        .await
        .is_ok());

    let store_1_file_path = root_location_first_store
        .join("store-1")
        .join("delete-folder")
        .join("test");
    let store_2_file_path = root_location_second_store
        .join("store-2")
        .join("delete-folder")
        .join("test");

    assert!(store_1_file_path.exists());
    assert!(store_2_file_path.exists());

    assert!(multi_store
        .mirror_stores_from_primary()
        .delete_directory(PathBuf::from("delete-folder").as_path())
        .await
        .is_ok());

    assert!(!store_1_file_path.exists());
    assert!(!store_2_file_path.exists());
}

#[rstest]
#[case(Policy::ContinueOnFailure)]
#[case(Policy::StopOnFailure)]
#[tokio::test]
async fn can_failure_policy(#[case] policy: Policy) {
    let root_location_first_store = create_temp_folder();
    let root_location_second_store = create_temp_folder();
    let root_location_third_store = create_temp_folder();

    let config = drivers::disk::Config {
        location: root_location_first_store.join("store-1"),
    };

    let first_store = StoreConfig::Disk(config).build().await.unwrap();

    let config = drivers::disk::Config {
        location: root_location_second_store.join("store-2"),
    };
    let second_store = StoreConfig::Disk(config).build().await.unwrap();

    let config = drivers::disk::Config {
        location: root_location_third_store.join("store-3"),
    };
    let third_store = StoreConfig::Disk(config).build().await.unwrap();

    let mut multi_store = MultiStore::new(first_store);
    multi_store.set_mirrors_policy(policy.clone());
    multi_store.add_stores(HashMap::from([
        ("store-2", second_store),
        ("store-3", third_store),
    ]));

    assert!(multi_store
        .mirror_stores_from_primary()
        .write(PathBuf::from("test").as_path(), b"content")
        .await
        .is_ok());

    assert!(fs::remove_dir_all(root_location_second_store).is_ok());
    assert!(fs::remove_dir_all(root_location_third_store).is_ok());

    let res = multi_store
        .mirror_stores_from_primary()
        .delete(PathBuf::from("test").as_path())
        .await;

    assert_debug_snapshot!(format!("{policy:?}"), res);
}

#[tokio::test]
async fn can_mirror() {
    let root_location_first_store = create_temp_folder();
    let root_location_second_store = create_temp_folder();
    let root_location_third_store = create_temp_folder();

    let config = drivers::disk::Config {
        location: root_location_first_store.join("store-1"),
    };

    let first_store = StoreConfig::Disk(config).build().await.unwrap();

    let config = drivers::disk::Config {
        location: root_location_second_store.join("store-2"),
    };
    let second_store = StoreConfig::Disk(config).build().await.unwrap();

    let config = drivers::disk::Config {
        location: root_location_third_store.join("store-3"),
    };
    let third_store = StoreConfig::Disk(config).build().await.unwrap();

    let mut multi_store = MultiStore::new(first_store);
    multi_store.add_stores(HashMap::from([
        ("store-2", second_store),
        ("store-3", third_store),
    ]));
    assert!(multi_store
        .add_mirrors("foo", vec!["store-2", "store-3"].as_slice())
        .is_ok());

    assert!(multi_store
        .mirror("foo")
        .unwrap()
        .write(PathBuf::from("test").as_path(), b"content")
        .await
        .is_ok());

    assert!(!root_location_first_store
        .join("store-1")
        .join("test")
        .exists());
    assert!(root_location_second_store
        .join("store-2")
        .join("test")
        .exists());
    assert!(root_location_third_store
        .join("store-3")
        .join("test")
        .exists());
}