fslite-sqlite 0.1.0

A transport-independent, async virtual filesystem with a SQLite-backed persistent backend, HTTP adapter, and CLI.
Documentation
use fslite_core::{MutationOptions, RequestContext, VirtualPath, WriteOptions, WriteSource};
use fslite_sqlite::SqliteFileSystem;

fn path(input: &str) -> VirtualPath {
    VirtualPath::parse(input).unwrap()
}

#[tokio::test]
async fn usage_reflects_replacement() {
    let fs = SqliteFileSystem::open_in_memory(Default::default())
        .await
        .unwrap();
    let workspace = fs.create_workspace(Default::default()).await.unwrap();
    let ctx = RequestContext::trusted(workspace.id);

    fs.write(
        &ctx,
        &path("/f"),
        WriteSource::from_bytes(vec![0u8; 1000]),
        Default::default(),
    )
    .await
    .unwrap();
    let after_first = fs.workspace_usage(&ctx).await.unwrap();
    assert_eq!(after_first.active_logical_bytes, 1000);

    fs.write(
        &ctx,
        &path("/f"),
        WriteSource::from_bytes(vec![0u8; 250]),
        WriteOptions::replace(),
    )
    .await
    .unwrap();
    let after_replace = fs.workspace_usage(&ctx).await.unwrap();
    assert_eq!(after_replace.active_logical_bytes, 250);
}

#[tokio::test]
async fn usage_reflects_purge() {
    let fs = SqliteFileSystem::open_in_memory(Default::default())
        .await
        .unwrap();
    let workspace = fs.create_workspace(Default::default()).await.unwrap();
    let ctx = RequestContext::trusted(workspace.id);

    fs.write(
        &ctx,
        &path("/f"),
        WriteSource::from_bytes(vec![0u8; 512]),
        Default::default(),
    )
    .await
    .unwrap();

    let entry = fs
        .trash(&ctx, &path("/f"), MutationOptions::default())
        .await
        .unwrap();
    let while_trashed = fs.workspace_usage(&ctx).await.unwrap();
    assert_eq!(while_trashed.active_logical_bytes, 0);
    assert_eq!(while_trashed.trashed_logical_bytes, 512);

    fs.purge(&ctx, entry.id).await.unwrap();
    let after_purge = fs.workspace_usage(&ctx).await.unwrap();
    assert_eq!(after_purge.active_logical_bytes, 0);
    assert_eq!(after_purge.trashed_logical_bytes, 0);
}

#[tokio::test]
async fn usage_counts_nodes_including_root() {
    let fs = SqliteFileSystem::open_in_memory(Default::default())
        .await
        .unwrap();
    let workspace = fs.create_workspace(Default::default()).await.unwrap();
    let ctx = RequestContext::trusted(workspace.id);

    let usage = fs.workspace_usage(&ctx).await.unwrap();
    assert_eq!(usage.active_nodes, 1);

    fs.mkdir(&ctx, &path("/a"), Default::default())
        .await
        .unwrap();
    fs.touch(&ctx, &path("/b"), Default::default())
        .await
        .unwrap();

    let usage = fs.workspace_usage(&ctx).await.unwrap();
    assert_eq!(usage.active_nodes, 3);
}

#[tokio::test]
async fn usage_respects_configured_quotas() {
    let fs = SqliteFileSystem::open_in_memory(Default::default())
        .await
        .unwrap();
    let mut options = fslite_sqlite::WorkspaceOptions::default();
    options.max_bytes = 4096;
    options.max_nodes = 10;
    options.max_file_bytes = 1024;
    let workspace = fs.create_workspace(options).await.unwrap();
    let ctx = RequestContext::trusted(workspace.id);

    let usage = fs.workspace_usage(&ctx).await.unwrap();
    assert_eq!(usage.max_logical_bytes, 4096);
    assert_eq!(usage.max_nodes, 10);
    assert_eq!(usage.max_file_bytes, 1024);
}