use std::io::{Read, Write};
use std::time::{Duration, SystemTime};
use xpct::{
approx_eq_time, be_err, be_none, be_ok, be_some, equal, every, expect, fields, have_len,
match_elements, match_fields, match_pattern, pattern,
};
use liteboxfs::{
Connection, CreateOptions, DEFAULT_ROOT_NAME, Error, FileBy, FileOrigin, RootId,
metadata::{FileKind, Owner},
root::Root,
};
mod root_id {
use super::*;
#[test]
fn default_root_id_matches_default_root() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let default_root_id = fs.root_id();
expect!(fs.find_root(DEFAULT_ROOT_NAME))
.to(be_ok())
.map(|root| root.id)
.to(equal(default_root_id));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}
mod root_name {
use super::*;
#[test]
fn default_root_name_is_default() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
expect!(fs.root())
.to(be_ok())
.map(|root| root.name)
.to(be_some())
.to(equal(DEFAULT_ROOT_NAME.to_string()));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn root_name_after_switch_to_named_root() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let new_root = fs.create_root(Some("test-root"))?;
fs.switch_root(new_root.id)?;
expect!(fs.root())
.to(be_ok())
.map(|root| root.name)
.to(be_some())
.to(equal("test-root".to_string()));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn root_name_returns_none_for_unnamed_root() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let new_root = fs.create_root(None)?;
fs.switch_root(new_root.id)?;
expect!(fs.root())
.to(be_ok())
.map(|root| root.name)
.to(be_none());
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}
mod set_root_name {
use super::*;
#[test]
fn changes_name_of_current_root() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let root = fs.create_root(Some("initial"))?;
fs.switch_root(root.id)?;
fs.set_root_name(Some("renamed"))?;
expect!(fs.root())
.to(be_ok())
.map(|root| root.name)
.to(be_some())
.to(equal("renamed"));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}
mod switch_root {
use super::*;
#[test]
fn switch_root_returns_previous_root_id() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let default_root_id = fs.root_id();
let new_root = fs.create_root(Some("new-root"))?;
expect!(fs.switch_root(new_root.id))
.to(be_ok())
.map(|root| root.id)
.to(equal(default_root_id));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn switch_root_changes_current_root() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let new_root = fs.create_root(Some("new-root"))?;
fs.switch_root(new_root.id)?;
expect!(fs.root_id()).to(equal(new_root.id));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn switch_root_to_same_root_is_noop() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let default_root_id = fs.root_id();
expect!(fs.switch_root(default_root_id))
.to(be_ok())
.map(|root| root.id)
.to(equal(default_root_id));
expect!(fs.root_id()).to(equal(default_root_id));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn switch_root_by_name() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let new_root = fs.create_root(Some("named"))?;
expect!(fs.switch_root("named"))
.to(be_ok())
.map(|root| root.id);
expect!(fs.root_id()).to(equal(new_root.id));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn switch_root_to_default_by_selector() -> liteboxfs::Result<()> {
use liteboxfs::RootBy;
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let default_root_id = fs.root_id();
let new_root = fs.create_root(Some("named"))?;
fs.switch_root(new_root.id)?;
expect!(fs.switch_root(RootBy::Default))
.to(be_ok())
.map(|root| root.id)
.to(equal(new_root.id));
expect!(fs.root_id()).to(equal(default_root_id));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn switch_root_multiple_times() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let default_root_id = fs.root_id();
let root_a = fs.create_root(Some("root-a"))?;
let root_b = fs.create_root(Some("root-b"))?;
expect!(fs.switch_root(root_a.id))
.to(be_ok())
.map(|root| root.id)
.to(equal(default_root_id));
expect!(fs.root_id()).to(equal(root_a.id));
expect!(fs.switch_root(root_b.id))
.to(be_ok())
.to(equal(root_a));
expect!(fs.root_id()).to(equal(root_b.id));
expect!(fs.switch_root(default_root_id))
.to(be_ok())
.to(equal(root_b));
expect!(fs.root_id()).to(equal(default_root_id));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}
mod find_root {
use super::*;
#[test]
fn find_root_returns_default_root_id_for_default_name() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let default_root_id = fs.root_id();
expect!(fs.find_root(DEFAULT_ROOT_NAME))
.to(be_ok())
.map(|root| root.id)
.to(equal(default_root_id));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn find_root_returns_created_root_id() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let created_id = fs.create_root(Some("my-root"))?;
expect!(fs.find_root("my-root"))
.to(be_ok())
.to(equal(created_id));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn find_root_by_id() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let created = fs.create_root(Some("named"))?;
expect!(fs.find_root(created.id))
.to(be_ok())
.map(|root| root.id)
.to(equal(created.id));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}
mod create_root {
use super::*;
#[test]
fn create_root_with_name_returns_new_id() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let default_root_id = fs.root_id();
expect!(fs.create_root(Some("new-root")))
.to(be_ok())
.map(|root| root.id)
.to_not(equal(default_root_id));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn create_root_without_name_returns_new_id() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let default_root_id = fs.root_id();
expect!(fs.create_root(None))
.to(be_ok())
.map(|root| root.id)
.to_not(equal(default_root_id));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn create_root_does_not_switch_to_new_root() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let default_root_id = fs.root_id();
fs.create_root(Some("new-root"))?;
expect!(fs.root_id()).to(equal(default_root_id));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn create_multiple_unnamed_roots() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let default_root_id = fs.root_id();
let root_a = fs.create_root(None)?;
let root_b = fs.create_root(None)?;
let root_c = fs.create_root(None)?;
expect!(&root_a).to_not(equal(&root_b));
expect!(&root_b).to_not(equal(&root_c));
expect!(&root_a).to_not(equal(&root_c));
expect!(root_a)
.map(|root| root.id)
.to_not(equal(default_root_id));
expect!(root_b)
.map(|root| root.id)
.to_not(equal(default_root_id));
expect!(root_c)
.map(|root| root.id)
.to_not(equal(default_root_id));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}
mod list_roots {
use xpct::any;
use super::*;
#[test]
fn list_roots_contains_default_root() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let default_root_id = fs.root_id();
expect!(fs.list_roots().collect::<liteboxfs::Result<Vec<_>>>())
.to(be_ok())
.to(have_len(1))
.to(match_elements([match_fields(fields!(Root {
id: equal(default_root_id),
name: equal(Some(DEFAULT_ROOT_NAME.to_string())),
created: approx_eq_time(SystemTime::now(), Duration::from_secs(1)),
}))]));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn list_roots_includes_all_created_roots() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let default_root_id = fs.root_id();
let named_root = fs.create_root(Some("named"))?;
let unnamed_root = fs.create_root(None)?;
expect!(fs.list_roots().collect::<liteboxfs::Result<Vec<_>>>())
.to(be_ok())
.to(have_len(3))
.to(every(|| {
any(|ctx| {
ctx.cloned()
.to(match_fields(fields!(Root {
id: equal(default_root_id),
name: equal(Some(DEFAULT_ROOT_NAME.to_string())),
created: approx_eq_time(SystemTime::now(), Duration::from_secs(1)),
})))
.to(match_fields(fields!(Root {
id: equal(named_root.id),
name: equal(Some("named".to_string())),
created: approx_eq_time(SystemTime::now(), Duration::from_secs(1)),
})))
.to(match_fields(fields!(Root {
id: equal(unnamed_root.id),
name: be_none(),
created: approx_eq_time(SystemTime::now(), Duration::from_secs(1)),
})))
.done()
})
}));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}
mod root_isolation {
use super::*;
#[test]
fn file_in_one_root_not_visible_in_another() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
let new_root = fs.create_root(Some("other"))?;
fs.switch_root(new_root.id)?;
expect!(fs.open("test.txt")).to(be_err()).to(match_pattern(
pattern!(Error::FileNotFound { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from("/test.txt")),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn files_with_same_path_can_exist_in_different_roots() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
let mut content_in_default = String::new();
let mut content_in_other = String::new();
conn.exec(|fs| {
let default_root_id = fs.root_id();
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(b"content from default")?;
drop(file);
let other_root = fs.create_root(Some("other"))?;
fs.switch_root(other_root.id)?;
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(b"content from other")?;
drop(file);
let mut file = fs.open("test.txt")?;
file.read_to_string(&mut content_in_other)?;
drop(file);
fs.switch_root(default_root_id)?;
let mut file = fs.open("test.txt")?;
file.read_to_string(&mut content_in_default)?;
liteboxfs::Result::Ok(())
})?;
expect!(content_in_default).to(equal("content from default".to_string()));
expect!(content_in_other).to(equal("content from other".to_string()));
Ok(())
}
#[test]
fn file_deletion_in_one_root_does_not_affect_another() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let default_root_id = fs.root_id();
fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
let other_root = fs.create_root(Some("other"))?;
fs.switch_root(other_root.id)?;
fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
fs.delete("test.txt")?;
fs.switch_root(default_root_id)?;
expect!(fs.open("test.txt")).to(be_ok());
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}
mod errors {
use liteboxfs::RootBy;
use super::*;
#[test]
fn switch_root_with_invalid_id_returns_invalid_root_id() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let fake_id: RootId = "00000000-0000-0000-0000-000000000000".parse().unwrap();
expect!(fs.switch_root(fake_id))
.to(be_err())
.to(match_pattern(
pattern!(Error::RootNotFound { root } if root == &RootBy::Id(fake_id)),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn find_root_with_nonexistent_name_returns_root_not_found() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
expect!(fs.find_root("nonexistent"))
.to(be_err())
.to(match_pattern(
pattern!(Error::RootNotFound { root } if root == &RootBy::Name("nonexistent".into())),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn create_root_with_duplicate_name_returns_root_already_exists() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create_root(Some("duplicate"))?;
expect!(fs.create_root(Some("duplicate")))
.to(be_err())
.to(match_pattern(
pattern!(Error::RootAlreadyExists { name } if name == "duplicate"),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn create_root_with_default_name_returns_root_already_exists() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
expect!(fs.create_root(Some(DEFAULT_ROOT_NAME)))
.to(be_err())
.to(match_pattern(
pattern!(Error::RootAlreadyExists { name } if name == DEFAULT_ROOT_NAME),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn set_root_name_to_existing_name_returns_root_already_exists() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let other_root_id = fs.create_root(Some("other"))?;
fs.switch_root(other_root_id.id)?;
expect!(fs.set_root_name(Some(DEFAULT_ROOT_NAME)))
.to(be_err())
.to(match_pattern(
pattern!(Error::RootAlreadyExists { name } if name == DEFAULT_ROOT_NAME),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn deleted_root_cannot_be_switched_to() -> liteboxfs::Result<()> {
use super::*;
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let root_to_delete = fs.create_root(Some("deleted"))?;
fs.delete_root(root_to_delete.id)?;
expect!(fs.switch_root(root_to_delete.id))
.to(be_err())
.to(match_pattern(
pattern!(Error::RootNotFound { root } if root == &root_to_delete.id.into()),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn cannot_delete_default_root() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let default_root_id = fs.root_id();
let new_root = fs.create_root(Some("new"))?;
fs.switch_root(new_root.id)?;
expect!(fs.delete_root(default_root_id))
.to(be_err())
.to(match_pattern(pattern!(Error::IsDefaultRoot)));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn cannot_delete_current_root() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let other_root = fs.create_root(Some("other"))?;
fs.switch_root(other_root.id)?;
expect!(fs.delete_root(other_root.id))
.to(be_err())
.to(match_pattern(pattern!(Error::IsCurrentRoot)));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn cannot_change_default_root_name() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
expect!(fs.set_root_name(Some("new name")))
.to(be_err())
.to(match_pattern(pattern!(Error::IsDefaultRoot)));
Ok(())
})
}
#[test]
fn cannot_unset_default_root_name() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
expect!(fs.set_root_name(None))
.to(be_err())
.to(match_pattern(pattern!(Error::IsDefaultRoot)));
Ok(())
})
}
#[test]
fn delete_root_by_name() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let to_delete = fs.create_root(Some("disposable"))?;
expect!(fs.delete_root("disposable")).to(be_ok());
expect!(fs.find_root(to_delete.id))
.to(be_err())
.to(match_pattern(
pattern!(Error::RootNotFound { root } if root == &to_delete.id.into()),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn delete_root_by_default_selector_returns_is_default_root() -> liteboxfs::Result<()> {
use liteboxfs::RootBy;
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let other = fs.create_root(Some("other"))?;
fs.switch_root(other.id)?;
expect!(fs.delete_root(RootBy::Default))
.to(be_err())
.to(match_pattern(pattern!(Error::IsDefaultRoot)));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}