use std::collections::BTreeMap;
use crate::config::schema::{
AptInstall, CargoInstall, CheckSpec, ComponentDef, ConfigDocument, EnvironmentMutation,
GitInstall, InstallKind, InstallSpec, MutationScope, OriginMap, PipInstall, ProfileDef,
UvToolInstall, VariantDef, all_platforms, cargo_host_target_identity, release_profile,
validate_binary_providers, zero_success,
};
use crate::error::ForgeError;
use crate::util::{remove_process_var, set_process_var};
fn parse(input: &str) -> Result<ConfigDocument, ForgeError> {
ConfigDocument::parse_catalog(input)
}
fn minimal() -> ConfigDocument {
ConfigDocument {
profiles: BTreeMap::from([(
"default".to_string(),
ProfileDef {
inherits: Vec::new(),
components: vec!["cargo-audit".to_string()],
add: Vec::new(),
remove: Vec::new(),
},
)]),
components: vec![ComponentDef {
id: "cargo-audit".to_string(),
kind: InstallKind::Tool,
display_name: None,
version: None,
requires: Vec::new(),
provides: Vec::new(),
conflicts: Vec::new(),
optional: false,
allow_insecure_hosts: Vec::new(),
platforms: all_platforms(),
detect: Some(CheckSpec::Command {
program: "cargo".to_string(),
args: vec!["audit".to_string(), "--version".to_string()],
stdout_contains: None,
success_codes: zero_success(),
timeout_secs: Some(10),
}),
install: Some(InstallSpec::Cargo(CargoInstall {
crate_name: "cargo-audit".to_string(),
version: "=0.21.2".to_string(),
source: None,
revision: None,
locked: true,
features: Vec::new(),
bins: vec!["cargo-audit".to_string()],
target: None,
toolchain: None,
profile: release_profile(),
build_env_allow: Vec::new(),
})),
verify: None,
variants: Vec::new(),
source: None,
revision: None,
agents: Vec::new(),
}],
..ConfigDocument::default()
}
}
#[test]
fn component_insecure_hosts_are_configuration_only_values() {
let mut config = minimal();
config.components[0].install = Some(InstallSpec::UvTool(UvToolInstall {
package: "https://mirror.example.test/tool.whl".into(),
bins: vec!["tool".into()],
force: true,
index: None,
}));
config.components[0].allow_insecure_hosts = vec![
"*".into(),
"mirror.example.test".into(),
"redirect.example.test:8080".into(),
];
config.validate().unwrap();
config.components[0].allow_insecure_hosts = vec!["https://mirror.example.test/path".into()];
assert!(
config
.validate()
.unwrap_err()
.to_string()
.contains("without protocols, paths, or whitespace")
);
}
#[test]
fn insecure_hosts_reject_unsupported_backends_and_accept_pip_wildcards() {
let mut unsupported = minimal();
unsupported.components[0].allow_insecure_hosts = vec!["mirror.example.test".into()];
assert!(
unsupported
.validate()
.unwrap_err()
.to_string()
.contains("only supports the pip and uv-tool backends")
);
let mut pip = minimal();
pip.components[0].install = Some(InstallSpec::Pip(PipInstall {
package: "uv".into(),
version: "0.12.3".into(),
python: "python3".into(),
environment: "$BOT_FORGE_HOME/python-tools/uv".into(),
index: None,
}));
pip.components[0].allow_insecure_hosts = vec!["mirror.example.test".into()];
pip.validate().unwrap();
pip.components[0].allow_insecure_hosts = vec!["*".into()];
pip.validate().unwrap();
}
#[test]
fn insecure_hosts_validate_every_explicit_variant_backend() {
let mut config = minimal();
config.components[0].install = Some(InstallSpec::UvTool(UvToolInstall {
package: "https://mirror.example.test/tool.whl".into(),
bins: vec!["tool".into()],
force: true,
index: None,
}));
config.components[0].allow_insecure_hosts = vec!["mirror.example.test".into()];
config.components[0].variants.push(VariantDef {
id: "apt".into(),
version: None,
requires: Vec::new(),
provides: Vec::new(),
conflicts: Vec::new(),
platforms: vec!["linux-*".into()],
detect: None,
install: Some(InstallSpec::Apt(AptInstall {
packages: vec!["tool".into()],
update: false,
})),
verify: None,
});
assert!(
config
.validate()
.unwrap_err()
.to_string()
.contains("only supports the pip and uv-tool backends")
);
}
#[test]
fn accepts_strict_document() {
minimal().validate().unwrap();
}
#[test]
fn certificate_preflight_accepts_a_policy_gated_shell_installer() {
let document = ConfigDocument::parse(
r#"catalog = "rust-dev"
[policy]
allow_shell = true
[preflight.certificate]
platforms = ["linux-*"]
[preflight.certificate.detect]
kind = "path"
path = "/usr/local/share/ca-certificates/company.crt"
[preflight.certificate.install]
backend = "shell"
command = "install-company-certificate"
resources = ["network", "system-certificate-store"]
timeout_secs = 300
[profiles.default]
components = []
"#,
)
.unwrap();
assert!(matches!(
document
.preflight
.certificate
.as_ref()
.map(|certificate| &certificate.install),
Some(InstallSpec::Shell(_))
));
}
#[test]
fn certificate_preflight_rejects_non_shell_installers() {
let error = ConfigDocument::parse(
r#"catalog = "rust-dev"
[preflight.certificate]
[preflight.certificate.detect]
kind = "path"
path = "/usr/local/share/ca-certificates/company.crt"
[preflight.certificate.install]
backend = "apt"
packages = ["ca-certificates"]
[profiles.default]
components = []
"#,
)
.unwrap_err();
assert!(
error
.to_string()
.contains("preflight.certificate.install only supports the shell backend")
);
}
#[test]
fn rejects_removed_version_and_unknown_fields() {
assert!(ConfigDocument::parse("schema_version = 1").is_err());
assert!(ConfigDocument::parse("schema_version = 2\nunknown = true").is_err());
assert!(ConfigDocument::parse("[profiles.default]\ncomponents = []\n").is_err());
assert!(
ConfigDocument::parse("catalog = \"rust-dev\"\n[profiles.default]\ncomponents = []\n")
.is_ok()
);
}
#[test]
fn expands_cargo_tool_shorthand_to_canonical_component() {
let document = parse(
r#"[cargo-tools]
cargo-audit = "=0.21.2"
[profiles.default]
components = ["cargo-audit"]
"#,
)
.unwrap();
assert!(document.cargo_tools.is_empty());
let component = document.component("cargo-audit").unwrap();
let Some(CheckSpec::Command {
program,
args,
timeout_secs,
..
}) = &component.detect
else {
panic!("expected command detect")
};
assert_eq!(program, "cargo");
assert_eq!(args, &["audit", "--version"]);
assert_eq!(*timeout_secs, Some(10));
let Some(InstallSpec::Cargo(cargo)) = &component.install else {
panic!("expected cargo install")
};
assert_eq!(cargo.crate_name, "cargo-audit");
assert_eq!(cargo.bins, ["cargo-audit"]);
assert!(cargo.locked);
}
#[test]
fn detailed_cargo_shorthand_requires_explicit_nonstandard_binary() {
let document = parse(
r#"[cargo-tools.bindgen-cli]
version = "=0.72.1"
bin = "bindgen"
"#,
)
.unwrap();
let component = document.component("bindgen-cli").unwrap();
let Some(CheckSpec::Command { program, args, .. }) = &component.detect else {
panic!("expected command detect")
};
assert_eq!(program, "bindgen");
assert_eq!(args, &["--version"]);
}
#[test]
fn cargo_toolchain_is_used_for_detection_and_installation() {
let document = parse(
r#"[cargo-tools.cargo-audit]
version = "0.21.2"
toolchain = "1.90.0"
[profiles.default]
components = ["cargo-audit"]
"#,
)
.unwrap();
let component = document.component("cargo-audit").unwrap();
let Some(CheckSpec::Command { args, .. }) = &component.detect else {
panic!("expected command detect")
};
assert_eq!(args, &["+1.90.0", "audit", "--version"]);
let Some(InstallSpec::Cargo(cargo)) = &component.install else {
panic!("expected cargo install")
};
assert_eq!(cargo.toolchain.as_deref(), Some("1.90.0"));
}
#[test]
fn rejects_inexact_cargo_versions_and_duplicate_binaries() {
assert!(parse("[cargo-tools]\ncargo-audit = \"^0.21\"\n").is_err());
let mut config = minimal();
let mut duplicate = config.components[0].clone();
duplicate.id = "other".into();
config.components.push(duplicate);
assert!(
config
.validate()
.unwrap_err()
.to_string()
.contains("is provided by both")
);
}
#[test]
fn typed_backends_reject_missing_or_option_like_coordinates() {
let cargo_without_bins = r#"[[components]]
id = "cargo-tool"
[components.install]
backend = "cargo"
crate = "cargo-tool"
version = "=1.0.0"
"#;
assert!(
parse(cargo_without_bins)
.unwrap_err()
.to_string()
.contains("must declare bins")
);
for input in [
r#"[[components]]
id = "apt-tool"
[components.install]
backend = "apt"
packages = [""]
"#,
r#"[[components]]
id = "npm-tool"
[components.install]
backend = "npm"
package = "--help"
version = "1.0.0"
"#,
r#"[[components]]
id = "winget-tool"
[components.install]
backend = "winget"
package = "--help"
"#,
] {
assert!(parse(input).is_err());
}
}
#[test]
fn shorthand_and_full_component_have_identical_canonical_documents() {
let shorthand = parse(
r#"[cargo-tools]
cargo-audit = "=0.21.2"
"#,
)
.unwrap();
let full = parse(
r#"[[components]]
id = "cargo-audit"
[components.detect]
kind = "command"
program = "cargo"
args = ["audit", "--version"]
timeout_secs = 10
[components.install]
backend = "cargo"
crate = "cargo-audit"
version = "=0.21.2"
bins = ["cargo-audit"]
"#,
)
.unwrap();
assert_eq!(shorthand, full);
assert_eq!(
serde_json::to_vec(&shorthand).unwrap(),
serde_json::to_vec(&full).unwrap()
);
}
#[test]
fn package_tool_shorthand_matches_linux_macos_and_windows_components() {
let shorthand = parse(
r#"[package-tools.nodejs]
detect = ["node", "--version"]
apt = ["nodejs", "npm"]
apt_update = true
winget = "OpenJS.NodeJS.LTS"
brew = ["node"]
"#,
)
.unwrap();
let component = shorthand.component("nodejs").unwrap();
assert_eq!(component.platforms, ["linux-*"]);
let Some(InstallSpec::Apt(apt)) = &component.install else {
panic!("expected apt install")
};
assert_eq!(apt.packages, ["nodejs", "npm"]);
assert!(apt.update);
let macos = component
.variants
.iter()
.find(|variant| variant.platforms == ["macos-*"])
.unwrap();
let Some(InstallSpec::Brew(brew)) = &macos.install else {
panic!("expected brew install")
};
assert_eq!(brew.formulae, ["node"]);
let windows = component
.variants
.iter()
.find(|variant| variant.platforms == ["windows-*"])
.unwrap();
let Some(InstallSpec::Winget(winget)) = &windows.install else {
panic!("expected winget install")
};
assert_eq!(winget.package, "OpenJS.NodeJS.LTS");
}
#[test]
fn package_tool_shorthand_allows_a_macos_only_formula() {
let document = parse(
r#"[package-tools.pkgconf]
detect = ["pkgconf", "--version"]
brew = ["pkgconf"]
"#,
)
.unwrap();
let component = document
.components
.iter()
.find(|component| component.id == "pkgconf")
.unwrap();
assert_eq!(component.platforms, ["macos-*"]);
assert!(component.variants.is_empty());
let Some(InstallSpec::Brew(brew)) = &component.install else {
panic!("expected brew install")
};
assert_eq!(brew.formulae, ["pkgconf"]);
}
#[test]
fn cargo_toolset_defines_components_and_group_once() {
let document = parse(
r#"[cargo-toolsets.quality]
cargo-audit = "0.21.2"
cargo-deny = "0.18.5"
"#,
)
.unwrap();
assert!(document.cargo_toolsets.is_empty());
assert_eq!(document.groups["quality"], ["cargo-audit", "cargo-deny"]);
assert!(document.component("cargo-audit").is_some());
assert!(document.component("cargo-deny").is_some());
}
#[test]
fn rustup_and_environment_shorthands_expand_to_typed_contracts() {
let document = parse(
r#"[rustup-tools.miri]
components = ["miri"]
toolchain = "nightly"
detect = ["cargo", "+nightly", "miri", "--version"]
[environment.paths]
managed-bin = "$BOT_FORGE_HOME/bin"
"#,
)
.unwrap();
let component = document.component("miri").unwrap();
let Some(InstallSpec::Rustup(rustup)) = &component.install else {
panic!("expected rustup install")
};
assert_eq!(rustup.toolchain.as_deref(), Some("nightly"));
assert_eq!(rustup.components, ["miri"]);
assert_eq!(document.environment.mutations.len(), 1);
assert!(matches!(
&document.environment.mutations[0],
EnvironmentMutation::PathPrepend { id, value, scope: MutationScope::User, .. }
if id == "managed-bin" && value == "$BOT_FORGE_HOME/bin"
));
}
#[test]
fn rustup_default_requires_an_explicit_toolchain() {
assert!(
parse(
r#"[[components]]
id = "rustfmt"
[components.install]
backend = "rustup"
components = ["rustfmt"]
default = true
"#,
)
.unwrap_err()
.to_string()
.contains("must declare toolchain")
);
}
#[test]
fn rustup_bootstrap_requires_exact_platform_digests() {
let invalid = r#"[rustup-tools.toolchain]
components = ["rustfmt"]
[rustup-tools.toolchain.bootstrap]
url = "https://example.invalid/{target}/rustup-init{exe}"
[rustup-tools.toolchain.bootstrap.sha256]
"linux-*" = "0000000000000000000000000000000000000000000000000000000000000000"
"#;
assert!(parse(invalid).is_err());
let valid = r#"[rustup-tools.toolchain]
components = ["rustfmt"]
detect = [["rustup", "--version"], ["rustfmt", "--version"]]
[rustup-tools.toolchain.bootstrap]
url = "https://example.invalid/{target}/rustup-init{exe}"
[rustup-tools.toolchain.bootstrap.sha256]
linux-x86_64-gnu = "0000000000000000000000000000000000000000000000000000000000000000"
"#;
let document = parse(valid).unwrap();
assert!(matches!(
document.component("toolchain").unwrap().detect,
Some(CheckSpec::All { .. })
));
}
#[test]
fn package_shorthand_rejects_ambiguous_or_incomplete_fields() {
assert!(parse("[package-tools.demo]\ndetect = [\"demo\"]\n").is_err());
assert!(parse(
"[package-tools.demo]\ndetect = [\"demo\"]\napt = [\"demo\"]\nwinget_arguments = [\"--silent\"]\n"
)
.is_err());
}
#[test]
fn shorthand_overlay_replaces_component_and_tracks_expansion_origin() {
let mut base = minimal();
let mut origins = OriginMap::default();
base.merge_overlay_text(
"[cargo-tools]\ncargo-audit = \"0.22.0\"\n",
"site.toml",
&mut origins,
)
.unwrap();
let Some(InstallSpec::Cargo(cargo)) = &base.component("cargo-audit").unwrap().install else {
panic!("expected cargo install")
};
assert_eq!(cargo.version, "=0.22.0");
assert_eq!(
origins
.fields
.get("components.cargo-audit")
.map(String::as_str),
Some("site.toml (expanded from cargo-tools.cargo-audit)")
);
}
#[test]
fn groups_expand_deterministically_and_versions_bind_rustup() {
let mut document = parse(
r#"catalog = "rust-dev"
[versions]
rust = "1.90.0"
[groups]
quality = ["rust-toolchain"]
[[components]]
id = "rust-toolchain"
[components.install]
backend = "rustup"
toolchain-ref = "rust"
"#,
)
.unwrap();
assert_eq!(
document.expand_profile_entry("group:quality").unwrap(),
["rust-toolchain"]
);
document.resolve_version_references().unwrap();
let Some(InstallSpec::Rustup(rustup)) = &document.component("rust-toolchain").unwrap().install
else {
panic!("expected rustup install")
};
assert_eq!(rustup.toolchain.as_deref(), Some("1.90.0"));
assert!(rustup.toolchain_ref.is_none());
}
#[test]
fn versions_bind_rustup_variants() {
let mut document = parse(
r#"[versions]
rust = "1.90.0"
[[components]]
id = "rust-toolchain"
platforms = ["linux-*"]
[[components.variants]]
id = "windows"
platforms = ["windows-*"]
[components.variants.install]
backend = "rustup"
toolchain-ref = "rust"
components = ["rustfmt"]
"#,
)
.unwrap();
document.resolve_version_references().unwrap();
let Some(InstallSpec::Rustup(rustup)) =
&document.component("rust-toolchain").unwrap().variants[0].install
else {
panic!("expected rustup variant")
};
assert_eq!(rustup.toolchain.as_deref(), Some("1.90.0"));
assert!(rustup.toolchain_ref.is_none());
}
#[test]
fn rejects_duplicate_cargo_binary_declared_by_variant() {
let mut config = minimal();
let cargo = config.components[0].install.clone();
config.components.push(ComponentDef {
id: "other".into(),
kind: InstallKind::Tool,
display_name: None,
version: None,
requires: Vec::new(),
provides: Vec::new(),
conflicts: Vec::new(),
optional: false,
allow_insecure_hosts: Vec::new(),
platforms: vec!["linux-*".into()],
detect: None,
install: None,
verify: None,
variants: vec![VariantDef {
id: "windows".into(),
version: None,
requires: Vec::new(),
provides: Vec::new(),
conflicts: Vec::new(),
platforms: vec!["windows-*".into()],
detect: None,
install: cargo,
verify: None,
}],
source: None,
revision: None,
agents: Vec::new(),
});
assert!(
config
.validate()
.unwrap_err()
.to_string()
.contains("is provided by both")
);
}
#[test]
fn rejects_duplicate_managed_binary_across_cargo_and_git() {
let mut config = minimal();
config.components.push(ComponentDef {
id: "git-tool".into(),
kind: InstallKind::Tool,
display_name: None,
version: None,
requires: Vec::new(),
provides: Vec::new(),
conflicts: Vec::new(),
optional: false,
allow_insecure_hosts: Vec::new(),
platforms: all_platforms(),
detect: None,
install: Some(InstallSpec::Git(GitInstall {
url: "https://example.invalid/tool.git".into(),
revision: "0123456789abcdef".into(),
subdirectory: None,
bins: BTreeMap::from([("cargo-audit".into(), "bin/tool".into())]),
})),
verify: None,
variants: Vec::new(),
source: None,
revision: None,
agents: Vec::new(),
});
assert!(
config
.validate()
.unwrap_err()
.to_string()
.contains("is provided by both")
);
}
#[test]
fn rejects_case_colliding_managed_binaries_on_case_insensitive_platforms() {
let mut config = minimal();
let mut second = config.components[0].clone();
second.id = "other".into();
let Some(InstallSpec::Cargo(cargo)) = second.install.as_mut() else {
unreachable!()
};
cargo.bins = vec!["Cargo-Audit".into()];
config.components.push(second);
let result = validate_binary_providers(&config);
if cfg!(any(target_os = "macos", windows)) {
assert!(
result
.unwrap_err()
.to_string()
.contains("case-insensitive filesystem")
);
} else {
result.unwrap();
}
}
#[test]
fn rejects_duplicate_binary_within_one_cargo_install() {
let mut config = minimal();
let Some(InstallSpec::Cargo(cargo)) = config.components[0].install.as_mut() else {
panic!("expected cargo install")
};
cargo.bins.push("cargo-audit".into());
assert!(
config
.validate()
.unwrap_err()
.to_string()
.contains("declares managed binary")
);
}
#[test]
fn rejects_unknown_catalog_groups_and_unused_versions() {
assert!(ConfigDocument::parse("catalog = \"https://example.invalid/catalog.toml\"\n").is_err());
assert!(parse("[versions]\nrust = \"1.90.0\"\n").is_err());
assert!(parse("[groups]\nquality = [\"missing\"]\n").is_err());
}
#[test]
fn rejects_user_supplied_catalog_digest() {
assert!(ConfigDocument::parse("catalog-digest = \"forged\"\n").is_err());
let mut base = minimal();
assert!(
base.merge_overlay_text(
"catalog-digest = \"forged\"\n",
"user.toml",
&mut OriginMap::default(),
)
.unwrap_err()
.to_string()
.contains("main-configuration-only field")
);
}
#[test]
fn rejects_cycles_and_unresolved_capabilities() {
let mut config = minimal();
config.components[0].requires = vec!["component:cargo-audit".to_string()];
assert!(config.validate().unwrap_err().to_string().contains("cycle"));
config.components[0].requires = vec!["capability:missing".to_string()];
assert!(
config
.validate()
.unwrap_err()
.to_string()
.contains("without a provider")
);
}
#[test]
fn cargo_fingerprint_is_order_independent() {
let InstallSpec::Cargo(mut cargo) = minimal().components[0].install.clone().unwrap() else {
unreachable!()
};
cargo.features = vec!["z".to_string(), "a".to_string()];
cargo.build_env_allow = vec!["RUSTFLAGS".to_string(), "CC".to_string()];
let first = cargo.fingerprint("source", "lock");
cargo.features.reverse();
cargo.build_env_allow.reverse();
assert_eq!(first, cargo.fingerprint("source", "lock"));
}
#[test]
fn cargo_cache_is_shared_by_source_but_build_identity_stays_isolated() {
let first = CargoInstall {
crate_name: "cargo-a".into(),
version: "=1.0.0".into(),
source: None,
revision: None,
locked: true,
features: Vec::new(),
bins: vec!["cargo-a".into()],
target: None,
toolchain: None,
profile: "release".into(),
build_env_allow: Vec::new(),
};
let mut second = first.clone();
second.crate_name = "cargo-b".into();
second.bins = vec!["cargo-b".into()];
assert_eq!(first.cache_digest(), second.cache_digest());
assert_ne!(first.source_digest(), second.source_digest());
}
#[test]
fn cargo_fingerprint_covers_all_build_coordinates() {
let InstallSpec::Cargo(cargo) = minimal().components[0].install.clone().unwrap() else {
unreachable!()
};
let baseline = cargo.fingerprint("source", "lock");
type CargoMutation = Box<dyn Fn(&mut CargoInstall)>;
let mutations: Vec<CargoMutation> = vec![
Box::new(|value| value.crate_name.push_str("-other")),
Box::new(|value| value.version = "=9.9.9".into()),
Box::new(|value| value.source = Some("index+https://example.invalid/index".into())),
Box::new(|value| value.revision = Some("0123456789abcdef".into())),
Box::new(|value| value.locked = !value.locked),
Box::new(|value| value.target = Some("x86_64-unknown-linux-musl".into())),
Box::new(|value| value.toolchain = Some("nightly".into())),
Box::new(|value| value.profile = "dev".into()),
Box::new(|value| value.features.push("json".into())),
Box::new(|value| value.bins.push("second-bin".into())),
Box::new(|value| value.build_env_allow.push("RUSTFLAGS".into())),
];
for mutate in mutations {
let mut changed = cargo.clone();
mutate(&mut changed);
assert_ne!(baseline, changed.fingerprint("source", "lock"));
}
assert_ne!(baseline, cargo.fingerprint("other-source", "lock"));
assert_ne!(baseline, cargo.fingerprint("source", "other-lock"));
}
#[test]
fn implicit_cargo_target_uses_the_compilation_host_identity() {
let InstallSpec::Cargo(cargo) = minimal().components[0].install.clone().unwrap() else {
unreachable!()
};
let implicit = cargo.fingerprint("source", "lock");
let mut explicit = cargo;
explicit.target = Some(cargo_host_target_identity());
assert_eq!(implicit, explicit.fingerprint("source", "lock"));
}
#[test]
fn cargo_fingerprint_changes_with_the_resolved_compiler() {
let InstallSpec::Cargo(cargo) = minimal().components[0].install.clone().unwrap() else {
unreachable!()
};
let environment = cargo.build_environment_digest(true);
let first = cargo.fingerprint_with_build_identity("source", "lock", "rustc-a", &environment);
let second = cargo.fingerprint_with_build_identity("source", "lock", "rustc-b", &environment);
assert_ne!(first, second);
}
#[test]
fn planning_fingerprint_does_not_query_mutable_toolchain_state() {
let InstallSpec::Cargo(cargo) = minimal().components[0].install.clone().unwrap() else {
unreachable!()
};
assert_eq!(
cargo.fingerprint("source", "lock"),
cargo.fingerprint_with_build_identity(
"source",
"lock",
"planning",
&cargo.build_environment_digest(false),
)
);
}
#[test]
fn planning_fingerprint_does_not_read_mutable_environment_values() {
let InstallSpec::Cargo(mut cargo) = minimal().components[0].install.clone().unwrap() else {
unreachable!()
};
cargo.build_env_allow = vec!["BOT_FORGE_TEST_BUILD_INPUT".into()];
set_process_var("BOT_FORGE_TEST_BUILD_INPUT", "first");
let planning = cargo.fingerprint("source", "lock");
let artifact = cargo.artifact_fingerprint("source", "lock");
set_process_var("BOT_FORGE_TEST_BUILD_INPUT", "second");
assert_eq!(planning, cargo.fingerprint("source", "lock"));
assert_ne!(artifact, cargo.artifact_fingerprint("source", "lock"));
remove_process_var("BOT_FORGE_TEST_BUILD_INPUT");
}
#[test]
fn profile_modifiers_add_and_remove_expanded_components() {
let mut base = minimal();
let mut replacement = base.components[0].clone();
replacement.id = "cargo-deny".to_string();
let InstallSpec::Cargo(cargo) = replacement.install.as_mut().unwrap() else {
unreachable!()
};
cargo.crate_name = "cargo-deny".to_string();
cargo.bins = vec!["cargo-deny".to_string()];
let CheckSpec::Command { program, args, .. } = replacement.detect.as_mut().unwrap() else {
unreachable!()
};
*program = "cargo".to_string();
*args = vec!["deny".to_string(), "--version".to_string()];
base.components.push(replacement);
let mut origins = OriginMap::default();
base.merge_overlay_text(
"[profiles.default]\nadd = [\"cargo-deny\"]\nremove = [\"cargo-audit\"]\n",
"site.toml",
&mut origins,
)
.unwrap();
assert_eq!(base.profiles["default"].components, ["cargo-deny"]);
assert!(base.profiles["default"].inherits.is_empty());
assert!(base.profiles["default"].add.is_empty());
assert!(base.profiles["default"].remove.is_empty());
assert_eq!(
origins
.fields
.get("profiles.default.components")
.map(String::as_str),
Some("site.toml (expanded from profiles.default.add/remove)")
);
}
#[test]
fn profile_remove_rejects_unknown_effective_component() {
let mut base = minimal();
let error = base
.merge_overlay_text(
"[profiles.default]\nremove = [\"cargo-deny\"]\n",
"site.toml",
&mut OriginMap::default(),
)
.unwrap_err();
assert!(error.to_string().contains("outside the valid profile"));
}
#[test]
fn profile_modifiers_compose_across_ordered_layers() {
let mut base = minimal();
let mut cargo_deny = base.components[0].clone();
cargo_deny.id = "cargo-deny".into();
let InstallSpec::Cargo(cargo) = cargo_deny.install.as_mut().unwrap() else {
unreachable!()
};
cargo.crate_name = "cargo-deny".into();
cargo.bins = vec!["cargo-deny".into()];
let CheckSpec::Command { args, .. } = cargo_deny.detect.as_mut().unwrap() else {
unreachable!()
};
*args = vec!["deny".into(), "--version".into()];
base.components.push(cargo_deny);
let mut origins = OriginMap::default();
base.merge_overlay_text(
"[profiles.default]\nadd = [\"cargo-deny\"]\n",
"team.toml",
&mut origins,
)
.unwrap();
base.merge_overlay_text(
"[profiles.default]\nremove = [\"cargo-audit\"]\n",
"host.toml",
&mut origins,
)
.unwrap();
assert_eq!(base.profiles["default"].components, ["cargo-deny"]);
assert!(origins.fields["profiles.default.components"].starts_with("host.toml"));
}
#[test]
fn profile_modifier_expands_groups_and_same_layer_components() {
let mut base = minimal();
let mut origins = OriginMap::default();
base.merge_overlay_text(
r#"[groups]
extra = ["cargo-deny"]
[profiles.default]
add = ["group:extra"]
[[components]]
id = "cargo-deny"
[components.detect]
kind = "command"
program = "cargo"
args = ["deny", "--version"]
[components.install]
backend = "cargo"
crate = "cargo-deny"
version = "=0.18.5"
bins = ["cargo-deny"]
"#,
"site.toml",
&mut origins,
)
.unwrap();
assert_eq!(
base.profiles["default"].components,
["cargo-audit", "cargo-deny"]
);
}
#[test]
fn profile_modifier_rejects_ambiguous_same_layer_replacement() {
let mut base = minimal();
let error = base
.merge_overlay_text(
"[profiles.default]\ncomponents = [\"cargo-audit\"]\nadd = [\"cargo-audit\"]\n",
"site.toml",
&mut OriginMap::default(),
)
.unwrap_err();
assert!(
error
.to_string()
.contains("cannot mix add/remove with inherits/components")
);
}
#[test]
fn overlays_reject_primary_only_fields() {
for field in ["catalog = \"rust-dev\"\n", "catalog-digest = \"forged\"\n"] {
assert!(
minimal()
.merge_overlay_text(field, "site.toml", &mut OriginMap::default())
.is_err()
);
}
}
#[test]
fn explicit_check_defaults_match_omitted_defaults() {
let concise = parse(
"[[components]]\nid = \"demo\"\n[components.detect]\nkind = \"command\"\nprogram = \"demo\"\n",
)
.unwrap();
let explicit = parse(
"[[components]]\nid = \"demo\"\nplatforms = [\"*\"]\n[components.detect]\nkind = \"command\"\nprogram = \"demo\"\nargs = []\nsuccess_codes = [0]\ntimeout_secs = 10\n",
)
.unwrap();
assert_eq!(concise, explicit);
}
#[test]
fn named_sources_bind_to_backend_coordinates() {
let mut document = parse(
r#"[sources.cargo]
kind = "cargo-registry"
url = "https://packages.example/cargo/index"
[sources.npm]
kind = "npm-registry"
url = "https://packages.example/npm"
[sources.archives]
kind = "archive-mirror"
base_url = "https://packages.example/releases"
[[components]]
id = "cargo-tool"
[components.install]
backend = "cargo"
crate = "cargo-tool"
version = "=1.2.3"
source = "cargo"
bins = ["cargo-tool"]
[[components]]
id = "npm-tool"
[components.install]
backend = "npm"
package = "npm-tool"
version = "1.2.3"
source = "npm"
[[components]]
id = "archive-tool"
[components.install]
backend = "archive"
url = "source:archives/archive-tool.tar.gz"
sha256 = "0000000000000000000000000000000000000000000000000000000000000000"
target = "$BOT_FORGE_HOME/archive-tool"
"#,
)
.unwrap();
let mut origins = OriginMap::default();
document.resolve_source_references(&mut origins).unwrap();
let Some(InstallSpec::Cargo(cargo)) = &document.component("cargo-tool").unwrap().install else {
unreachable!()
};
assert_eq!(
cargo.source.as_deref(),
Some("index+https://packages.example/cargo/index")
);
let Some(InstallSpec::Npm(npm)) = &document.component("npm-tool").unwrap().install else {
unreachable!()
};
assert_eq!(npm.source.as_deref(), Some("https://packages.example/npm"));
let Some(InstallSpec::Archive(archive)) = &document.component("archive-tool").unwrap().install
else {
unreachable!()
};
assert_eq!(
archive.url,
"https://packages.example/releases/archive-tool.tar.gz"
);
assert_eq!(
origins.fields["components.archive-tool.install.url"],
"expanded from sources.archives"
);
}
#[test]
fn named_sources_reject_unused_and_wrong_kinds() {
let mut unused =
parse("[sources.unused]\nkind = \"npm-registry\"\nurl = \"https://example.invalid/npm\"\n")
.unwrap();
assert!(
unused
.resolve_source_references(&mut OriginMap::default())
.is_err()
);
let mut wrong = parse(
r#"[sources.registry]
kind = "npm-registry"
url = "https://example.invalid/npm"
[[components]]
id = "tool"
[components.install]
backend = "cargo"
crate = "tool"
version = "=1.0.0"
source = "registry"
bins = ["tool"]
"#,
)
.unwrap();
assert!(
wrong
.resolve_source_references(&mut OriginMap::default())
.is_err()
);
}
#[test]
fn cargo_shorthand_normalizes_plain_exact_version() {
let document = parse("[cargo-tools]\ncargo-audit = \"0.21.2\"\n").unwrap();
let Some(InstallSpec::Cargo(cargo)) = &document.component("cargo-audit").unwrap().install
else {
panic!("expected cargo install")
};
assert_eq!(cargo.version, "=0.21.2");
}
#[test]
fn rejects_unsafe_git_paths_and_file_archive_stripping() {
for input in [
r#"[[components]]
id = "git-tool"
[components.install]
backend = "git"
url = "https://example.invalid/tool.git"
revision = "0123456789abcdef"
subdirectory = "../outside"
bins = { tool = "bin/tool" }
"#,
r#"[[components]]
id = "archive-tool"
[components.install]
backend = "archive"
url = "https://example.invalid/tool"
sha256 = "0000000000000000000000000000000000000000000000000000000000000000"
format = "file"
target = "$BOT_FORGE_HOME/tool"
strip_components = 1
"#,
r#"[[components]]
id = "archive-tool"
[components.install]
backend = "archive"
url = "https://example.invalid/tool"
sha256 = "0000000000000000000000000000000000000000000000000000000000000000"
target = "$BOT_FORGE_HOME/../outside"
"#,
] {
assert!(parse(input).is_err());
}
}
#[test]
fn rejects_unknown_mutation_scope_and_escaping_fragments() {
let system_scope = r#"[[environment.mutations]]
kind = "variable"
id = "unsafe-system"
name = "DEMO"
value = "1"
scope = "system"
"#;
assert!(toml::from_str::<ConfigDocument>(system_scope).is_err());
let mutation = r#"kind = "file-fragment"
id = "escape"
path = "../outside"
lines = ["unsafe"]"#;
let source = format!("[[environment.mutations]]\n{mutation}\n");
assert!(parse(&source).is_err());
}
#[test]
fn policy_ranges_match_scheduler_capacities() {
for input in [
"[policy]\nmax_downloads = 0\n",
"[policy]\nmax_parallel = 257\n",
"[policy]\nmax_memory_mib = 511\n",
"[policy]\nmax_memory_mib = 4294967296\n",
] {
assert!(parse(input).is_err(), "accepted {input}");
}
}
#[test]
fn apt_mirror_rejects_incomplete_and_shadowed_rules_during_load() {
for input in [
"[apt_mirror]\nuri = \"https://apt.example/ubuntu\"\n",
"[apt_mirror]\nsuites = [\"{codename}\"]\ncomponents = [\"main\"]\n",
"[apt_mirror]\nuri = \"ftp://apt.example/ubuntu\"\nsuites = [\"stable\"]\ncomponents = [\"main\"]\n",
"[apt_mirror]\nuri = \"https://apt.example/ubuntu\"\nsuites = [\"{unknown}\"]\ncomponents = [\"main\"]\n",
"[apt_mirror]\nuri = \"https://apt.example/ubuntu\"\nsuites = [\"stable\"]\ncomponents = [\"main\"]\nsource_file = \"relative.sources\"\n",
r#"[apt_mirror]
suites = ["{codename}"]
components = ["main"]
[[apt_mirror.rules]]
uri = "https://all.example/ubuntu"
[[apt_mirror.rules]]
distribution = "ubuntu"
uri = "https://ubuntu.example/ubuntu"
"#,
] {
assert!(parse(input).is_err(), "accepted {input}");
}
}
#[test]
fn apt_mirror_accepts_complete_ordered_rules() {
let input = r#"[apt_mirror]
suites = ["{codename}"]
components = ["main"]
source_file = "/etc/apt/sources.list.d/bot-forge.sources"
[[apt_mirror.rules]]
distribution = "ubuntu"
architecture = "amd64"
uri = "https://amd64.example/ubuntu"
[[apt_mirror.rules]]
uri = "https://fallback.example/debian"
"#;
assert!(parse(input).is_ok());
}
#[test]
fn rejects_fields_that_do_not_belong_to_component_kind() {
let tool_with_skill_fields = r#"[[components]]
id = "tool"
source = "./skills"
agents = ["claude"]
"#;
assert!(parse(tool_with_skill_fields).is_err());
let skill_with_tool_fields = r#"[[components]]
id = "skill"
kind = "skill"
source = "./skills"
agents = ["claude"]
[components.detect]
kind = "command"
program = "true"
"#;
assert!(parse(skill_with_tool_fields).is_err());
}
#[test]
fn remote_skills_and_git_backends_require_fixed_commits() {
for input in [
r#"[[components]]
id = "skill"
kind = "skill"
source = "https://example.invalid/skills.git"
agents = ["claude"]
"#,
r#"[[components]]
id = "skill"
kind = "skill"
source = "ssh://example.invalid/skills.git"
revision = "0123456789abcdef"
agents = ["claude"]
"#,
r#"[[components]]
id = "git-tool"
[components.install]
backend = "git"
url = "https://example.invalid/tool.git"
revision = "main123"
bins = { tool = "bin/tool" }
"#,
] {
assert!(parse(input).is_err(), "accepted {input}");
}
let pinned = r#"[[components]]
id = "skill"
kind = "skill"
source = "https://example.invalid/skills.git"
revision = "0123456789abcdef"
agents = ["claude"]
"#;
assert!(parse(pinned).is_ok());
}