use serde_json::json;
use trusty_common::memory_core::palace::PalaceId;
fn test_state() -> crate::AppState {
trusty_common::memory_core::retrieval::seed_shared_embedder_with_mock();
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path().to_path_buf();
std::mem::forget(tmp);
unsafe {
std::env::set_var("TRUSTY_SKIP_PALACE_ENFORCEMENT", "1");
}
let state = crate::AppState::new(root);
state.set_ready();
state
}
#[test]
fn all_tools_returns_expected_set() {
let tools = super::tools::all_tools();
let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
assert_eq!(
names,
vec![
"list_palaces",
"get_palace",
"recall_memories",
"list_drawers",
"kg_query",
"get_config",
"get_status",
"get_dream_status",
"get_palace_dream_status",
"create_memory",
"kg_assert",
"memory_recall_all",
]
);
for t in &tools {
assert_eq!(
t.parameters["type"], "object",
"tool {} schema type",
t.name
);
assert!(
t.parameters["required"].is_array(),
"tool {} required not array",
t.name
);
}
}
#[tokio::test]
async fn execute_tool_dispatches_known_tools() {
let state = test_state();
let result = super::tools::execute_tool("list_palaces", "{}", &state).await;
assert!(
result.is_array(),
"list_palaces should be array, got {result}"
);
assert_eq!(result.as_array().unwrap().len(), 0);
let unknown = super::tools::execute_tool("not_a_tool", "{}", &state).await;
assert!(
unknown["error"]
.as_str()
.unwrap_or("")
.contains("unknown tool"),
"expected unknown-tool error, got {unknown}"
);
let missing = super::tools::execute_tool("get_palace", "{}", &state).await;
assert!(
missing["error"]
.as_str()
.unwrap_or("")
.contains("palace_id"),
"expected missing-arg error, got {missing}"
);
}
fn seed_palace(state: &crate::AppState, name: &str) {
let palace = trusty_common::memory_core::Palace {
id: PalaceId::new(name),
name: name.to_string(),
description: None,
created_at: chrono::Utc::now(),
data_dir: state.data_root.join(name),
};
state
.registry
.create_palace(&state.data_root, palace)
.expect("create palace");
}
#[tokio::test]
async fn chat_kg_assert_refreshes_prompt_cache() {
let state = test_state();
seed_palace(&state, "chatassert");
let args = json!({
"palace_id": "chatassert",
"subject": "masa",
"predicate": "has_convention",
"object": "always branch off a freshly fetched origin/main",
})
.to_string();
let result = super::tools::execute_tool("kg_assert", &args, &state).await;
assert_eq!(result["status"], "asserted", "got {result}");
let guard = state.prompt_context_cache.read().await;
assert!(
guard
.formatted
.contains("always branch off a freshly fetched origin/main"),
"chat kg_assert did not reach the prompt cache; got: {:?}",
guard.formatted
);
}
#[tokio::test]
async fn chat_kg_assert_reports_tier_s_refusal_without_writing() {
let state = test_state();
seed_palace(&state, "chatreject");
let over_long = "x".repeat(crate::prompt_facts::TIER_S_MAX_OBJECT_CHARS + 1);
let args = json!({
"palace_id": "chatreject",
"subject": "masa",
"predicate": "has_convention",
"object": over_long,
})
.to_string();
let result = super::tools::execute_tool("kg_assert", &args, &state).await;
let err = result["error"].as_str().unwrap_or_default();
assert!(
err.contains("Tier S fact rejected"),
"expected the Tier S refusal text, got {result}"
);
let handle = state
.registry
.get(&PalaceId::new("chatreject"))
.expect("palace handle");
let stored = handle.kg.query_active("masa").await.expect("query");
assert!(
stored.is_empty(),
"refused write reached storage: {stored:?}"
);
assert!(
state.prompt_context_cache.read().await.triples.is_empty(),
"refused write reached the prompt cache"
);
}