#![cfg(test)]
use etcetera::BaseStrategy as _;
use pretty_assertions::assert_eq;
use std::{collections::HashSet, convert, env, fs, path::Path};
use dots::{Link, World, WritePath};
use tap::Pipe as _;
use tempfile::tempdir;
fn create_dummy_config_file(dir: &Path) {
fs::write(dir.join("dots.toml"), "").unwrap();
}
#[track_caller]
fn check(
cwd: impl AsRef<Path>,
transform_world: impl FnOnce(World) -> World,
paths: impl IntoIterator<Item = (impl AsRef<Path>, impl AsRef<str>)>,
) {
let world = World::new(cwd.as_ref()).unwrap().pipe(transform_world);
let writes = world
.process()
.unwrap()
.writes
.into_iter()
.pipe(HashSet::from_iter);
assert_eq!(
writes,
paths
.into_iter()
.map(|(path, contents)| WritePath {
path: path.as_ref().to_path_buf(),
contents: contents.as_ref().to_string()
})
.collect::<HashSet<_>>()
);
}
fn create_files_in(
dir: &Path,
files: impl IntoIterator<Item = (impl AsRef<Path>, impl AsRef<str>)>,
) {
for (path, contents) in files {
let path = dir.join(path.as_ref());
fs::create_dir_all(path.parent().unwrap()).unwrap();
fs::write(&path, contents.as_ref()).unwrap();
}
}
#[bon::builder]
fn link(contents: &str, path: &str, sha256: Option<&str>, marker: Option<&str>) -> Link {
Link {
url: "dummy".into(),
contents: contents.to_string(),
path: path.into(),
sha256: sha256.as_ref().map(ToString::to_string),
marker: marker.as_ref().map(ToString::to_string),
}
}
#[test]
fn dirs() {
let dir = tempdir().unwrap();
let dir = dir.path();
let strat = etcetera::choose_base_strategy().unwrap();
create_files_in(
dir,
[
(
"dots.toml",
r#"
[[dir]]
input = "configs"
output = "{config_dir}"
[[dir]]
input = "data"
output = "{data_dir}/foo/"
[[dir]]
input = "cache"
output = "{cache_dir}/bar/"
[[dir]]
input = "cache"
output = "~/baz/"
"#,
),
("configs/foo.txt", "foo"),
("configs/bar.txt", "bar"),
("data/foo.txt", "foo"),
("data/bar.txt", "bar"),
("cache/foo.txt", "foo"),
("cache/bar.txt", "bar"),
],
);
check(
dir,
convert::identity,
[
(strat.config_dir().join("foo.txt"), "foo"),
(strat.config_dir().join("bar.txt"), "bar"),
(strat.data_dir().join("foo").join("foo.txt"), "foo"),
(strat.data_dir().join("foo").join("bar.txt"), "bar"),
(strat.cache_dir().join("bar").join("foo.txt"), "foo"),
(strat.cache_dir().join("bar").join("bar.txt"), "bar"),
(strat.home_dir().join("baz").join("foo.txt"), "foo"),
(strat.home_dir().join("baz").join("bar.txt"), "bar"),
],
);
}
#[test]
fn links() {
let dir = tempdir().unwrap();
let dir = dir.path();
create_dummy_config_file(dir);
check(
dir,
|mut world| {
world.links = vec![
link().contents("foo").path("configs/foo.txt").call(),
link()
.contents("bar")
.path("bar.txt")
.marker("foo bar baz")
.call(),
];
world
},
[
(
dir.join("configs").join("foo.txt"),
concat!(
"# @generated by `@dots ` <https://github.com/nik-rev/dots>\n# ",
"Do not edit by hand.\n# \n# downloaded from: dummy\nfoo"
)
.to_string(),
),
(
dir.join("bar.txt"),
concat!(
"# @dots foo bar baz\n# @generated by `@dots `",
" <https://github.com/nik-rev/dots>\n# ",
"Do not edit by hand.\n# \n# downloaded from: dummy\nbar"
)
.to_string(),
),
],
);
}
#[test]
fn marker_does_not_affect_sha256() {
const SHA256: &str = "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae";
let dir = tempdir().unwrap();
let dir = dir.path();
create_dummy_config_file(dir);
check(
dir,
|mut world| {
world.links = vec![
link()
.contents("foo")
.path("configs/foo-1.txt")
.sha256(SHA256)
.call(),
link()
.contents("foo")
.path("foo-2.txt")
.sha256(SHA256)
.marker("marker")
.call(),
];
world
},
[
(
dir.join("configs").join("foo-1.txt"),
concat!(
"# @generated by `@dots ` <https://github.com/nik-rev/dots>\n",
"# Do not edit by hand.\n",
"# \n",
"# downloaded from: dummy\n",
"foo"
)
.to_string(),
),
(
dir.join("foo-2.txt"),
concat!(
"# @dots marker\n",
"# @generated by `@dots ` <https://github.com/nik-rev/dots>\n",
"# Do not edit by hand.\n",
"# \n",
"# downloaded from: dummy\n",
"foo"
)
.to_string(),
),
],
);
}
#[test]
fn hash_mismatch_is_err() {
const UNIQUE_FILENAME: &str =
"2c26b46b68ffc68ff99b45ec1d304134e3422d70e483bfa0f98a5e886266e7ae";
let dir = tempdir().unwrap();
let dir = dir.path();
let strat = etcetera::choose_base_strategy().unwrap();
fs::write(
dir.join("dots.toml"),
r#"
[[dir]]
input = "configs"
output = "{config_dir}"
"#,
)
.unwrap();
fs::create_dir_all(dir.join("configs")).unwrap();
fs::write(dir.join("configs").join(UNIQUE_FILENAME), "foo").unwrap();
let mut world = World::new(dir).unwrap();
world.links = vec![
link()
.contents("foo")
.path("configs/foo-1.txt")
.sha256("incorrect-hash")
.call(),
];
assert!(
world
.process()
.unwrap_err()
.first()
.unwrap()
.to_string()
.contains("hash mismatch")
);
fs::read_to_string(strat.config_dir().join("configs").join(UNIQUE_FILENAME)).unwrap_err();
}
#[test]
fn env_variable_interpolation() {
let dir = tempdir().unwrap();
let dir = dir.path();
let strat = etcetera::choose_base_strategy().unwrap();
unsafe {
env::set_var("VARIABLE_FOO", "<foo>");
}
unsafe {
env::set_var("VARIABLE_BAR", "<bar>");
}
create_files_in(
dir,
[
(
"dots.toml",
r#"
[[dir]]
input = "configs"
output = "{config_dir}/{$VARIABLE_FOO}/{$VARIABLE_BAR}"
"#,
),
("configs/foo.txt", "foo"),
],
);
check(
dir,
convert::identity,
[(
strat
.config_dir()
.join("<foo>")
.join("<bar>")
.join("foo.txt"),
"foo",
)],
);
}