#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use assert_cmd::Command;
use serde_json::Value;
use std::fs;
use std::path::Path;
use tempfile::TempDir;
struct World {
home: TempDir,
project: TempDir,
registry: TempDir,
}
impl World {
fn new() -> Self {
let project = TempDir::new().expect("project tempdir");
fs::create_dir_all(project.path().join(".ggen")).expect("create .ggen");
World {
home: TempDir::new().expect("home tempdir"),
project,
registry: TempDir::new().expect("registry tempdir"),
}
}
fn write_pack(&self, id: &str, version: &str, package: &str) {
let toml = format!(
r#"[pack]
id = "{id}"
name = "Agent Pack {id}"
version = "{version}"
description = "Real fixture for agent_lifecycle_test"
category = "test"
license = "MIT"
production_ready = true
packages = ["{package}"]
"#
);
fs::write(self.registry.path().join(format!("{id}.toml")), toml).expect("write pack toml");
}
fn lock_path(&self) -> std::path::PathBuf {
self.project.path().join(".ggen").join("packs.lock")
}
fn agent(&self) -> Command {
let mut cmd = Command::cargo_bin("ggen").expect("ggen binary");
cmd.arg("agent")
.current_dir(self.project.path())
.env("HOME", self.home.path())
.env("GGEN_PACKS_DIR", self.registry.path())
.env(
"GGEN_PACK_CACHE_DIR",
self.home.path().join(".ggen").join("packs"),
);
cmd
}
}
fn run_json(mut cmd: Command, args: &[&str]) -> Value {
for a in args {
cmd.arg(a);
}
cmd.arg("--format").arg("json");
let out = cmd.output().expect("run ggen agent");
let stdout = String::from_utf8_lossy(&out.stdout);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
out.status.success(),
"`ggen agent {args:?}` failed: status={:?}\nstdout={stdout}\nstderr={stderr}",
out.status.code(),
);
serde_json::from_str(stdout.trim())
.unwrap_or_else(|e| panic!("stdout is not JSON ({e}): {stdout}"))
}
#[test]
fn agi_completes_project_lifecycle_through_ggen_agent() {
let world = World::new();
world.write_pack("alpha", "1.2.3", "alpha-core");
world.write_pack("beta", "0.1.0", "beta-core");
let caps = run_json(world.agent(), &["capabilities"]);
let ops: Vec<&str> = caps["operations"]
.as_array()
.expect("operations array")
.iter()
.filter_map(|o| o["name"].as_str())
.collect();
for expected in [
"search",
"compatibility",
"install",
"status",
"verify",
"remove",
] {
assert!(
ops.contains(&expected),
"capabilities missing {expected}: {ops:?}"
);
}
let hits = run_json(world.agent(), &["search", "alpha"]);
assert!(
hits.as_array()
.expect("search array")
.iter()
.any(|h| h["pack"]["id"] == "alpha"),
"search must find alpha: {hits}"
);
let compat = run_json(world.agent(), &["compatibility", "alpha,beta"]);
assert_eq!(
compat["compatible"],
Value::Bool(true),
"alpha+beta must be compatible: {compat}"
);
let inst = run_json(world.agent(), &["install", "alpha"]);
assert_eq!(inst["dry_run"], Value::Bool(false));
assert!(
inst["digest"]
.as_str()
.map(|d| !d.is_empty())
.unwrap_or(false),
"real install must pin a digest: {inst}"
);
assert_eq!(inst["receipt"]["signature_present"], Value::Bool(true));
let receipt_path = inst["receipt"]["receipt_path"]
.as_str()
.expect("receipt path")
.to_string();
assert!(world.lock_path().exists(), "lockfile must exist on disk");
assert!(
Path::new(&receipt_path).exists(),
"receipt file must exist: {receipt_path}"
);
let status = run_json(world.agent(), &["status"]);
assert_eq!(status["lockfile_present"], Value::Bool(true));
assert!(
status["installed"]
.as_array()
.expect("installed array")
.iter()
.any(|p| p["pack_id"] == "alpha" && p["version"] == "1.2.3"),
"status must report alpha@1.2.3: {status}"
);
let verify = run_json(world.agent(), &["verify", &receipt_path]);
assert_eq!(
verify["is_valid"],
Value::Bool(true),
"emitted receipt must verify: {verify}"
);
let removed = run_json(world.agent(), &["remove", "alpha"]);
assert_eq!(removed["removed"], Value::Bool(true));
let status2 = run_json(world.agent(), &["status"]);
assert!(
!status2["installed"]
.as_array()
.expect("installed array")
.iter()
.any(|p| p["pack_id"] == "alpha"),
"alpha must be gone after remove: {status2}"
);
}
#[test]
fn agent_install_nonexistent_pack_is_fail_closed() {
let world = World::new(); let mut cmd = world.agent();
cmd.arg("install").arg("ghost").arg("--format").arg("json");
let out = cmd.output().expect("run");
assert!(
!out.status.success(),
"installing a nonexistent pack must fail loudly, got success"
);
assert!(
!world.lock_path().exists(),
"a failed install must not write a lockfile"
);
}
#[test]
fn agent_verify_tampered_receipt_is_invalid_via_cli() {
let world = World::new();
world.write_pack("alpha", "1.0.0", "alpha-core");
let inst = run_json(world.agent(), &["install", "alpha"]);
let receipt_path = inst["receipt"]["receipt_path"]
.as_str()
.expect("receipt path")
.to_string();
let mut body: Value = serde_json::from_slice(&fs::read(&receipt_path).unwrap()).unwrap();
body["signature"] = Value::String("00".repeat(64));
fs::write(&receipt_path, serde_json::to_vec_pretty(&body).unwrap()).unwrap();
let verify = run_json(world.agent(), &["verify", &receipt_path]);
assert_eq!(
verify["is_valid"],
Value::Bool(false),
"a tampered receipt must not verify through the CLI: {verify}"
);
}
#[test]
fn agent_install_dry_run_writes_no_durable_state() {
let world = World::new();
world.write_pack("alpha", "1.0.0", "alpha-core");
let inst = run_json(world.agent(), &["install", "alpha", "--dry_run", "true"]);
assert_eq!(inst["dry_run"], Value::Bool(true));
assert!(
inst["digest"]
.as_str()
.map(|d| d.is_empty())
.unwrap_or(true),
"dry run must pin no digest: {inst}"
);
assert!(
!world.lock_path().exists(),
"dry run must not write a lockfile"
);
}