use anyhow::Result;
use clap::Args;
use serde_json::{Value, json};
use crate::cli::CliOutput;
use crate::storage as db;
#[derive(Args, Debug, Clone)]
pub struct DependentsOfInvalidatedArgs {
#[arg(long = "memory-id", value_name = "ID")]
pub memory_id: String,
#[arg(long)]
pub json: bool,
}
pub fn cmd_dependents_of_invalidated(
db_path: &std::path::Path,
args: &DependentsOfInvalidatedArgs,
out: &mut CliOutput<'_>,
) -> Result<()> {
let conn = db::open(db_path)?;
let params = json!({"memory_id": args.memory_id});
let envelope = crate::mcp::handle_dependents_of_invalidated(&conn, ¶ms)
.map_err(|e| anyhow::anyhow!("dependents-of-invalidated: {e}"))?;
if args.json {
writeln!(out.stdout, "{}", serde_json::to_string(&envelope)?)?;
return Ok(());
}
let count = envelope.get("count").and_then(Value::as_u64).unwrap_or(0);
writeln!(
out.stdout,
"dependents-of-invalidated: {count} dependent(s)"
)?;
if let Some(arr) = envelope.get("dependents").and_then(Value::as_array) {
for d in arr {
let id = d.get("id").and_then(Value::as_str).unwrap_or("?");
let ns = d.get("namespace").and_then(Value::as_str).unwrap_or("?");
writeln!(out.stdout, " {id} ns={ns}")?;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cli::test_utils::{TestEnv, seed_memory};
#[test]
fn dependents_of_invalidated_cli_text_output_with_dependents() {
let mut env = TestEnv::fresh();
let db = env.db_path.clone();
let target = seed_memory(&db, "ns", "invalidated-reflection", "content");
let dep = seed_memory(&db, "ns", "dependent-memory", "content");
{
let conn = db::open(&db).unwrap();
let now = chrono::Utc::now().to_rfc3339();
conn.execute(
"INSERT INTO memory_links (source_id, target_id, relation, created_at, valid_from)
VALUES (?1, ?2, 'reflects_on', ?3, ?3)",
rusqlite::params![dep, target, now],
)
.expect("insert reflects_on");
}
let args = DependentsOfInvalidatedArgs {
memory_id: target,
json: false,
};
{
let mut out = env.output();
cmd_dependents_of_invalidated(&db, &args, &mut out).expect("ok");
}
let stdout = env.stdout_str();
assert!(stdout.contains("1 dependent(s)"), "got: {stdout}");
assert!(stdout.contains("ns=ns"), "got: {stdout}");
assert!(stdout.contains(&dep), "got: {stdout}");
}
#[test]
fn dependents_of_invalidated_cli_empty_returns_zero() {
let mut env = TestEnv::fresh();
let db = env.db_path.clone();
let args = DependentsOfInvalidatedArgs {
memory_id: "nonexistent".into(),
json: true,
};
{
let mut out = env.output();
cmd_dependents_of_invalidated(&db, &args, &mut out).expect("ok");
}
let stdout = env.stdout_str();
let envelope: Value = serde_json::from_str(stdout.trim()).expect("parse envelope");
assert_eq!(envelope["count"].as_u64(), Some(0));
}
#[test]
fn dependents_of_invalidated_cli_empty_id_returns_err() {
let mut env = TestEnv::fresh();
let db = env.db_path.clone();
let args = DependentsOfInvalidatedArgs {
memory_id: String::new(),
json: true,
};
let mut out = env.output();
let err = cmd_dependents_of_invalidated(&db, &args, &mut out).expect_err("must fail");
assert!(
err.to_string().contains("dependents-of-invalidated"),
"got: {err}"
);
}
}