mod common;
use common::{TestResult, init_project, init_project_v1, run_commands};
use std::fs;
fn write_legacy_rfc_project(dir: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {
let rfc_dir = dir.join("gov/rfc/RFC-0001");
fs::create_dir_all(rfc_dir.join("clauses"))?;
fs::write(
rfc_dir.join("rfc.json"),
r#"{
"rfc_id": "RFC-0001",
"title": "Legacy RFC",
"version": "1.0.0",
"status": "normative",
"phase": "stable",
"owners": ["@test-user"],
"created": "2026-01-01",
"refs": [],
"sections": [
{
"title": "Specification",
"clauses": ["clauses/C-LEGACY.json"]
}
],
"changelog": [
{
"version": "1.0.0",
"date": "2026-01-01",
"added": ["Initial release"]
}
]
}"#,
)?;
fs::write(
rfc_dir.join("clauses/C-LEGACY.json"),
r#"{
"clause_id": "C-LEGACY",
"title": "Legacy Clause",
"kind": "normative",
"status": "active",
"text": "Legacy clause text.",
"since": "1.0.0"
}"#,
)?;
Ok(())
}
#[test]
fn test_migrate_rejects_legacy_json_storage() -> TestResult {
let temp_dir = init_project_v1()?;
write_legacy_rfc_project(temp_dir.path())?;
let output = run_commands(temp_dir.path(), &[&["migrate"]])?;
assert!(output.contains("error[E0505]"), "output: {}", output);
assert!(
output.contains("Use govctl <0.9 to run `govctl migrate` before upgrading."),
"output: {}",
output
);
let rfc_dir = temp_dir.path().join("gov/rfc/RFC-0001");
assert!(rfc_dir.join("rfc.json").exists());
assert!(rfc_dir.join("clauses/C-LEGACY.json").exists());
assert!(!rfc_dir.join("rfc.toml").exists());
assert!(!rfc_dir.join("clauses/C-LEGACY.toml").exists());
let config = fs::read_to_string(temp_dir.path().join("gov/config.toml"))?;
assert!(
config.contains("version = 1"),
"schema version should not be bumped on legacy JSON rejection: {}",
config
);
Ok(())
}
#[test]
fn test_migrate_dry_run_rejects_legacy_json_storage() -> TestResult {
let temp_dir = init_project_v1()?;
write_legacy_rfc_project(temp_dir.path())?;
let output = run_commands(temp_dir.path(), &[&["--dry-run", "migrate"]])?;
assert!(output.contains("error[E0505]"), "output: {}", output);
assert!(
output.contains("Use govctl <0.9 to run `govctl migrate` before upgrading."),
"output: {}",
output
);
assert!(
!output.contains("Would write: gov/rfc/RFC-0001/rfc.toml"),
"output: {}",
output
);
assert!(
!output.contains("Would delete: gov/rfc/RFC-0001/rfc.json"),
"output: {}",
output
);
let rfc_dir = temp_dir.path().join("gov/rfc/RFC-0001");
assert!(rfc_dir.join("rfc.json").exists());
assert!(rfc_dir.join("clauses/C-LEGACY.json").exists());
assert!(!rfc_dir.join("rfc.toml").exists());
assert!(!rfc_dir.join("clauses/C-LEGACY.toml").exists());
let config = fs::read_to_string(temp_dir.path().join("gov/config.toml"))?;
assert!(
config.contains("version = 1"),
"dry-run should not bump version: {}",
config
);
Ok(())
}
#[test]
fn test_migrate_dry_run_previews_config_version_bump_without_artifact_changes() -> TestResult {
let temp_dir = init_project_v1()?;
run_commands(temp_dir.path(), &[&["rfc", "new", "New Format RFC"]])?;
let output = run_commands(temp_dir.path(), &[&["--dry-run", "migrate"]])?;
assert!(
output.contains("Would write: gov/config.toml"),
"dry-run should preview config version bump as a file op: {output}"
);
let config = fs::read_to_string(temp_dir.path().join("gov/config.toml"))?;
assert!(
config.contains("version = 1"),
"dry-run should not bump version: {config}"
);
Ok(())
}
#[test]
fn test_migrate_is_noop_on_current_version() -> TestResult {
let temp_dir = init_project()?;
run_commands(
temp_dir.path(),
&[
&["rfc", "new", "Migrated RFC"],
&[
"clause",
"new",
"RFC-0001:C-SUMMARY",
"Summary",
"-s",
"Summary",
],
&["migrate"],
],
)?;
let output = run_commands(temp_dir.path(), &[&["migrate"]])?;
assert!(
output.contains("already at schema version 2"),
"output: {}",
output
);
Ok(())
}
#[test]
fn test_migrate_bumps_version_even_without_file_changes() -> TestResult {
let temp_dir = init_project_v1()?;
run_commands(temp_dir.path(), &[&["rfc", "new", "New Format RFC"]])?;
let config = fs::read_to_string(temp_dir.path().join("gov/config.toml"))?;
assert!(config.contains("version = 1"));
let output = run_commands(temp_dir.path(), &[&["migrate"]])?;
assert!(
output.contains("Schema version bumped to 2"),
"should bump version even with no file ops: {}",
output
);
let config = fs::read_to_string(temp_dir.path().join("gov/config.toml"))?;
assert!(
config.contains("version = 2"),
"config should now be version 2: {}",
config
);
Ok(())
}
#[test]
fn test_check_rejects_legacy_json_storage() -> TestResult {
let temp_dir = init_project_v1()?;
write_legacy_rfc_project(temp_dir.path())?;
let output = run_commands(temp_dir.path(), &[&["check"]])?;
assert!(output.contains("error[E0505]"), "output: {}", output);
assert!(
output.contains("Use govctl <0.9 to run `govctl migrate` before upgrading."),
"output: {}",
output
);
let rfc_dir = temp_dir.path().join("gov/rfc/RFC-0001");
assert!(rfc_dir.join("rfc.json").exists());
assert!(!rfc_dir.join("rfc.toml").exists());
let config = fs::read_to_string(temp_dir.path().join("gov/config.toml"))?;
assert!(
config.contains("version = 1"),
"version should not be bumped on failure: {}",
config
);
Ok(())
}