ci/verbs/tests/deps_update_verb.rs
//! The weekly dependency-update step (OSSF-NEXT): what a Renovate or Dependabot bot would
//! do, done by the dwarves forge. `ci-verbs deps-update` runs `cargo update` in every
//! workspace lock of a checkout, commits the moved locks (no trailer), and leaves a
//! proposal (`propose.json`) that the forge pushes as a NEW branch only after the same
//! run's CI step went green. Nothing is merged and nothing is force-pushed.
use std::path::PathBuf;
use ci_verbs::deps;
fn tmp(tag: &str) -> PathBuf {
let d = std::env::temp_dir().join(format!("ci-verbs-deps-{tag}-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&d);
std::fs::create_dir_all(&d).unwrap();
d
}
const BEFORE: &str = r#"version = 4
[[package]]
name = "russh"
version = "0.63.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "wasmtime"
version = "49.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "gunnar-core"
version = "2.0.0"
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
"#;
#[test]
fn the_locks_are_every_workspace_lock_but_fixtures_vendor_and_targets() {
let d = tmp("locks");
for p in [
"Cargo.lock",
"bench/Cargo.lock",
"crates/gunnar-front/Cargo.lock",
"fuzz/Cargo.lock",
"xtask/tests/fixtures/cve_vulnerable/Cargo.lock",
"vendor/gix-features/Cargo.lock",
"target/debug/x/Cargo.lock",
"crates/a/testdata/Cargo.lock",
] {
let f = d.join(p);
std::fs::create_dir_all(f.parent().unwrap()).unwrap();
std::fs::write(&f, "version = 4\n").unwrap();
}
let rel: Vec<String> = deps::lockfiles(&d).iter().map(|p| p.strip_prefix(&d).unwrap().display().to_string()).collect();
assert_eq!(rel, ["Cargo.lock", "bench/Cargo.lock", "crates/gunnar-front/Cargo.lock", "fuzz/Cargo.lock"]);
let _ = std::fs::remove_dir_all(d);
}
#[test]
fn the_bumps_name_each_crate_that_moved() {
let after = BEFORE.replace("0.63.3", "0.64.1").replace("49.0.4", "49.0.5").replace("0.8.5", "0.8.6");
let b = deps::bumps(&deps::lock_versions(BEFORE), &deps::lock_versions(&after));
let lines: Vec<String> = b.iter().map(|x| x.to_string()).collect();
assert_eq!(lines, ["rand 0.8.5 → 0.8.6", "russh 0.63.3 → 0.64.1", "wasmtime 49.0.4 → 49.0.5"]);
assert!(deps::bumps(&deps::lock_versions(BEFORE), &deps::lock_versions(BEFORE)).is_empty());
let added = format!("{BEFORE}\n[[package]]\nname = \"new\"\nversion = \"1.0.0\"\n");
let b = deps::bumps(&deps::lock_versions(BEFORE), &deps::lock_versions(&added));
assert_eq!(b.iter().map(|x| x.to_string()).collect::<Vec<_>>(), ["new (new) → 1.0.0"]);
}
#[test]
fn the_branch_is_new_each_time_and_the_message_carries_no_trailer() {
assert_eq!(deps::branch_name("20261102", "abcdef0123456789"), "deps/update-20261102-abcdef01");
let after = BEFORE.replace("49.0.4", "49.0.5");
let b = deps::bumps(&deps::lock_versions(BEFORE), &deps::lock_versions(&after));
let m = deps::commit_message(&[("Cargo.lock".into(), b)]);
assert!(m.starts_with("deps: cargo update (1 crate moves in 1 lock)"), "{m}");
assert!(m.contains("wasmtime 49.0.4 → 49.0.5"), "{m}");
assert!(!m.to_ascii_lowercase().contains("co-authored-by"), "no trailer of any kind: {m}");
assert!(!m.to_ascii_lowercase().contains("signed-off-by"), "{m}");
}
#[test]
fn the_verb_says_usage() {
assert_eq!(deps::verb(&[]), 2);
}