use crate::common::Drip;
use serde_json::Value;
use std::fs;
use std::path::Path;
use std::process::{Command, Output};
fn drip_cmd(drip: &Drip, project: &Path, home: &Path, args: &[&str]) -> Output {
Command::new(&drip.bin)
.args(args)
.current_dir(project)
.env("HOME", home)
.env("DRIP_DATA_DIR", drip.data_dir.path())
.env("DRIP_SESSION_ID", &drip.session_id)
.env_remove("SHELL")
.output()
.expect("drip spawn")
}
#[test]
fn codex_uninstall_removes_mcp_and_agents_blocks() {
let drip = Drip::new();
let project = tempfile::tempdir().unwrap();
let home = tempfile::tempdir().unwrap();
let codex_dir = home.path().join(".codex");
fs::create_dir_all(&codex_dir).unwrap();
fs::write(
codex_dir.join("config.toml"),
"[user_section]\nkey = \"value\"\n",
)
.unwrap();
fs::write(
codex_dir.join("AGENTS.md"),
"# My personal notes\nbe careful with deletes\n",
)
.unwrap();
let o = drip_cmd(
&drip,
project.path(),
home.path(),
&["init", "--agent", "codex"],
);
assert!(
o.status.success(),
"init: {}",
String::from_utf8_lossy(&o.stderr)
);
let cfg = fs::read_to_string(codex_dir.join("config.toml")).unwrap();
assert!(cfg.contains("[mcp_servers.drip]"));
let md = fs::read_to_string(codex_dir.join("AGENTS.md")).unwrap();
assert!(md.contains("drip:agents-instructions"));
let o = drip_cmd(
&drip,
project.path(),
home.path(),
&["uninstall", "--agent", "codex"],
);
assert!(
o.status.success(),
"uninstall: {}",
String::from_utf8_lossy(&o.stderr)
);
let cfg = fs::read_to_string(codex_dir.join("config.toml")).unwrap();
assert!(
!cfg.contains("[mcp_servers.drip]"),
"DRIP block left behind: {cfg}"
);
assert!(
cfg.contains("[user_section]"),
"user section dropped: {cfg}"
);
assert!(cfg.contains("key = \"value\""));
let md = fs::read_to_string(codex_dir.join("AGENTS.md")).unwrap();
assert!(!md.contains("drip:agents-instructions"));
assert!(md.contains("# My personal notes"), "user notes lost: {md}");
assert!(md.contains("be careful with deletes"));
}
#[test]
fn codex_init_refreshes_stale_block_with_old_args() {
let drip = Drip::new();
let project = tempfile::tempdir().unwrap();
let home = tempfile::tempdir().unwrap();
let codex_dir = home.path().join(".codex");
fs::create_dir_all(&codex_dir).unwrap();
fs::write(
codex_dir.join("config.toml"),
"[user_section]\nkey = \"value\"\n\n\
[mcp_servers.drip]\n\
command = \"/old/path/to/drip\"\n\
args = [\"mcp\"]\n",
)
.unwrap();
let o = drip_cmd(
&drip,
project.path(),
home.path(),
&["init", "--agent", "codex"],
);
assert!(o.status.success(), "{}", String::from_utf8_lossy(&o.stderr));
let cfg = fs::read_to_string(codex_dir.join("config.toml")).unwrap();
assert!(
cfg.contains("--agent\", \"codex\""),
"init must refresh stale args, got:\n{cfg}"
);
assert!(
!cfg.contains("\"/old/path/to/drip\""),
"old command path must be replaced, got:\n{cfg}"
);
assert!(
cfg.contains("[user_section]"),
"user section dropped: {cfg}"
);
assert!(cfg.contains("key = \"value\""));
}
#[test]
fn codex_uninstall_is_idempotent() {
let drip = Drip::new();
let project = tempfile::tempdir().unwrap();
let home = tempfile::tempdir().unwrap();
let o1 = drip_cmd(
&drip,
project.path(),
home.path(),
&["uninstall", "--agent", "codex"],
);
assert!(o1.status.success());
let o2 = drip_cmd(
&drip,
project.path(),
home.path(),
&["uninstall", "--agent", "codex"],
);
assert!(o2.status.success());
}
#[test]
fn gemini_init_writes_settings_and_gemini_md() {
let drip = Drip::new();
let project = tempfile::tempdir().unwrap();
let home = tempfile::tempdir().unwrap();
let o = drip_cmd(
&drip,
project.path(),
home.path(),
&["init", "--global", "--agent", "gemini"],
);
assert!(o.status.success(), "{}", String::from_utf8_lossy(&o.stderr));
let settings = home.path().join(".gemini/settings.json");
let v: Value = serde_json::from_str(&fs::read_to_string(&settings).unwrap()).unwrap();
assert!(v["mcpServers"]["drip"]["command"].is_string());
assert_eq!(v["mcpServers"]["drip"]["args"][0].as_str(), Some("mcp"));
let md = home.path().join(".gemini/GEMINI.md");
assert!(md.exists());
assert!(fs::read_to_string(&md)
.unwrap()
.contains("drip:agents-instructions"));
}
#[test]
fn gemini_init_preserves_user_settings() {
let drip = Drip::new();
let project = tempfile::tempdir().unwrap();
let home = tempfile::tempdir().unwrap();
let gem_dir = home.path().join(".gemini");
fs::create_dir_all(&gem_dir).unwrap();
let pre = serde_json::json!({
"telemetry": false,
"mcpServers": {
"another": { "command": "/x/y", "args": [] }
}
});
fs::write(
gem_dir.join("settings.json"),
serde_json::to_string_pretty(&pre).unwrap(),
)
.unwrap();
drip_cmd(
&drip,
project.path(),
home.path(),
&["init", "--global", "--agent", "gemini"],
);
let v: Value =
serde_json::from_str(&fs::read_to_string(gem_dir.join("settings.json")).unwrap()).unwrap();
assert_eq!(v["telemetry"].as_bool(), Some(false));
assert!(v["mcpServers"]["drip"]["command"].is_string());
assert!(v["mcpServers"]["another"]["command"].is_string());
}
#[test]
fn gemini_uninstall_removes_drip_only() {
let drip = Drip::new();
let project = tempfile::tempdir().unwrap();
let home = tempfile::tempdir().unwrap();
let gem_dir = home.path().join(".gemini");
fs::create_dir_all(&gem_dir).unwrap();
fs::write(
gem_dir.join("settings.json"),
serde_json::to_string_pretty(&serde_json::json!({
"mcpServers": { "another": { "command": "/x/y", "args": [] } },
"telemetry": false
}))
.unwrap(),
)
.unwrap();
drip_cmd(
&drip,
project.path(),
home.path(),
&["init", "--global", "--agent", "gemini"],
);
drip_cmd(
&drip,
project.path(),
home.path(),
&["uninstall", "--global", "--agent", "gemini"],
);
let v: Value =
serde_json::from_str(&fs::read_to_string(gem_dir.join("settings.json")).unwrap()).unwrap();
assert!(v["mcpServers"].get("drip").is_none());
assert_eq!(v["mcpServers"]["another"]["command"].as_str(), Some("/x/y"));
assert_eq!(v["telemetry"].as_bool(), Some(false));
let md = fs::read_to_string(gem_dir.join("GEMINI.md")).unwrap();
assert!(!md.contains("drip:agents-instructions"));
}
#[test]
fn gemini_init_writes_agent_flag_in_args() {
let drip = Drip::new();
let project = tempfile::tempdir().unwrap();
let home = tempfile::tempdir().unwrap();
drip_cmd(
&drip,
project.path(),
home.path(),
&["init", "--global", "--agent", "gemini"],
);
let v: Value = serde_json::from_str(
&fs::read_to_string(home.path().join(".gemini/settings.json")).unwrap(),
)
.unwrap();
let strs: Vec<&str> = v["mcpServers"]["drip"]["args"]
.as_array()
.unwrap()
.iter()
.filter_map(|x| x.as_str())
.collect();
assert_eq!(strs, vec!["mcp", "--agent", "gemini"]);
}
#[test]
fn drip_agent_env_persists_to_session_row() {
let drip = Drip::new();
let dir = tempfile::tempdir().unwrap();
let f = dir.path().join("hello.txt");
fs::write(&f, "hi\n").unwrap();
let o = drip
.cmd()
.arg("read")
.arg(&f)
.env("DRIP_AGENT", "gemini")
.output()
.expect("drip read");
assert!(o.status.success(), "{}", String::from_utf8_lossy(&o.stderr));
let o = drip.cmd().arg("sessions").output().unwrap();
assert!(o.status.success());
let out = String::from_utf8_lossy(&o.stdout);
let row = out
.lines()
.find(|l| l.contains(&drip.session_id))
.unwrap_or_else(|| panic!("session row missing: {out}"));
assert!(
row.contains("Gemini"),
"expected Gemini agent label in row: {row}"
);
}
#[test]
fn drip_agent_unknown_value_falls_back_to_heuristic() {
let drip = Drip::new();
let dir = tempfile::tempdir().unwrap();
let f = dir.path().join("hello.txt");
fs::write(&f, "hi\n").unwrap();
drip.cmd()
.arg("read")
.arg(&f)
.env("DRIP_AGENT", "totally-not-an-agent")
.output()
.expect("drip read");
let o = drip.cmd().arg("sessions").output().unwrap();
let out = String::from_utf8_lossy(&o.stdout);
let row = out.lines().find(|l| l.contains(&drip.session_id)).unwrap();
assert!(
row.contains("custom"),
"expected fallback `custom` label, got: {row}"
);
}
#[test]
fn drip_mcp_agent_flag_sets_env_var_for_session() {
let drip = Drip::new();
let mut child = std::process::Command::new(&drip.bin)
.args(["mcp", "--agent", "codex"])
.env("DRIP_DATA_DIR", drip.data_dir.path())
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.expect("spawn drip mcp");
drop(child.stdin.take()); let o = child.wait_with_output().expect("wait");
assert!(
o.status.code().is_some(),
"drip mcp --agent crashed: stderr={}",
String::from_utf8_lossy(&o.stderr)
);
}
#[test]
fn malformed_mcp_json_fails_loudly_rather_than_clobbering() {
let drip = Drip::new();
let project = tempfile::tempdir().unwrap();
let home = tempfile::tempdir().unwrap();
let gem_dir = home.path().join(".gemini");
fs::create_dir_all(&gem_dir).unwrap();
let bad = "{ this is not json,, ";
fs::write(gem_dir.join("settings.json"), bad).unwrap();
let o = drip_cmd(
&drip,
project.path(),
home.path(),
&["init", "--global", "--agent", "gemini"],
);
assert!(!o.status.success(), "init should fail on malformed JSON");
let after = fs::read_to_string(gem_dir.join("settings.json")).unwrap();
assert_eq!(
after, bad,
"init clobbered malformed settings.json: {after:?}"
);
}