mod common;
use common::grex;
use std::fs;
use tempfile::TempDir;
const V1_1_1_LOCKFILE: &str = "\
{\"id\":\"alpha\",\"sha\":\"abc\",\"branch\":\"main\",\"installed_at\":\"2026-04-27T10:00:00Z\",\"actions_hash\":\"h\",\"schema_version\":\"1\"}
{\"id\":\"beta\",\"sha\":\"def\",\"branch\":\"main\",\"installed_at\":\"2026-04-27T10:00:00Z\",\"actions_hash\":\"h\",\"schema_version\":\"1\"}
";
fn seed_v1_1_1_lockfile(workspace: &std::path::Path) -> std::path::PathBuf {
let lock_dir = workspace.join(".grex");
fs::create_dir_all(&lock_dir).unwrap();
let lock_path = lock_dir.join("grex.lock.jsonl");
fs::write(&lock_path, V1_1_1_LOCKFILE).unwrap();
lock_path
}
#[test]
fn dry_run_on_legacy_lockfile_reports_and_does_not_write() {
let tmp = TempDir::new().unwrap();
let lock_path = seed_v1_1_1_lockfile(tmp.path());
let before = fs::read(&lock_path).unwrap();
let assertion = grex()
.args(["migrate-lockfile", "--dry-run", "--workspace"])
.arg(tmp.path())
.assert()
.success();
let stdout = String::from_utf8(assertion.get_output().stdout.clone()).unwrap();
assert!(
stdout.contains("would migrate"),
"dry-run output must announce a would-migrate; got: {stdout}",
);
let after = fs::read(&lock_path).unwrap();
assert_eq!(before, after, "--dry-run must not write the lockfile");
}
#[test]
fn wet_run_on_legacy_lockfile_rewrites_to_v1_2_0() {
let tmp = TempDir::new().unwrap();
let lock_path = seed_v1_1_1_lockfile(tmp.path());
let assertion =
grex().args(["migrate-lockfile", "--workspace"]).arg(tmp.path()).assert().success();
let stdout = String::from_utf8(assertion.get_output().stdout.clone()).unwrap();
assert!(
stdout.contains("migrated 2 entries"),
"wet-run output must report 2 migrated entries; got: {stdout}",
);
let raw = fs::read_to_string(&lock_path).unwrap();
assert!(raw.contains("\"path\":\"alpha\""), "post-migration bytes missing alpha path: {raw}");
assert!(raw.contains("\"path\":\"beta\""), "post-migration bytes missing beta path: {raw}");
}
#[test]
fn rerun_on_v1_2_0_lockfile_is_a_noop() {
let tmp = TempDir::new().unwrap();
let lock_path = seed_v1_1_1_lockfile(tmp.path());
grex().args(["migrate-lockfile", "--workspace"]).arg(tmp.path()).assert().success();
let after_first = fs::read(&lock_path).unwrap();
let assertion =
grex().args(["migrate-lockfile", "--workspace"]).arg(tmp.path()).assert().success();
let stdout = String::from_utf8(assertion.get_output().stdout.clone()).unwrap();
assert!(
stdout.contains("already on v1.2.0"),
"second run on migrated lockfile must announce no-op; got: {stdout}",
);
let after_second = fs::read(&lock_path).unwrap();
assert_eq!(after_first, after_second, "second run must not rewrite the lockfile bytes");
}
#[test]
fn no_lockfile_in_workspace_is_reported_and_exits_zero() {
let tmp = TempDir::new().unwrap();
let assertion =
grex().args(["migrate-lockfile", "--workspace"]).arg(tmp.path()).assert().success();
let stdout = String::from_utf8(assertion.get_output().stdout.clone()).unwrap();
assert!(
stdout.contains("no lockfile"),
"no-lockfile path must report empty state; got: {stdout}",
);
}
#[test]
fn help_text_lists_workspace_and_dry_run() {
let assertion = grex().args(["migrate-lockfile", "--help"]).assert().success();
let stdout = String::from_utf8(assertion.get_output().stdout.clone()).unwrap();
assert!(stdout.contains("--workspace"), "help must mention --workspace; got: {stdout}");
assert!(stdout.contains("--dry-run"), "help must mention --dry-run; got: {stdout}");
}