use std::fs;
use std::process::{Command, Output};
use serde_json::Value;
use tempfile::TempDir;
fn gf(project: &std::path::Path, args: &[&str]) -> Output {
Command::new(env!("CARGO_BIN_EXE_gf"))
.arg("--project")
.arg(project)
.args(args)
.output()
.expect("run same-build gf binary")
}
fn gf_repo(repository: &std::path::Path, args: &[&str]) -> Output {
Command::new(env!("CARGO_BIN_EXE_gf"))
.arg("--project-dir")
.arg(repository)
.args(args)
.output()
.expect("run same-build gf binary")
}
fn json(output: &Output) -> Value {
assert!(
output.status.success(),
"gf failed: {}",
String::from_utf8_lossy(&output.stderr)
);
serde_json::from_slice(&output.stdout).expect("stable JSON result")
}
#[test]
fn current_export_and_import_round_trip_with_stable_json() {
let source = TempDir::new().expect("source project parent");
let source_project = source.path().join("source");
fs::create_dir(&source_project).expect("source project directory");
let envelope = source.path().join("project.gfportable");
let exported = json(&gf(
&source_project,
&[
"--json",
"export",
"--current",
"--output",
envelope.to_str().unwrap(),
],
));
assert_eq!(exported["contract"], "graphforge-portable-export/1");
assert_eq!(exported["source"], "current");
assert!(exported["checkpoint"].is_null());
assert_eq!(exported["output"], envelope.to_str().unwrap());
assert_eq!(exported["envelope_sha256"].as_str().unwrap().len(), 64);
assert!(exported["participant_count"].as_u64().unwrap() > 0);
let destination_project = source.path().join("destination");
let imported = json(&gf(
&destination_project,
&[
"--json",
"import",
"--input",
envelope.to_str().unwrap(),
"--idempotency-key",
"00000000-0000-0000-0000-000000000229",
],
));
assert_eq!(imported["contract"], "graphforge-portable-import/1");
assert_eq!(
imported["source_generation_uuid"],
exported["generation_uuid"]
);
assert_eq!(imported["envelope_sha256"], exported["envelope_sha256"]);
assert_eq!(imported["idempotent_replay"], false);
let listed = gf(&destination_project, &["checkpoint", "list"]);
assert!(
listed.status.success(),
"reopen failed: {}",
String::from_utf8_lossy(&listed.stderr)
);
}
#[test]
fn checkpoint_export_reports_the_selected_checkpoint() {
let project = TempDir::new().expect("project parent");
let source_project = project.path().join("source");
fs::create_dir(&source_project).expect("source project directory");
let created = gf(
&source_project,
&[
"checkpoint",
"create",
"before-change",
"--idempotency-key",
"00000000-0000-0000-0000-000000000001",
],
);
assert!(created.status.success());
let envelope = project.path().join("checkpoint.gfportable");
let exported = json(&gf(
&source_project,
&[
"--json",
"export",
"--checkpoint",
"before-change",
"--output",
envelope.to_str().unwrap(),
],
));
assert_eq!(exported["source"], "checkpoint");
assert_eq!(exported["checkpoint"], "before-change");
}
#[test]
fn import_rejects_noncanonical_idempotency_key_as_stable_json() {
let project = TempDir::new().expect("project parent");
let output = gf(
&project.path().join("destination"),
&[
"--json",
"import",
"--input",
project.path().join("missing.gfportable").to_str().unwrap(),
"--idempotency-key",
"NOT-A-UUID",
],
);
assert_eq!(output.status.code(), Some(2));
let value: Value = serde_json::from_slice(&output.stderr).expect("JSON error");
assert_eq!(value["error"]["code"], "GF_VALIDATION");
assert_eq!(
value["error"]["message"],
"validation error: expected canonical UUID"
);
}
#[test]
fn initialized_repository_can_import_into_its_pristine_state() {
let repository = TempDir::new().expect("repository");
let initialized_git = Command::new("git")
.args(["init", "--quiet"])
.current_dir(repository.path())
.status()
.expect("initialize Git fixture");
assert!(initialized_git.success());
let initialized = gf_repo(repository.path(), &["init"]);
assert!(
initialized.status.success(),
"init failed: {}",
String::from_utf8_lossy(&initialized.stderr)
);
let envelope = repository
.path()
.join(".graphforge/exports/pristine.gfportable");
let exported = gf_repo(
repository.path(),
&[
"export",
"--current",
"--output",
envelope.to_str().unwrap(),
],
);
assert!(exported.status.success());
let imported = gf_repo(
repository.path(),
&[
"import",
"--input",
envelope.to_str().unwrap(),
"--idempotency-key",
"00000000-0000-0000-0000-000000000230",
],
);
assert!(
imported.status.success(),
"import failed: {}",
String::from_utf8_lossy(&imported.stderr)
);
}