use std::path::PathBuf;
fn repo_root() -> PathBuf {
let exe = std::env::current_exe().expect("resolve current test executable path");
let exe_dir = exe
.parent()
.expect("test executable should have a parent directory");
let profile_dir = if exe_dir.file_name() == Some(std::ffi::OsStr::new("deps")) {
exe_dir
.parent()
.expect("deps directory should have a parent directory")
} else {
exe_dir
};
profile_dir
.parent()
.expect("profile directory should have a parent (target)")
.parent()
.expect("target directory should have a parent (repo root)")
.to_path_buf()
}
fn read_repo_file(relative_path: &str) -> String {
std::fs::read_to_string(repo_root().join(relative_path))
.unwrap_or_else(|err| panic!("failed to read {relative_path}: {err}"))
}
#[test]
fn release_script_derives_repo_url_from_origin_remote() {
let script = read_repo_file("scripts/lib/release_verify_pipeline.sh");
assert!(
script.contains("ralph_get_repo_http_url"),
"release pipeline should derive the repo URL from git remote origin"
);
assert!(
!script.contains("https://github.com/fitchmultz/ralph/compare"),
"release pipeline should not hardcode compare links to a specific owner"
);
}
#[test]
fn release_notes_template_uses_repo_placeholders() {
let template = read_repo_file(".github/release-notes-template.md");
assert!(
template.contains("{{REPO_URL}}"),
"release-notes template should use repo URL placeholders"
);
assert!(
template.contains("{{REPO_CLONE_URL}}"),
"release-notes template should use clone URL placeholders"
);
}
#[test]
fn release_policy_treats_cargo_lock_as_release_metadata() {
let script = read_repo_file("scripts/lib/release_policy.sh");
assert!(
script.contains("\"Cargo.lock\""),
"release policy should treat Cargo.lock as release metadata"
);
}
#[test]
fn release_artifact_builder_cleans_release_artifacts_directory() {
let script = read_repo_file("scripts/build-release-artifacts.sh");
let cleanup_count = script.matches("rm -rf \"$RELEASE_ARTIFACTS_DIR\"").count();
assert!(
cleanup_count >= 1,
"artifact builder should clean target/release-artifacts before packaging"
);
}
#[test]
fn release_policy_uses_target_transaction_state() {
let script = read_repo_file("scripts/lib/release_state.sh");
assert!(
script.contains("target/release-transactions"),
"release state should keep release transaction state under target/release-transactions"
);
}
#[test]
fn release_script_publishes_only_after_local_release_is_prepared() {
let script = read_repo_file("scripts/lib/release_publish_pipeline.sh");
assert!(
read_repo_file("scripts/lib/release_verify_pipeline.sh")
.contains("release_prepare_verified_snapshot")
&& script.contains("release_create_commit_and_tag")
&& script.contains("release_create_or_update_github_release_draft")
&& script.contains("release_publish_crate"),
"release pipeline should verify locally before publishing externally"
);
assert!(
script.find("release_push_remote_main") < script.find("release_publish_crate"),
"release pipeline should push origin/main before crates.io publish"
);
assert!(
script.find("release_push_remote_tag") < script.find("release_publish_crate"),
"release pipeline should push the release tag before crates.io publish"
);
assert!(
script.find("release_create_or_update_github_release_draft")
< script.find("release_publish_crate"),
"release pipeline should draft the GitHub release before crates.io publish"
);
assert!(
script.find("release_publish_crate") < script.find("release_publish_github_release"),
"release pipeline should only publish the GitHub release after crates.io succeeds"
);
assert!(
script.contains("--draft") && script.contains("--draft=false"),
"release pipeline should split GitHub release draft creation from final publication"
);
}
#[test]
fn release_script_reconciles_without_legacy_skip_flags() {
let script = read_repo_file("scripts/release.sh");
assert!(
script.contains("scripts/release.sh reconcile")
|| script.contains("reconcile 0.2.0")
|| script.contains("reconcile <version>"),
"release.sh should document durable transaction-state reconciliation"
);
assert!(
!script.contains("RALPH_RELEASE_SKIP_PUBLISH"),
"release.sh should not rely on the old manual skip-publish recovery flag"
);
assert!(
!script.contains("RALPH_RELEASE_ALLOW_EXISTING_TAG"),
"release.sh should not retain the old existing-tag override flag"
);
}
#[test]
fn release_verify_allows_release_metadata_drift_after_version_sync() {
let script = read_repo_file("scripts/release.sh");
assert!(
script.contains("release_validate_repo_state 0 1"),
"execute mode should allow release-only metadata drift after verify prepares the publish snapshot"
);
}
#[test]
fn release_execute_initializes_transaction_after_validation() {
let script = read_repo_file("scripts/release.sh");
assert!(
script.find("release_validate_repo_state 0")
< script.find("release_state_init \"execute\""),
"execute mode should not create transaction state before repo-state validation succeeds"
);
}
#[test]
fn release_verify_records_a_reusable_snapshot() {
let script = read_repo_file("scripts/release.sh");
assert!(
script.contains("release_verify_state_init")
&& script.contains("release_prepare_verified_snapshot")
&& script.contains("release_verify_assert_ready_for_execute"),
"release.sh should prepare a durable verify snapshot and require it before execute"
);
}
#[test]
fn release_verify_state_uses_target_release_verifications_directory() {
let script = read_repo_file("scripts/lib/release_verify_state.sh");
assert!(
script.contains("target/release-verifications"),
"verified release state should live under target/release-verifications"
);
}