use super::TempDir;
use crate::paths::*;
fn packaged(root: &std::path::Path) -> AppPaths {
AppPaths::from_host(
"com.example.myapp",
root.join("data"),
root.join("cache"),
root.join("resources"),
)
.expect("packaged paths")
}
#[test]
fn nothing_writable_is_inside_the_installation_directory() {
let temp = TempDir::new("paths");
let paths = packaged(temp.path());
for writable in [
paths.database(),
paths.uploads(),
paths.spill(),
paths.logs(),
paths.temp(),
paths.exports(),
paths.public(),
paths.secret_file(),
] {
assert!(
!writable.starts_with(paths.resources()),
"{} is inside the installation directory",
writable.display()
);
}
}
#[test]
fn a_spilled_upload_goes_to_cache_and_the_database_does_not() {
let temp = TempDir::new("paths");
let paths = packaged(temp.path());
assert!(paths.spill().starts_with(paths.cache()));
assert!(paths.temp().starts_with(paths.cache()));
assert!(paths.database().starts_with(paths.data()));
assert!(paths.uploads().starts_with(paths.data()));
assert!(paths.secret_file().starts_with(paths.data()));
}
#[test]
fn prepare_creates_every_directory_and_can_be_run_twice() {
let temp = TempDir::new("paths");
let paths = packaged(temp.path());
paths.prepare().expect("a first launch");
paths.prepare().expect("a second launch");
for dir in [
paths.data().to_path_buf(),
paths.cache().to_path_buf(),
paths.config().to_path_buf(),
paths.public(),
paths.uploads(),
paths.spill(),
paths.logs(),
paths.exports(),
] {
assert!(dir.is_dir(), "{} was not created", dir.display());
}
}
#[test]
fn the_sqlite_url_is_absolute_and_uses_forward_slashes() {
let temp = TempDir::new("paths");
let paths = packaged(temp.path());
let url = paths.sqlite_url();
assert!(url.starts_with("sqlite://"), "{url}");
assert!(url.ends_with("app.db?mode=rwc"), "{url}");
assert!(!url.contains('\\'), "{url}");
assert!(
url.trim_start_matches("sqlite://").len() > "app.db?mode=rwc".len() + 3,
"{url}"
);
}
#[test]
fn an_identifier_that_is_not_one_is_refused_before_a_directory_is_named() {
let temp = TempDir::new("paths");
assert!(
AppPaths::from_host("not-an-identifier", temp.path(), temp.path(), temp.path()).is_err()
);
}
fn write(path: &std::path::Path, contents: &str) {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).expect("a directory");
}
std::fs::write(path, contents).expect("a file");
}
#[test]
fn assets_are_staged_once_per_version() {
let temp = TempDir::new("assets");
let source = temp.path().join("bundled");
let destination = temp.path().join("staged");
write(&source.join("js/main.js"), "one");
assert!(stage_public_assets(&source, &destination, "1.0.0").expect("a first launch"));
assert_eq!(
std::fs::read_to_string(destination.join("js/main.js")).unwrap(),
"one"
);
write(&source.join("js/main.js"), "two");
assert!(!stage_public_assets(&source, &destination, "1.0.0").expect("a second launch"));
assert_eq!(
std::fs::read_to_string(destination.join("js/main.js")).unwrap(),
"one"
);
}
#[test]
fn an_upgrade_removes_an_asset_the_new_version_does_not_have() {
let temp = TempDir::new("assets");
let source = temp.path().join("bundled");
let destination = temp.path().join("staged");
write(&source.join("js/old.js"), "gone next version");
stage_public_assets(&source, &destination, "1.0.0").expect("a first install");
assert!(destination.join("js/old.js").exists());
std::fs::remove_file(source.join("js/old.js")).unwrap();
write(&source.join("js/new.js"), "current");
assert!(stage_public_assets(&source, &destination, "1.1.0").expect("an upgrade"));
assert!(!destination.join("js/old.js").exists());
assert!(destination.join("js/new.js").exists());
}
#[test]
fn an_upgrade_does_not_touch_application_data() {
let temp = TempDir::new("assets");
let paths = packaged(temp.path());
paths.prepare().expect("a first launch");
let source = temp.path().join("bundled");
write(&source.join("js/main.js"), "one");
stage_public_assets(&source, &paths.public(), "1.0.0").expect("a first install");
let database = paths.database();
std::fs::write(&database, b"user data").expect("a database");
let upload = paths.uploads().join("photo.png");
std::fs::write(&upload, b"a photo").expect("an upload");
write(&source.join("js/main.js"), "two");
stage_public_assets(&source, &paths.public(), "2.0.0").expect("an upgrade");
assert_eq!(std::fs::read(&database).unwrap(), b"user data");
assert_eq!(std::fs::read(&upload).unwrap(), b"a photo");
assert_eq!(
std::fs::read_to_string(paths.public().join("js/main.js")).unwrap(),
"two"
);
}
#[test]
fn staging_from_a_package_with_no_assets_says_so() {
let temp = TempDir::new("assets");
let error = stage_public_assets(
&temp.path().join("missing"),
&temp.path().join("staged"),
"1.0.0",
)
.expect_err("nothing to stage");
assert_eq!(error.step, "assets");
}
#[test]
fn a_nested_asset_tree_is_copied_whole() {
let temp = TempDir::new("assets");
let source = temp.path().join("bundled");
let destination = temp.path().join("staged");
write(&source.join("css/styles.css"), "body{}");
write(&source.join("js/pp-reactive-v2.min.js"), "runtime");
write(&source.join("images/deep/nested/icon.svg"), "<svg/>");
stage_public_assets(&source, &destination, "1.0.0").expect("a first install");
assert!(destination.join("css/styles.css").exists());
assert!(destination.join("js/pp-reactive-v2.min.js").exists());
assert!(destination.join("images/deep/nested/icon.svg").exists());
}
use crate::paths::EmbeddedAsset;
fn embedded() -> Vec<EmbeddedAsset<'static>> {
vec![
EmbeddedAsset {
path: "js/main.js",
bytes: b"import runtime",
},
EmbeddedAsset {
path: "js/pp-reactive-v2.min.js",
bytes: b"the runtime",
},
EmbeddedAsset {
path: "css/styles.css",
bytes: b"body{}",
},
EmbeddedAsset {
path: "favicon.ico",
bytes: b"