feat: integration test suite — 416 tests, 61% coverage
Add OpenBao and Kratos to docker-compose dev stack with bootstrap seeding. Full integration tests hitting real services: - Vault SDK: KV read/write/delete, re-auth on bad token, new_with_token constructor for dev mode - Kratos SDK: list/get/create/disable/enable users, session listing - Token store: PAT lifecycle with OpenBao backing, expiry handling - Identity tools: full tool dispatch through Kratos admin API - Gitea SDK: resolve_username, ensure_token (PAT auto-provisioning), list/get repos, issues, comments, branches, file content - Devtools: tool dispatch for all gitea_* tools against live Gitea - Archive indexer: batch flush, periodic flush task, edit/redact/reaction updates against OpenSearch - Memory store: set/query/get_recent with user scoping in OpenSearch - Room history: context retrieval by timestamp and event_id, access control enforcement - Search archive: keyword search with room/sender filters, room scoping - Code search: language filter, repo filter, branch scoping - Breadcrumbs: symbol retrieval, empty index handling, token budget - Bridge: full event lifecycle mapping, request ID filtering - Evaluator: DM/mention/silence short-circuits, LLM evaluation path, reply-to-human suppression - Agent registry: list/get_id, prompt reuse, prompt-change recreation - Conversations: token tracking, multi-turn context recall, room isolation Bug fixes caught by tests: - AgentRegistry in-memory cache skipped hash comparison on prompt change - KratosClient::set_state sent bare PUT without traits (400 error) - find_code_session returns None on NULL conversation_id
This commit is contained in:
@@ -59,9 +59,29 @@ impl AgentRegistry {
|
||||
let current_instructions = definitions::orchestrator_instructions(system_prompt, active_agents);
|
||||
let current_hash = instructions_hash(¤t_instructions);
|
||||
|
||||
// Check in-memory cache
|
||||
// Check in-memory cache — but verify instructions haven't changed
|
||||
if let Some(agent) = agents.get(&agent_name) {
|
||||
return Ok((agent.id.clone(), false));
|
||||
// Compare stored hash in SQLite against current hash
|
||||
if let Some((_id, stored_hash)) = self.store.get_agent(&agent_name) {
|
||||
if stored_hash == current_hash {
|
||||
return Ok((agent.id.clone(), false));
|
||||
}
|
||||
// Hash mismatch — prompt changed at runtime. Delete and recreate.
|
||||
info!(
|
||||
old_hash = stored_hash.as_str(),
|
||||
new_hash = current_hash.as_str(),
|
||||
"System prompt changed at runtime — recreating orchestrator agent"
|
||||
);
|
||||
let old_id = agent.id.clone();
|
||||
agents.remove(&agent_name);
|
||||
if let Err(e) = mistral.delete_agent_async(&old_id).await {
|
||||
warn!("Failed to delete stale orchestrator agent: {}", e.message);
|
||||
}
|
||||
self.store.delete_agent(&agent_name);
|
||||
} else {
|
||||
// In-memory but not in SQLite (shouldn't happen) — trust cache
|
||||
return Ok((agent.id.clone(), false));
|
||||
}
|
||||
}
|
||||
|
||||
// Check SQLite for persisted agent ID
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -237,9 +237,16 @@ impl KratosClient {
|
||||
|
||||
async fn set_state(&self, email_or_id: &str, state: &str) -> Result<Identity, String> {
|
||||
let id = self.resolve_id(email_or_id).await?;
|
||||
|
||||
// Fetch current identity first — PUT replaces the whole resource
|
||||
let current = self.get_user(&id).await?;
|
||||
let url = format!("{}/admin/identities/{}", self.admin_url, id);
|
||||
|
||||
let body = serde_json::json!({ "state": state });
|
||||
let body = serde_json::json!({
|
||||
"schema_id": "default",
|
||||
"state": state,
|
||||
"traits": current.traits,
|
||||
});
|
||||
let resp = self
|
||||
.http
|
||||
.put(&url)
|
||||
|
||||
@@ -47,6 +47,18 @@ impl VaultClient {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a VaultClient with a pre-set token (for dev mode / testing).
|
||||
/// Skips Kubernetes auth entirely.
|
||||
pub fn new_with_token(url: &str, kv_mount: &str, token: &str) -> Self {
|
||||
Self {
|
||||
url: url.trim_end_matches('/').to_string(),
|
||||
role: String::new(),
|
||||
kv_mount: kv_mount.to_string(),
|
||||
http: HttpClient::new(),
|
||||
token: Mutex::new(Some(token.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Authenticate with OpenBao via Kubernetes auth method.
|
||||
/// Reads the service account JWT from the mounted token file.
|
||||
async fn authenticate(&self) -> Result<String, String> {
|
||||
|
||||
Reference in New Issue
Block a user