use pushkin_core::manifest::Manifest;
const BASE: &str = r#"
version = 1
canonical = "json-schema-2020-12"
authoring = "zod"
[gates]
"#;
fn parse(floor: &str) -> Result<Manifest, pushkin_core::manifest::ManifestError> {
Manifest::parse(&format!("{BASE}{floor}"))
}
const FULL: &str = r#"
[[floor.commands]]
name = "fmt"
run = ["cargo", "fmt", "--check"]
scope = "per_file"
inputs = "toolchain"
[[floor.commands]]
name = "test"
run = ["cargo", "test", "--workspace"]
scope = "whole_repo"
inputs = "repo"
reconcile_ignored = true
[[floor.commands]]
name = "bench"
run = ["cargo", "test", "--release", "-p", "pushkin"]
scope = "whole_repo"
inputs = "toolchain"
covers_ignored_of = "test"
[[floor.commands]]
name = "deny"
run = ["cargo", "deny", "check"]
scope = "whole_repo"
inputs = "network"
install = "cargo install --locked cargo-deny"
on_stop = true
"#;
#[test]
fn the_full_table_parses_and_preserves_declared_order() {
let manifest = parse(FULL).expect("full table must parse");
let floor = manifest.floor.as_ref().expect("[floor] must be present");
let names: Vec<&str> = floor.commands.iter().map(|c| c.name.as_str()).collect();
assert_eq!(names, vec!["fmt", "test", "bench", "deny"]);
}
#[test]
fn every_declared_field_round_trips() {
let manifest = parse(FULL).expect("full table must parse");
let floor = manifest.floor.as_ref().expect("[floor] must be present");
let fmt = &floor.commands[0];
assert_eq!(fmt.run, vec!["cargo", "fmt", "--check"]);
assert_eq!(fmt.scope, pushkin_core::manifest::FloorScope::PerFile);
assert_eq!(fmt.inputs, pushkin_core::manifest::FloorInputs::Toolchain);
assert!(fmt.install.is_none());
let deny = &floor.commands[3];
assert_eq!(deny.inputs, pushkin_core::manifest::FloorInputs::Network);
assert_eq!(
deny.install.as_deref(),
Some("cargo install --locked cargo-deny")
);
assert!(deny.on_stop);
}
#[test]
fn on_stop_and_reconcile_ignored_default_to_false() {
let manifest = parse(FULL).expect("full table must parse");
let floor = manifest.floor.as_ref().expect("[floor] must be present");
let fmt = &floor.commands[0];
assert!(!fmt.on_stop, "on_stop must default to false");
assert!(
!fmt.reconcile_ignored,
"reconcile_ignored must default to false"
);
assert!(floor.commands[1].reconcile_ignored);
}
#[test]
fn an_absent_floor_table_parses_because_the_verb_owns_that_error() {
let manifest = Manifest::parse(BASE).expect("a manifest without [floor] must parse");
assert!(manifest.floor.is_none());
}
#[test]
fn an_empty_commands_list_parses_and_is_the_verbs_problem_too() {
let manifest = parse("[floor]\ncommands = []\n").expect("empty commands must parse");
let floor = manifest.floor.as_ref().expect("[floor] must be present");
assert!(floor.commands.is_empty());
}
#[test]
fn a_missing_name_is_rejected_naming_the_field() {
let error = parse(
r#"
[[floor.commands]]
run = ["cargo", "fmt"]
scope = "whole_repo"
inputs = "repo"
"#,
)
.expect_err("a command without a name must be rejected");
assert!(
error.to_string().contains("name"),
"the error must name the missing field, got: {error}"
);
}
#[test]
fn a_missing_run_is_rejected_naming_the_field() {
let error = parse(
r#"
[[floor.commands]]
name = "fmt"
scope = "whole_repo"
inputs = "repo"
"#,
)
.expect_err("a command without run must be rejected");
assert!(
error.to_string().contains("run"),
"the error must name the missing field, got: {error}"
);
}
#[test]
fn a_missing_scope_is_rejected_naming_the_field() {
let error = parse(
r#"
[[floor.commands]]
name = "fmt"
run = ["cargo", "fmt"]
inputs = "repo"
"#,
)
.expect_err("a command without scope must be rejected");
assert!(
error.to_string().contains("scope"),
"the error must name the missing field, got: {error}"
);
}
#[test]
fn a_missing_inputs_is_rejected_naming_the_field() {
let error = parse(
r#"
[[floor.commands]]
name = "fmt"
run = ["cargo", "fmt"]
scope = "whole_repo"
"#,
)
.expect_err("a command without inputs must be rejected");
assert!(
error.to_string().contains("inputs"),
"the error must name the missing field, got: {error}"
);
}
#[test]
fn an_unknown_key_inside_a_command_is_rejected() {
let error = parse(
r#"
[[floor.commands]]
name = "fmt"
run = ["cargo", "fmt"]
scope = "whole_repo"
inputs = "repo"
timeout_ms = 5000
"#,
)
.expect_err("an unknown key must be rejected (deny_unknown_fields)");
assert!(
error.to_string().contains("timeout_ms"),
"the error must name the offending key, got: {error}"
);
}
#[test]
fn an_unknown_key_on_the_floor_table_is_rejected() {
let error = parse("[floor]\ncommands = []\nparallel = true\n")
.expect_err("an unknown key on [floor] must be rejected");
assert!(
error.to_string().contains("parallel"),
"the error must name the offending key, got: {error}"
);
}
#[test]
fn a_duplicate_name_is_rejected_naming_the_duplicate() {
let error = parse(
r#"
[[floor.commands]]
name = "fmt"
run = ["cargo", "fmt"]
scope = "whole_repo"
inputs = "repo"
[[floor.commands]]
name = "fmt"
run = ["cargo", "fmt", "--check"]
scope = "whole_repo"
inputs = "repo"
"#,
)
.expect_err("a duplicate command name must be rejected");
let message = error.to_string();
assert!(
message.contains("fmt") && message.contains("duplicate"),
"the error must name the duplicated entry, got: {message}"
);
}
#[test]
fn an_empty_run_array_is_rejected_naming_the_command() {
let error = parse(
r#"
[[floor.commands]]
name = "nothing"
run = []
scope = "whole_repo"
inputs = "repo"
"#,
)
.expect_err("an empty run array must be rejected");
let message = error.to_string();
assert!(
message.contains("nothing"),
"the error must name the offending entry, got: {message}"
);
}
#[test]
fn an_unknown_scope_value_is_rejected() {
let error = parse(
r#"
[[floor.commands]]
name = "fmt"
run = ["cargo", "fmt"]
scope = "per_line"
inputs = "repo"
"#,
)
.expect_err("an unknown scope must be rejected");
assert!(
error.to_string().contains("per_line"),
"the error must name the offending value, got: {error}"
);
}
#[test]
fn an_unknown_inputs_value_is_rejected() {
let error = parse(
r#"
[[floor.commands]]
name = "fmt"
run = ["cargo", "fmt"]
scope = "whole_repo"
inputs = "the_moon"
"#,
)
.expect_err("an unknown inputs class must be rejected");
assert!(
error.to_string().contains("the_moon"),
"the error must name the offending value, got: {error}"
);
}
#[test]
fn covers_ignored_of_naming_an_undeclared_command_is_rejected() {
let error = parse(
r#"
[[floor.commands]]
name = "bench"
run = ["cargo", "test", "--release"]
scope = "whole_repo"
inputs = "toolchain"
covers_ignored_of = "no_such_command"
"#,
)
.expect_err("a dangling covers_ignored_of must be rejected");
let message = error.to_string();
assert!(
message.contains("no_such_command"),
"the error must name the dangling reference, got: {message}"
);
}
#[test]
fn covers_ignored_of_resolves_regardless_of_declared_order() {
let manifest = parse(
r#"
[[floor.commands]]
name = "bench"
run = ["cargo", "test", "--release"]
scope = "whole_repo"
inputs = "toolchain"
covers_ignored_of = "test"
[[floor.commands]]
name = "test"
run = ["cargo", "test", "--workspace"]
scope = "whole_repo"
inputs = "repo"
reconcile_ignored = true
"#,
)
.expect("a backward reference must resolve");
let floor = manifest.floor.as_ref().expect("[floor] must be present");
assert_eq!(floor.commands[0].covers_ignored_of.as_deref(), Some("test"));
}
#[test]
fn a_command_may_not_cover_its_own_ignored_tests() {
let error = parse(
r#"
[[floor.commands]]
name = "test"
run = ["cargo", "test", "--workspace"]
scope = "whole_repo"
inputs = "repo"
reconcile_ignored = true
covers_ignored_of = "test"
"#,
)
.expect_err("self-coverage must be rejected");
assert!(
error.to_string().contains("test"),
"the error must name the offending entry, got: {error}"
);
}