use serde_json::json;
use super::*;
fn flock() -> FlockNames {
FlockNames {
sheep: ["api", "we.b", "web"]
.iter()
.map(|name| (*name).to_string())
.collect(),
dogs: ["metrics", "kelpie"]
.iter()
.map(|name| (*name).to_string())
.collect(),
}
}
fn config() -> Json {
json!({
"name": "api",
"script": "./api.js",
"args": ["--port", "8080"],
"env": {},
"cwd": null,
"instances": 2,
"autorestart": true,
"max_memory": "1G",
"restart_delay": null,
"watch_paths": ["src/**/*.js"],
"probe": {"type": "http", "url": "http://127.0.0.1:8080/healthz"},
"dogs": {
"kelpie": {
"enabled": true,
"git": {"checkout": "/srv/api", "merging": "ff"},
"api.token": "literal-dotted-key"
},
"metrics": {"interval": 10}
}
})
}
fn sheep_written(raw: &str) -> Written {
let segments = segments(raw).unwrap_or_else(|err| panic!("{raw}: {err}"));
let name = address::longest_in(&segments, &flock().sheep)
.unwrap_or_else(|| panic!("{raw}: no sheep in this flock is named by it"));
let depth = name.split('.').count();
Written {
holder: Holder::Sheep,
name,
rest: segments[depth..].to_vec(),
}
}
fn dog_written(name: &str, path: &[&str]) -> Written {
Written {
holder: Holder::Dog,
name: name.to_string(),
rest: path.iter().map(|part| (*part).to_string()).collect(),
}
}
fn sheep_toml(raw: &str) -> String {
let written = sheep_written(raw);
let dogs = address::DogNames::new(&flock(), &config(), &Json::Null);
let steps = sheep_steps(&written.name, &written.rest, &dogs, &config())
.unwrap_or_else(|err| panic!("{raw}: {err}"));
sheep_document(&written, &config(), &steps).unwrap_or_else(|err| panic!("{raw}: {err}"))
}
#[test]
fn a_sheep_name_alone_prints_its_config() {
let toml = sheep_toml("api");
assert_eq!(
toml,
"args = [\"--port\", \"8080\"]\n\
autorestart = true\n\
instances = 2\n\
max_memory = \"1G\"\n\
name = \"api\"\n\
script = \"./api.js\"\n\
watch_paths = [\"src/**/*.js\"]\n\
\n\
[dogs.kelpie]\n\"api.token\" = \"literal-dotted-key\"\nenabled = true\n\
\n\
[dogs.kelpie.git]\ncheckout = \"/srv/api\"\nmerging = \"ff\"\n\
\n\
[dogs.metrics]\ninterval = 10\n\
\n\
[probe]\ntype = \"http\"\nurl = \"http://127.0.0.1:8080/healthz\"\n"
);
}
#[test]
fn an_unset_field_is_left_out_of_a_whole_config() {
let toml = sheep_toml("api");
assert!(!toml.contains("cwd"), "{toml}");
assert!(!toml.contains("restart_delay"), "{toml}");
assert!(!toml.contains("[env]"), "{toml}");
}
#[test]
fn one_field_prints_one_line() {
assert_eq!(sheep_toml("api.max_memory"), "max_memory = \"1G\"\n");
}
#[test]
fn a_dogs_table_inside_a_sheep_prints_its_own_header() {
assert_eq!(
sheep_toml("api.dogs.kelpie.git"),
"[dogs.kelpie.git]\ncheckout = \"/srv/api\"\nmerging = \"ff\"\n"
);
}
#[test]
fn every_dog_table_prints_when_no_dog_is_named() {
let toml = sheep_toml("api.dogs");
assert!(toml.contains("[dogs.kelpie]"), "{toml}");
assert!(toml.contains("[dogs.metrics]"), "{toml}");
}
#[test]
fn an_empty_dog_table_keeps_its_header() {
let mut cfg = config();
cfg["dogs"]["kelpie"]["options"] = json!({});
let written = sheep_written("api.dogs.kelpie.options");
let toml = sheep_document(&written, &cfg, &written.rest).unwrap();
assert_eq!(toml, "[dogs.kelpie.options]\n", "{toml}");
let whole = sheep_document(&sheep_written("api"), &cfg, &[]).unwrap();
assert!(whole.contains("[dogs.kelpie.options]"), "{whole}");
}
#[test]
fn a_sheep_with_no_dogs_prints_no_dogs_header() {
let mut cfg = config();
cfg["dogs"] = json!({});
let whole = sheep_document(&sheep_written("api"), &cfg, &[]).unwrap();
assert!(!whole.contains("dogs"), "{whole}");
let bare = sheep_written("api.dogs");
assert_eq!(
sheep_document(&bare, &cfg, &bare.rest).unwrap(),
"",
"an empty container has nothing to spell"
);
}
#[test]
fn one_key_of_a_dog_table_prints_under_that_table() {
assert_eq!(
sheep_toml("api.dogs.kelpie.git.merging"),
"[dogs.kelpie.git]\nmerging = \"ff\"\n"
);
}
#[test]
fn a_dotted_key_of_a_dog_table_stays_one_key() {
assert_eq!(
sheep_toml("api.dogs.kelpie.api.token"),
"[dogs.kelpie]\n\"api.token\" = \"literal-dotted-key\"\n"
);
}
#[test]
fn a_dotted_key_inside_a_dotted_key_reads_from_a_sheeps_dog_table() {
let mut cfg = config();
cfg["dogs"]["kelpie"]["api.token"] = json!({ "auth.key": 42 });
let dogs = address::DogNames::new(&flock(), &cfg, &Json::Null);
let written = sheep_written("api.dogs.kelpie.api.token.auth.key");
let steps = sheep_steps(&written.name, &written.rest, &dogs, &cfg).unwrap();
assert_eq!(
steps,
["dogs", "kelpie", "api.token", "auth.key"],
"{steps:?}"
);
assert_eq!(
sheep_document(&written, &cfg, &steps).unwrap(),
"[dogs.kelpie.\"api.token\"]\n\"auth.key\" = 42\n"
);
}
#[test]
fn a_sheep_name_with_a_dot_is_one_name() {
let written = sheep_written("we.b.dogs.kelpie");
assert_eq!(written.holder, Holder::Sheep);
assert_eq!(written.name, "we.b");
assert_eq!(written.rest, ["dogs", "kelpie"]);
}
#[test]
fn a_dog_address_offers_its_names_longest_first() {
assert_eq!(
dog_candidates(&segments("kelpie.worker.token").unwrap()),
["kelpie.worker.token", "kelpie.worker", "kelpie"]
);
assert_eq!(dog_candidates(&segments("kelpie").unwrap()), ["kelpie"]);
}
#[test]
fn a_name_neither_a_sheep_nor_a_dog_names_the_store() {
let refusal = ReadRefusal::Unknown(no_such_name(&segments("bark.cooldown").unwrap()));
assert_eq!(refusal.code(), ExitCode::NotFound);
assert!(refusal.message().contains("shep kv get"), "{refusal}");
assert!(refusal.message().contains("shep dogs"), "{refusal}");
}
#[test]
fn a_dog_with_no_section_says_its_settings_are_defaults() {
let refusal = ReadRefusal::Empty(all_defaults("metrics", &segments("metrics").unwrap()));
assert_eq!(refusal.code(), ExitCode::NotFound);
assert!(
refusal.message().starts_with("`metrics` is a dog"),
"{refusal}"
);
assert!(refusal.message().contains("default"), "{refusal}");
assert!(
refusal.message().contains("shep enable metrics"),
"{refusal}"
);
}
#[test]
fn a_path_inside_a_default_only_dog_names_the_dog_and_not_the_path() {
let refusal = ReadRefusal::Empty(all_defaults("metrics", &segments("metrics.bind").unwrap()));
assert!(
refusal.message().starts_with("`metrics` is a dog"),
"{refusal}"
);
assert!(
refusal
.message()
.contains("`metrics.bind` names nothing inside it"),
"{refusal}"
);
assert!(
refusal.message().contains("shep enable metrics"),
"{refusal}"
);
}
#[test]
fn a_dotted_dog_name_stays_one_name() {
let refusal = ReadRefusal::Empty(all_defaults(
"kelpie.worker",
&segments("kelpie.worker.token").unwrap(),
));
assert!(
refusal.message().starts_with("`kelpie.worker` is a dog"),
"{refusal}"
);
assert!(
refusal.message().contains("shep enable kelpie.worker`"),
"{refusal}"
);
assert!(
!refusal.message().contains("shep enable kelpie `"),
"{refusal}"
);
}
#[test]
fn an_empty_part_of_an_address_is_a_usage_error() {
assert!(matches!(
segments("api.dogs..kelpie"),
Err(ReadRefusal::Usage(_))
));
}
#[test]
fn a_field_no_sheep_has_is_refused_by_name() {
let written = sheep_written("api.watchdog");
let dogs = address::DogNames::new(&flock(), &config(), &Json::Null);
let Err(refusal) = sheep_steps(&written.name, &written.rest, &dogs, &config()) else {
panic!("`watchdog` is no field of a sheep's config");
};
assert_eq!(refusal.code(), ExitCode::Usage);
assert!(refusal.message().contains("`watchdog`"), "{refusal}");
}
#[test]
fn env_names_the_verbs_that_hold_a_sheeps_env() {
let written = sheep_written("api.env");
let dogs = address::DogNames::new(&flock(), &config(), &Json::Null);
let Err(refusal) = sheep_steps(&written.name, &written.rest, &dogs, &config()) else {
panic!("`env` is a refusal, not a read");
};
assert_eq!(refusal.code(), ExitCode::InvalidConfig);
assert!(refusal.message().contains("shep import env"), "{refusal}");
assert!(
refusal.message().contains("shep describe api"),
"the refusal has to name the sheep it refused, {refusal}"
);
assert!(!refusal.message().contains("{sheep}"), "{refusal}");
}
#[test]
fn a_path_nothing_is_set_at_is_not_found() {
let written = sheep_written("api.dogs.kelpie.git.remote");
let dogs = address::DogNames::new(&flock(), &config(), &Json::Null);
let steps = sheep_steps(&written.name, &written.rest, &dogs, &config()).unwrap();
let Err(refusal) = sheep_document(&written, &config(), &steps) else {
panic!("nothing holds `api.dogs.kelpie.git.remote`");
};
assert_eq!(refusal.code(), ExitCode::NotFound);
assert!(
refusal.message().contains("api.dogs.kelpie.git.remote"),
"{refusal}"
);
}
#[test]
fn an_unset_field_named_directly_is_not_found() {
let written = sheep_written("api.restart_delay");
let dogs = address::DogNames::new(&flock(), &config(), &Json::Null);
let steps = sheep_steps(&written.name, &written.rest, &dogs, &config()).unwrap();
assert!(matches!(
sheep_document(&written, &config(), &steps),
Err(ReadRefusal::Empty(_))
));
}
#[test]
fn a_dotted_table_header_of_a_dogs_section_stays_one_key_on_the_way_down() {
let section = "repo = \"/srv/project\"\n\n[\"api.token\"]\n\"auth.key\" = 42\n";
let doc: DocumentMut = section.parse().unwrap();
let written = dog_written("kelpie", &["api", "token", "auth", "key"]);
let steps = dog_steps(doc.as_table(), &written.rest);
assert_eq!(steps, ["api.token", "auth.key"], "{steps:?}");
assert_eq!(
dog_document(&written, &doc, &steps).unwrap(),
"[kelpie.\"api.token\"]\n\"auth.key\" = 42\n"
);
}
#[test]
fn a_dogs_own_section_prints_under_its_name_and_keeps_its_comments() {
let toml = dog_toml("kelpie", &[], SECTION);
assert!(toml.starts_with("[kelpie]\n"), "{toml}");
assert!(toml.contains("# what it may fetch"), "{toml}");
assert!(toml.contains("[kelpie.worker]"), "{toml}");
}
#[test]
fn one_key_of_a_dogs_section_prints_under_the_dog() {
let toml = dog_toml("kelpie", &["worker"], SECTION);
assert_eq!(
toml,
"[kelpie.worker]\nallowed_domains = [\"github.com\"]\ntimeout = 30\n"
);
}
#[test]
fn a_dotted_key_inside_a_dogs_section_reads_as_a_table() {
let toml = dog_toml("kelpie", &["api", "token"], SECTION);
assert_eq!(toml, "[kelpie.api]\ntoken = \"literal-dotted-key\"\n");
}
#[test]
fn a_key_no_dog_holds_is_not_found() {
let written = dog_written("kelpie", &["worker", "nope"]);
let doc: DocumentMut = SECTION.parse().unwrap();
let steps = dog_steps(doc.as_table(), &written.rest);
let Err(refusal) = dog_document(&written, &doc, &steps) else {
panic!("nothing holds `kelpie.worker.nope`");
};
assert_eq!(refusal.code(), ExitCode::NotFound);
}
const SECTION: &str = "\
# kelpie reads the repo it is adopted into
repo = \"/srv/project\"
api.token = \"literal-dotted-key\"
# what it may fetch
[worker]
allowed_domains = [\"github.com\"]
timeout = 30
";
fn dog_toml(name: &str, path: &[&str], section: &str) -> String {
let written = dog_written(name, path);
let doc: DocumentMut = section.parse().unwrap();
let steps = dog_steps(doc.as_table(), &written.rest);
dog_document(&written, &doc, &steps)
.unwrap_or_else(|err| panic!("{name}.{}: {err}", path.join(".")))
}