use super::*;
#[tokio::test]
async fn dispatch_kg_retract_triple_closes_one_object_and_keeps_siblings() {
let (state, _tmp) = test_state();
let _ = dispatch_tool(&state, "palace_create", json!({"name": "retract"}))
.await
.expect("palace_create");
for object in ["Acme", "Globex"] {
let _ = dispatch_tool(
&state,
"kg_assert",
json!({
"palace": "retract",
"subject": "alice",
"predicate": "works_at",
"object": object,
}),
)
.await
.expect("kg_assert");
}
let retracted = dispatch_tool(
&state,
"kg_retract_triple",
json!({
"palace": "retract",
"subject": "alice",
"predicate": "works_at",
"object": "Acme",
}),
)
.await
.expect("kg_retract_triple");
assert_eq!(retracted["closed"], 1);
assert_eq!(retracted["retracted"], true);
assert!(
retracted.get("reason").is_none(),
"a successful retraction carries no no-op reason: {retracted}"
);
let queried = dispatch_tool(
&state,
"kg_query",
json!({"palace": "retract", "subject": "alice"}),
)
.await
.expect("kg_query");
let triples = queried["triples"].as_array().expect("triples array");
assert_eq!(triples.len(), 1, "sibling object survived: {queried}");
assert_eq!(triples[0]["object"], "Globex");
}
#[tokio::test]
async fn dispatch_kg_retract_triple_missing_triple_is_a_legible_noop() {
let (state, _tmp) = test_state();
let _ = dispatch_tool(&state, "palace_create", json!({"name": "noop"}))
.await
.expect("palace_create");
let never_asserted = dispatch_tool(
&state,
"kg_retract_triple",
json!({
"palace": "noop",
"subject": "ghost",
"predicate": "works_at",
"object": "Nowhere",
}),
)
.await
.expect("kg_retract_triple on an absent triple is not an error");
assert_eq!(never_asserted["closed"], 0);
assert_eq!(never_asserted["retracted"], false);
assert!(
never_asserted["reason"].as_str().is_some(),
"the no-op says why: {never_asserted}"
);
let _ = dispatch_tool(
&state,
"kg_assert",
json!({
"palace": "noop",
"subject": "alice",
"predicate": "works_at",
"object": "Acme",
}),
)
.await
.expect("kg_assert");
let args = json!({
"palace": "noop",
"subject": "alice",
"predicate": "works_at",
"object": "Acme",
});
let first = dispatch_tool(&state, "kg_retract_triple", args.clone())
.await
.expect("first retract");
let second = dispatch_tool(&state, "kg_retract_triple", args)
.await
.expect("second retract");
assert_eq!(first["closed"], 1);
assert_eq!(second["closed"], 0, "retraction is idempotent: {second}");
}
#[tokio::test]
async fn dispatch_kg_retract_triple_requires_object() {
let (state, _tmp) = test_state();
let _ = dispatch_tool(&state, "palace_create", json!({"name": "strict"}))
.await
.expect("palace_create");
let err = dispatch_tool(
&state,
"kg_retract_triple",
json!({"palace": "strict", "subject": "alice", "predicate": "works_at"}),
)
.await
.expect_err("missing object must error");
assert!(
err.to_string().contains("missing 'object'"),
"unexpected error: {err}"
);
}
#[tokio::test]
async fn dispatch_kg_retract_triple_rebuilds_prompt_cache_for_hot_predicate() {
let (state, _tmp) = test_state();
let _ = dispatch_tool(&state, "palace_create", json!({"name": "hot"}))
.await
.expect("palace_create");
let _ = dispatch_tool(
&state,
"kg_assert",
json!({
"palace": "hot",
"subject": "msrv-rule",
"predicate": "is_fact",
"object": "MSRV is 1.94",
}),
)
.await
.expect("kg_assert");
let before = dispatch_tool(&state, "get_prompt_context", json!({}))
.await
.expect("get_prompt_context after assert");
let before_text = before.as_str().expect("string body");
assert!(
before_text.contains("MSRV is 1.94"),
"hot fact should be in the prompt cache before retraction: {before_text}"
);
let retracted = dispatch_tool(
&state,
"kg_retract_triple",
json!({
"palace": "hot",
"subject": "msrv-rule",
"predicate": "is_fact",
"object": "MSRV is 1.94",
}),
)
.await
.expect("kg_retract_triple");
assert_eq!(retracted["closed"], 1);
let after = dispatch_tool(&state, "get_prompt_context", json!({}))
.await
.expect("get_prompt_context after retract");
let after_text = after.as_str().expect("string body");
assert!(
!after_text.contains("MSRV is 1.94"),
"retracted hot fact must not still be injected into the prompt: {after_text}"
);
}