#![allow(clippy::large_futures)]
#![allow(clippy::disallowed_methods)]
use std::fs;
use std::path::Path;
use monochange_test_helpers::git;
use monochange_test_helpers::git_output;
use serde_json::Value;
mod test_support;
use test_support::monochange_command;
use test_support::setup_scenario_workspace;
#[etest::etest(skip=std::env::var_os("PRE_COMMIT").is_some())]
fn prepare_release_records_git_changeset_context_and_renders_context_templates() {
let tempdir = setup_scenario_workspace("changeset-context/base");
let root = tempdir.path();
git(root, &["init"]);
git(root, &["config", "user.name", "monochange Tests"]);
git(
root,
&["config", "user.email", "monochange-tests@example.com"],
);
git(root, &["add", "Cargo.toml", "crates", "monochange.toml"]);
git(root, &["commit", "-m", "chore: seed release fixture"]);
git(root, &["add", ".changeset/feature.md"]);
git(root, &["commit", "-m", "feat: add changeset"]);
let introduced_sha = git_output(root, &["rev-parse", "HEAD"]).trim().to_string();
copy_updated_changeset(root);
git(root, &["add", ".changeset/feature.md"]);
git(root, &["commit", "-m", "docs: refine changeset details"]);
let updated_sha = git_output(root, &["rev-parse", "HEAD"]).trim().to_string();
let output = monochange_command(Some("2026-04-06"))
.current_dir(root)
.arg("run")
.arg("release")
.arg("--dry-run")
.output()
.unwrap_or_else(|error| panic!("release: {error}"));
assert!(
output.status.success(),
"release failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let manifest_path = root.join(".monochange/local/release-manifest.json");
let manifest = fs_read_to_string(&manifest_path);
let parsed: Value =
serde_json::from_str(&manifest).unwrap_or_else(|error| panic!("manifest json: {error}"));
assert_eq!(
parsed["changesets"][0]["path"].as_str(),
Some(".changeset/feature.md")
);
assert_eq!(
parsed["changesets"][0]["context"]["provider"].as_str(),
Some("generic_git")
);
assert_eq!(
parsed["changesets"][0]["context"]["introduced"]["commit"]["short_sha"].as_str(),
Some(&introduced_sha[..7])
);
assert_eq!(
parsed["changesets"][0]["context"]["last_updated"]["commit"]["short_sha"].as_str(),
Some(&updated_sha[..7])
);
let rendered = parsed["changelogs"][0]["rendered"]
.as_str()
.unwrap_or_else(|| panic!("expected rendered changelog"));
assert!(!rendered.contains("> _Changeset:_ `.changeset/feature.md`"));
assert!(rendered.contains(&introduced_sha[..7]));
assert!(rendered.contains(&updated_sha[..7]));
}
#[etest::etest(skip=std::env::var_os("PRE_COMMIT").is_some())]
fn diagnostics_command_reports_changeset_introduction_and_last_updated() {
let tempdir = setup_scenario_workspace("changeset-context/base");
let root = tempdir.path();
git(root, &["init"]);
git(root, &["config", "user.name", "monochange Tests"]);
git(
root,
&["config", "user.email", "monochange-tests@example.com"],
);
git(root, &["add", "Cargo.toml", "crates", "monochange.toml"]);
git(root, &["commit", "-m", "chore: seed release fixture"]);
git(root, &["add", ".changeset/feature.md"]);
git(root, &["commit", "-m", "feat: add changeset"]);
let introduced_sha = git_output(root, &["rev-parse", "HEAD"]).trim().to_string();
copy_updated_changeset(root);
git(root, &["add", ".changeset/feature.md"]);
git(root, &["commit", "-m", "docs: refine changeset details"]);
let updated_sha = git_output(root, &["rev-parse", "HEAD"]).trim().to_string();
let output = monochange_command(None)
.current_dir(root)
.arg("step")
.arg("diagnose-changesets")
.args(["--changeset", ".changeset/feature.md", "--format", "json"])
.output()
.unwrap_or_else(|error| panic!("diagnostics: {error}"));
assert!(
output.status.success(),
"diagnostics command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout)
.unwrap_or_else(|error| panic!("diagnostics output utf8: {error}"));
let parsed: Value =
serde_json::from_str(&stdout).unwrap_or_else(|error| panic!("diagnostics json: {error}"));
assert_eq!(
parsed["requested_changesets"][0].as_str(),
Some(".changeset/feature.md")
);
assert_eq!(
parsed["changesets"][0]["context"]["introduced"]["commit"]["short_sha"].as_str(),
Some(&introduced_sha[..7])
);
assert_eq!(
parsed["changesets"][0]["context"]["last_updated"]["commit"]["short_sha"].as_str(),
Some(&updated_sha[..7])
);
}
#[test]
fn diagnostics_command_reports_all_changesets_and_deduplicates_explicit_inputs() {
let tempdir = setup_scenario_workspace("changeset-context/base");
let root = tempdir.path();
let output = monochange_command(None)
.current_dir(root)
.arg("step")
.arg("diagnose-changesets")
.arg("--format")
.arg("json")
.output()
.unwrap_or_else(|error| panic!("diagnostics: {error}"));
assert!(
output.status.success(),
"diagnostics command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let parsed: Value = serde_json::from_slice(&output.stdout)
.unwrap_or_else(|error| panic!("diagnostics json: {error}"));
let requested = parsed["requested_changesets"]
.as_array()
.unwrap_or_else(|| panic!("requested changesets"));
assert_eq!(requested.len(), 2);
assert_eq!(requested[0].as_str(), Some(".changeset/feature.md"));
assert_eq!(requested[1].as_str(), Some(".changeset/performance.md"));
let duplicate_output = monochange_command(None)
.current_dir(root)
.arg("step")
.arg("diagnose-changesets")
.arg("--changeset")
.arg(".changeset/feature.md")
.arg("--changeset")
.arg(".changeset/feature.md")
.arg("--format")
.arg("json")
.output()
.unwrap_or_else(|error| panic!("diagnostics: {error}"));
assert!(
duplicate_output.status.success(),
"diagnostics command failed: {}",
String::from_utf8_lossy(&duplicate_output.stderr)
);
let duplicate_parsed: Value = serde_json::from_slice(&duplicate_output.stdout)
.unwrap_or_else(|error| panic!("diagnostics json: {error}"));
let duplicate_requested = duplicate_parsed["requested_changesets"]
.as_array()
.unwrap_or_else(|| panic!("requested changesets"));
assert_eq!(duplicate_requested.len(), 1);
assert_eq!(
duplicate_requested[0].as_str(),
Some(".changeset/feature.md")
);
}
fn copy_updated_changeset(root: &Path) {
let source = Path::new(env!("CARGO_MANIFEST_DIR")).join(
"../../fixtures/tests/changeset-context/with-updated-changeset/.changeset/feature.md",
);
fs::copy(source, root.join(".changeset/feature.md"))
.unwrap_or_else(|error| panic!("copy updated changeset: {error}"));
}
fn fs_read_to_string(path: &Path) -> String {
fs::read_to_string(path)
.unwrap_or_else(|error| panic!("read manifest {}: {error}", path.display()))
}