use crate::testing::{
setup_full_stack_test_agent, setup_full_stack_test_agent_with_extra_tools, setup_test_agent,
setup_test_agent_orchestrator, setup_test_agent_orchestrator_task_leads,
setup_test_agent_root, setup_test_agent_root_with_extra_tools_and_llm_timeout,
setup_test_agent_with_extra_tools_and_llm_timeout, setup_test_agent_with_models, MockProvider,
MockTool,
};
use crate::tools::{EditFileTool, ReadFileTool};
use crate::traits::store_prelude::*;
use crate::traits::{Goal, Procedure, ProviderResponse, StateStore, UserProfile};
use crate::types::{ChannelContext, ChannelVisibility, StatusUpdate, UserRole};
use chrono::Utc;
use serde_json::json;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
#[tokio::test]
async fn test_basic_message_response() {
let harness = setup_test_agent(MockProvider::new()).await.unwrap();
let response = harness
.agent
.handle_message(
"test_session",
"Hello!",
None,
UserRole::Owner,
ChannelContext::private("test"),
None,
)
.await
.unwrap();
assert_eq!(response, "Mock response");
assert_eq!(harness.provider.call_count().await, 1);
}
#[tokio::test]
async fn test_empty_llm_response_retried_then_fallback() {
let provider = MockProvider::with_responses(vec![
ProviderResponse {
content: Some(String::new()),
tool_calls: vec![],
usage: Some(crate::traits::TokenUsage {
input_tokens: 10,
output_tokens: 0,
cached_input_tokens: None,
cache_creation_input_tokens: None,
model: "mock".to_string(),
}),
thinking: None,
response_note: None,
},
ProviderResponse {
content: Some(String::new()),
tool_calls: vec![],
usage: Some(crate::traits::TokenUsage {
input_tokens: 20,
output_tokens: 0,
cached_input_tokens: None,
cache_creation_input_tokens: None,
model: "mock".to_string(),
}),
thinking: None,
response_note: None,
},
]);
let harness = setup_test_agent_with_models(provider, "primary-model", "smart-model")
.await
.unwrap();
let response = harness
.agent
.handle_message(
"test_session",
"Hello!",
None,
UserRole::Owner,
ChannelContext::private("test"),
None,
)
.await
.unwrap();
let expected = "I wasn't able to process that request. Could you try rephrasing?";
assert_eq!(harness.provider.call_count().await, 2);
assert_eq!(response, expected);
let calls = harness.provider.call_log.lock().await.clone();
assert!(calls.len() >= 2, "expected at least 2 LLM calls");
let retry_messages = &calls[1].messages;
let saw_retry_nudge = retry_messages.iter().any(|m| {
m.get("role").and_then(|r| r.as_str()) == Some("system")
&& m.get("content")
.and_then(|c| c.as_str())
.is_some_and(|c| c.contains("previous reply was empty"))
});
assert!(
saw_retry_nudge,
"expected retry nudge system message on second call"
);
}
#[tokio::test]
async fn test_tool_execution() {
let provider = MockProvider::with_responses(vec![
MockProvider::tool_call_response("system_info", "{}"),
MockProvider::text_response("Here is your system info."),
]);
let harness = setup_test_agent(provider).await.unwrap();
let response = harness
.agent
.handle_message(
"test_session",
"What's my system info?",
None,
UserRole::Owner,
ChannelContext::private("test"),
None,
)
.await
.unwrap();
assert_eq!(harness.provider.call_count().await, 2);
assert_eq!(response, "Here is your system info.");
}
#[tokio::test]
async fn test_semantic_file_read_cache_avoids_duplicate_and_overlapping_physical_reads() {
let mut file = tempfile::NamedTempFile::new().unwrap();
use std::io::Write;
writeln!(file, "alpha\nbeta\ngamma\ndelta\nepsilon\nzeta").unwrap();
let path = file.path().to_string_lossy().into_owned();
let first_args = json!({"path": path, "start_line": 1, "end_line": 3}).to_string();
let overlap_args = json!({"path": path, "start_line": 2, "end_line": 5}).to_string();
let provider = MockProvider::with_responses(vec![
MockProvider::tool_call_response("read_file", &first_args),
MockProvider::tool_call_response("read_file", &first_args),
MockProvider::tool_call_response("read_file", &overlap_args),
MockProvider::text_response("The cached resume section contains beta and gamma."),
]);
let harness = setup_test_agent_with_extra_tools_and_llm_timeout(
provider,
vec![Arc::new(ReadFileTool)],
None,
)
.await
.unwrap();
let response = harness
.agent
.handle_message(
"semantic_read_cache",
"Inspect the resume section and answer from the file.",
None,
UserRole::Owner,
ChannelContext::private("test"),
None,
)
.await
.unwrap();
assert_eq!(
response,
"The cached resume section contains beta and gamma."
);
let physical_reads: i64 = sqlx::query_scalar(
"SELECT COUNT(*) FROM events \
WHERE session_id = ? AND event_type = 'tool_call' \
AND json_extract(data, '$.name') = 'read_file'",
)
.bind("semantic_read_cache")
.fetch_one(&harness.state.pool())
.await
.unwrap();
assert_eq!(physical_reads, 1);
let calls = harness.provider.call_log.lock().await;
let all_tool_content = calls
.iter()
.flat_map(|call| call.messages.iter())
.filter(|message| message.get("role").and_then(|value| value.as_str()) == Some("tool"))
.filter_map(|message| message.get("content").and_then(|value| value.as_str()))
.collect::<Vec<_>>()
.join("\n");
assert!(all_tool_content.contains("complete task-local file artifact"));
assert!(all_tool_content.contains("Uncovered intervals: 4-5"));
}
#[tokio::test]
async fn test_llm_call_event_emitted_with_telemetry() {
let harness = setup_test_agent(MockProvider::new()).await.unwrap();
harness
.agent
.handle_message(
"llm_obs_session",
"Hello there",
None,
UserRole::Owner,
ChannelContext::private("test"),
None,
)
.await
.unwrap();
let since = Utc::now() - chrono::Duration::hours(1);
let event_store = crate::events::EventStore::new(harness.state.pool())
.await
.expect("event store from harness pool");
let stats = event_store
.get_llm_stats(since)
.await
.expect("llm stats should compute");
assert_eq!(stats.total_calls, 1, "expected one LlmCall event");
assert_eq!(stats.avg_input_tokens, 10);
assert_eq!(stats.avg_output_tokens, 5);
assert_eq!(stats.fell_back_count, 0);
assert!(stats.p95_latency_ms >= stats.p50_latency_ms);
}
#[tokio::test]
async fn test_harness_eval_snapshot_on_task_end() {
let harness = setup_test_agent(MockProvider::new()).await.unwrap();
harness
.agent
.handle_message(
"harness_eval_session",
"Hello there",
None,
UserRole::Owner,
ChannelContext::private("test"),
None,
)
.await
.unwrap();
let event_store = crate::events::EventStore::new(harness.state.pool())
.await
.expect("event store from harness pool");
let events = event_store
.query_recent_events("harness_eval_session", 50)
.await
.expect("recent events");
let task_end = events
.iter()
.find(|event| event.event_type == crate::events::EventType::TaskEnd)
.expect("task_end event");
let data = task_end
.parse_data::<crate::events::TaskEndData>()
.expect("task end payload");
let eval = data
.harness_eval
.as_ref()
.expect("harness_eval snapshot on TaskEnd");
assert_eq!(eval.scores.overall, eval.scores.overall.clamp(0.0, 1.0));
assert!(eval.scores.routing_accuracy >= 0.0);
assert!(eval.completion_task_kind.contains("conversational"));
}
#[tokio::test]
async fn test_memory_persistence() {
let harness = setup_test_agent(MockProvider::new()).await.unwrap();
harness
.agent
.handle_message(
"persist_session",
"Remember this",
None,
UserRole::Owner,
ChannelContext::private("test"),
None,
)
.await
.unwrap();
let history = harness
.state
.get_history("persist_session", 10)
.await
.unwrap();
assert!(
history.len() >= 2,
"Expected at least 2 messages, got {}",
history.len()
);
let roles: Vec<&str> = history.iter().map(|m| m.role.as_str()).collect();
assert!(roles.contains(&"user"), "Missing user message in history");
assert!(
roles.contains(&"assistant"),
"Missing assistant message in history"
);
}
#[tokio::test]
async fn test_multi_turn_conversation() {
let harness = setup_test_agent(MockProvider::new()).await.unwrap();
harness
.agent
.handle_message(
"multi_session",
"First message",
None,
UserRole::Owner,
ChannelContext::private("test"),
None,
)
.await
.unwrap();
harness
.agent
.handle_message(
"multi_session",
"Second message",
None,
UserRole::Owner,
ChannelContext::private("test"),
None,
)
.await
.unwrap();
let call_log = harness.provider.call_log.lock().await;
assert_eq!(call_log.len(), 2);
let first_msg_count = call_log[0].messages.len();
let second_msg_count = call_log[1].messages.len();
assert!(
second_msg_count > first_msg_count,
"Second call should include first turn's history: {} vs {}",
second_msg_count,
first_msg_count,
);
}
#[tokio::test]
async fn test_session_isolation() {
let harness = setup_test_agent(MockProvider::new()).await.unwrap();
harness
.agent
.handle_message(
"session_a",
"Message for A",
None,
UserRole::Owner,
ChannelContext::private("test"),
None,
)
.await
.unwrap();
harness
.agent
.handle_message(
"session_b",
"Message for B",
None,
UserRole::Owner,
ChannelContext::private("test"),
None,
)
.await
.unwrap();
let history_a = harness.state.get_history("session_a", 10).await.unwrap();
let history_b = harness.state.get_history("session_b", 10).await.unwrap();
let a_contents: Vec<String> = history_a.iter().filter_map(|m| m.content.clone()).collect();
let b_contents: Vec<String> = history_b.iter().filter_map(|m| m.content.clone()).collect();
assert!(
a_contents
.iter()
.any(|c: &String| c.contains("Message for A")),
"Session A missing its own message"
);
assert!(
!a_contents
.iter()
.any(|c: &String| c.contains("Message for B")),
"Session A contains session B's message"
);
assert!(
b_contents
.iter()
.any(|c: &String| c.contains("Message for B")),
"Session B missing its own message"
);
assert!(
!b_contents
.iter()
.any(|c: &String| c.contains("Message for A")),
"Session B contains session A's message"
);
}
#[tokio::test]
async fn test_owner_receives_tools_for_command_request() {
let provider = MockProvider::with_responses(vec![MockProvider::text_response(
"I've received your message. I'm currently processing it via my daemon core.",
)]);
let harness = setup_test_agent(provider).await.unwrap();
let response = harness
.agent
.handle_message(
"slack_session",
"can you run python3 --version",
None,
UserRole::Owner,
ChannelContext {
visibility: ChannelVisibility::Private,
platform: "slack".to_string(),
channel_name: None,
channel_id: None,
sender_name: None,
sender_id: None,
channel_member_names: vec![],
user_id_map: std::collections::HashMap::new(),
trusted: false,
},
None,
)
.await
.unwrap();
assert_eq!(
response,
"I've received your message. I'm currently processing it via my daemon core."
);
let call_log = harness.provider.call_log.lock().await;
assert_eq!(call_log.len(), 1);
assert!(
!call_log[0].tools.is_empty(),
"Owner user should have tools available, but tools were empty"
);
}
#[tokio::test]
async fn test_public_user_gets_no_tools() {
let harness = setup_test_agent(MockProvider::new()).await.unwrap();
let _response = harness
.agent
.handle_message(
"public_session",
"can you run python3 --version",
None,
UserRole::Public,
ChannelContext {
visibility: ChannelVisibility::Private,
platform: "slack".to_string(),
channel_name: None,
channel_id: None,
sender_name: None,
sender_id: None,
channel_member_names: vec![],
user_id_map: std::collections::HashMap::new(),
trusted: false,
},
None,
)
.await
.unwrap();
let call_log = harness.provider.call_log.lock().await;
assert_eq!(call_log.len(), 1);
assert!(
call_log[0].tools.is_empty(),
"Public user should have NO tools, but got: {:?}",
call_log[0].tools
);
}
#[tokio::test]
async fn test_command_request_with_tool_use() {
let provider = MockProvider::with_responses(vec![
MockProvider::tool_call_response("system_info", "{}"),
MockProvider::text_response("Python 3.11.5"),
]);
let harness = setup_test_agent(provider).await.unwrap();
let response = harness
.agent
.handle_message(
"slack_session",
"can you run python3 --version",
None,
UserRole::Owner,
ChannelContext {
visibility: ChannelVisibility::Private,
platform: "slack".to_string(),
channel_name: None,
channel_id: None,
sender_name: None,
sender_id: None,
channel_member_names: vec![],
user_id_map: std::collections::HashMap::new(),
trusted: false,
},
None,
)
.await
.unwrap();
assert_eq!(harness.provider.call_count().await, 2);
assert_eq!(response, "Python 3.11.5");
let call_log = harness.provider.call_log.lock().await;
let second_call_msgs = &call_log[1].messages;
let has_tool_result = second_call_msgs
.iter()
.any(|m| m.get("role").and_then(|r| r.as_str()) == Some("tool"));
assert!(
has_tool_result,
"Second LLM call should include tool result message"
);
}
#[tokio::test]
async fn test_slack_command_request_llm_payload() {
let harness = setup_test_agent(MockProvider::new()).await.unwrap();
let _response = harness
.agent
.handle_message(
"slack_diag",
"can you run python3 --version",
None,
UserRole::Owner,
ChannelContext {
visibility: ChannelVisibility::Private,
platform: "slack".to_string(),
channel_name: None,
channel_id: None,
sender_name: None,
sender_id: None,
channel_member_names: vec![],
user_id_map: std::collections::HashMap::new(),
trusted: false,
},
None,
)
.await
.unwrap();
let call_log = harness.provider.call_log.lock().await;
assert_eq!(call_log.len(), 1, "Expected exactly 1 LLM call");
let call = &call_log[0];
let tool_names: Vec<String> = call
.tools
.iter()
.filter_map(|t| {
t.get("function")
.and_then(|f| f.get("name"))
.and_then(|n| n.as_str())
.map(String::from)
})
.collect();
assert!(!tool_names.is_empty(), "No tools provided to LLM");
assert!(
tool_names.contains(&"system_info".to_string()),
"system_info tool missing from tool defs: {:?}",
tool_names
);
let system_msg = call
.messages
.iter()
.find(|m| m.get("role").and_then(|r| r.as_str()) == Some("system"));
assert!(system_msg.is_some(), "No system message found in LLM call");
let system_content = system_msg
.unwrap()
.get("content")
.and_then(|c| c.as_str())
.unwrap_or("");
assert!(
system_content.contains("[User Role: owner]"),
"System prompt missing Owner role tag. Got: ...{}...",
&system_content[system_content.len().saturating_sub(200)..]
);
assert!(
!system_content.contains("You have NO tools available"),
"System prompt incorrectly contains Public user restriction for Owner"
);
let user_msg = call
.messages
.iter()
.find(|m| m.get("role").and_then(|r| r.as_str()) == Some("user"));
assert!(user_msg.is_some(), "No user message found in LLM call");
let user_content = user_msg
.unwrap()
.get("content")
.and_then(|c| c.as_str())
.unwrap_or("");
assert!(
user_content.contains("python3 --version"),
"User message doesn't contain the original request: {}",
user_content
);
}
#[tokio::test]
async fn test_gemini_no_grounding_with_function_tools() {
use serde_json::json;
let provider = crate::providers::GoogleGenAiProvider::new("fake-key");
let tools = vec![json!({
"type": "function",
"function": {
"name": "terminal",
"description": "Run a command",
"parameters": {
"type": "object",
"properties": {
"command": { "type": "string" }
}
}
}
})];
let converted = provider.convert_tools_for_test(&tools, false);
assert!(converted.is_some(), "Should have tools");
let tools_array = converted.unwrap();
assert_eq!(
tools_array.len(),
1,
"Expected only function_declarations, got: {:?}",
tools_array
);
assert!(
tools_array[0].get("function_declarations").is_some(),
"Missing function_declarations"
);
assert!(
tools_array[0].get("google_search").is_none(),
"google_search should NOT be present alongside function tools"
);
let converted_grounding = provider.convert_tools_for_test(&[], true);
assert!(converted_grounding.is_some());
let grounding_array = converted_grounding.unwrap();
assert_eq!(grounding_array.len(), 1);
assert!(
grounding_array[0].get("google_search").is_some(),
"google_search should be present when no function tools"
);
}
#[tokio::test]
async fn test_guest_user_has_no_tools_with_owner_only_warning() {
let harness = setup_test_agent(MockProvider::new()).await.unwrap();
let _response = harness
.agent
.handle_message(
"guest_session",
"run some command",
None,
UserRole::Guest,
ChannelContext::private("telegram"),
None,
)
.await
.unwrap();
let call_log = harness.provider.call_log.lock().await;
assert_eq!(call_log.len(), 1);
assert!(
call_log[0].tools.is_empty(),
"Guest user should not have tools available"
);
let system_msg = call_log[0]
.messages
.iter()
.find(|m| m.get("role").and_then(|r| r.as_str()) == Some("system"));
let system_content = system_msg
.unwrap()
.get("content")
.and_then(|c| c.as_str())
.unwrap_or("");
assert!(
system_content.contains("[User Role: guest]"),
"System prompt missing Guest role tag"
);
assert!(
system_content.contains("Tool access is owner-only"),
"System prompt missing owner-only tool-access warning"
);
}
#[tokio::test]
async fn test_owner_system_prompt_has_no_restrictions() {
let harness = setup_test_agent(MockProvider::new()).await.unwrap();
let _response = harness
.agent
.handle_message(
"owner_session",
"hello",
None,
UserRole::Owner,
ChannelContext::private("telegram"),
None,
)
.await
.unwrap();
let call_log = harness.provider.call_log.lock().await;
let system_msg = call_log[0]
.messages
.iter()
.find(|m| m.get("role").and_then(|r| r.as_str()) == Some("system"));
let system_content = system_msg
.unwrap()
.get("content")
.and_then(|c| c.as_str())
.unwrap_or("");
assert!(
system_content.contains("[User Role: owner]"),
"System prompt missing Owner role tag"
);
assert!(
!system_content.contains("current user is a guest"),
"Owner should NOT have guest warning"
);
assert!(
!system_content.contains("NO tools available"),
"Owner should NOT have public restriction"
);
}
#[tokio::test]
async fn test_system_prompt_pins_critical_facts_for_owner_dm() {
let mut harness = setup_test_agent(MockProvider::new()).await.unwrap();
harness.agent.set_test_orchestrator_mode();
harness
.state
.upsert_fact(
"user",
"name",
"Test Owner",
"owner",
None,
crate::types::FactPrivacy::Global,
)
.await
.unwrap();
harness
.state
.upsert_fact(
"assistant",
"bot_name",
"TestBot",
"owner",
None,
crate::types::FactPrivacy::Global,
)
.await
.unwrap();
harness
.state
.upsert_fact(
"user",
"spouse",
"Alice",
"owner",
None,
crate::types::FactPrivacy::Global,
)
.await
.unwrap();
let _response = harness
.agent
.handle_message(
"owner_session",
"Give me a quick recap.",
None,
UserRole::Owner,
ChannelContext::private("telegram"),
None,
)
.await
.unwrap();
let call_log = harness.provider.call_log.lock().await;
let system_content: String = call_log[0]
.messages
.iter()
.filter(|m| m.get("role").and_then(|r| r.as_str()) == Some("system"))
.filter_map(|m| m.get("content").and_then(|c| c.as_str()))
.collect::<Vec<_>>()
.join("\n");
let system_content = system_content.as_str();
assert!(
system_content.contains("CRITICAL FACTS"),
"Critical facts block should be injected in owner DM prompts. Got: {}",
&system_content[..system_content.len().min(300)]
);
assert!(
system_content.contains("Test Owner"),
"Owner name should be present in critical facts"
);
assert!(
system_content.contains("TestBot"),
"Assistant name should be present in critical facts"
);
assert!(
system_content.contains("partner: Alice"),
"Relationship should be present in critical facts"
);
}
fn simulate_telegram_auth(
allowed_user_ids: &mut Vec<u64>,
owner_user_ids: &[u64],
user_id: u64,
) -> Option<UserRole> {
use crate::channels::telegram::{check_auth, determine_role, AuthResult};
let auth = if allowed_user_ids.is_empty() {
check_auth(allowed_user_ids, user_id)
} else if allowed_user_ids.contains(&user_id) {
AuthResult::Authorized
} else {
AuthResult::Unauthorized
};
match auth {
AuthResult::Unauthorized => None,
AuthResult::Authorized | AuthResult::AutoClaimed => {
Some(determine_role(owner_user_ids, user_id))
}
}
}
fn simulate_slack_auth(
allowed_user_ids: &[String],
user_id: &str,
is_dm: bool,
) -> Option<UserRole> {
let is_whitelisted =
!allowed_user_ids.is_empty() && allowed_user_ids.contains(&user_id.to_string());
if is_whitelisted {
Some(UserRole::Owner)
} else if is_dm {
Some(UserRole::Public)
} else {
None }
}
fn simulate_discord_auth() -> Option<UserRole> {
Some(UserRole::Owner)
}
#[tokio::test]
async fn test_telegram_first_user_auto_claim() {
let mut allowed: Vec<u64> = vec![];
let owner_ids: Vec<u64> = vec![];
let role = simulate_telegram_auth(&mut allowed, &owner_ids, 12345);
assert_eq!(
role,
Some(UserRole::Owner),
"First user should be auto-claimed as Owner"
);
assert_eq!(
allowed,
vec![12345],
"User ID should be persisted to allow list"
);
let harness = setup_test_agent(MockProvider::new()).await.unwrap();
harness
.agent
.handle_message(
"tg_123",
"hello",
None,
role.unwrap(),
ChannelContext::private("telegram"),
None,
)
.await
.unwrap();
let call_log = harness.provider.call_log.lock().await;
assert!(
!call_log[0].tools.is_empty(),
"Auto-claimed user should have tools"
);
let sys = call_log[0]
.messages
.iter()
.find(|m| m["role"] == "system")
.unwrap();
assert!(
sys["content"]
.as_str()
.unwrap()
.contains("[User Role: owner]"),
"Auto-claimed user should be Owner"
);
}
#[tokio::test]
async fn test_telegram_allowed_user_no_owner_ids() {
let mut allowed = vec![111];
let owner_ids: Vec<u64> = vec![];
let role = simulate_telegram_auth(&mut allowed, &owner_ids, 111);
assert_eq!(
role,
Some(UserRole::Owner),
"Allowed user with no owner_ids should be Owner"
);
let harness = setup_test_agent(MockProvider::new()).await.unwrap();
harness
.agent
.handle_message(
"tg_111",
"run a command",
None,
role.unwrap(),
ChannelContext::private("telegram"),
None,
)
.await
.unwrap();
let call_log = harness.provider.call_log.lock().await;
assert!(!call_log[0].tools.is_empty(), "Should have tools as Owner");
let sys = call_log[0]
.messages
.iter()
.find(|m| m["role"] == "system")
.unwrap();
assert!(
!sys["content"]
.as_str()
.unwrap()
.contains("current user is a guest"),
"Should NOT have guest warning"
);
}
#[tokio::test]
async fn test_telegram_user_in_owner_ids() {
let mut allowed = vec![111, 222];
let owner_ids = vec![111];
let role = simulate_telegram_auth(&mut allowed, &owner_ids, 111);
assert_eq!(role, Some(UserRole::Owner));
}
#[tokio::test]
async fn test_telegram_guest_user() {
let mut allowed = vec![111, 222];
let owner_ids = vec![111]; let role = simulate_telegram_auth(&mut allowed, &owner_ids, 222);
assert_eq!(
role,
Some(UserRole::Guest),
"Non-owner allowed user should be Guest"
);
let harness = setup_test_agent(MockProvider::new()).await.unwrap();
harness
.agent
.handle_message(
"tg_222",
"hello",
None,
role.unwrap(),
ChannelContext::private("telegram"),
None,
)
.await
.unwrap();
let call_log = harness.provider.call_log.lock().await;
assert!(call_log[0].tools.is_empty(), "Guest should not have tools");
let sys = call_log[0]
.messages
.iter()
.find(|m| m["role"] == "system")
.unwrap();
assert!(
sys["content"]
.as_str()
.unwrap()
.contains("Tool access is owner-only"),
"Guest should see owner-only warning"
);
}
#[tokio::test]
async fn test_telegram_unauthorized_user() {
let mut allowed = vec![111];
let owner_ids: Vec<u64> = vec![];
let role = simulate_telegram_auth(&mut allowed, &owner_ids, 999);
assert_eq!(role, None, "Unauthorized user should be rejected");
}
#[tokio::test]
async fn test_telegram_second_user_after_auto_claim_rejected() {
let mut allowed: Vec<u64> = vec![];
let owner_ids: Vec<u64> = vec![];
let role1 = simulate_telegram_auth(&mut allowed, &owner_ids, 111);
assert_eq!(role1, Some(UserRole::Owner));
let role2 = simulate_telegram_auth(&mut allowed, &owner_ids, 222);
assert_eq!(
role2, None,
"Second user should be rejected after auto-claim"
);
}
#[tokio::test]
async fn test_slack_whitelisted_user_is_owner() {
let allowed = vec!["U12345".to_string()];
let role = simulate_slack_auth(&allowed, "U12345", true);
assert_eq!(role, Some(UserRole::Owner));
let harness = setup_test_agent(MockProvider::new()).await.unwrap();
harness
.agent
.handle_message(
"slack_U12345",
"do something",
None,
role.unwrap(),
ChannelContext::private("slack"),
None,
)
.await
.unwrap();
let call_log = harness.provider.call_log.lock().await;
assert!(
!call_log[0].tools.is_empty(),
"Slack owner should have tools"
);
}
#[tokio::test]
async fn test_slack_non_whitelisted_dm_is_public() {
let allowed = vec!["U12345".to_string()];
let role = simulate_slack_auth(&allowed, "U99999", true);
assert_eq!(role, Some(UserRole::Public));
let harness = setup_test_agent(MockProvider::new()).await.unwrap();
harness
.agent
.handle_message(
"slack_U99999",
"run command",
None,
role.unwrap(),
ChannelContext::private("slack"),
None,
)
.await
.unwrap();
let call_log = harness.provider.call_log.lock().await;
assert!(
call_log[0].tools.is_empty(),
"Public Slack user should have no tools"
);
}
#[tokio::test]
async fn test_slack_non_whitelisted_channel_ignored() {
let allowed = vec!["U12345".to_string()];
let role = simulate_slack_auth(&allowed, "U99999", false);
assert_eq!(
role, None,
"Non-whitelisted channel message should be ignored"
);
}
#[tokio::test]
async fn test_discord_always_owner() {
let role = simulate_discord_auth();
assert_eq!(role, Some(UserRole::Owner));
let harness = setup_test_agent(MockProvider::new()).await.unwrap();
harness
.agent
.handle_message(
"discord_123",
"hello",
None,
role.unwrap(),
ChannelContext::private("discord"),
None,
)
.await
.unwrap();
let call_log = harness.provider.call_log.lock().await;
assert!(
!call_log[0].tools.is_empty(),
"Discord user should have tools"
);
}
#[tokio::test]
async fn test_role_tool_access_tiers() {
let roles_and_expected = vec![
(UserRole::Owner, true, "owner"),
(UserRole::Guest, false, "guest"),
(UserRole::Public, false, "public"),
];
for (role, should_have_tools, label) in roles_and_expected {
let harness = setup_test_agent(MockProvider::new()).await.unwrap();
let _response = harness
.agent
.handle_message(
&format!("{}_session", label),
"test message",
None,
role,
ChannelContext::private("test"),
None,
)
.await
.unwrap();
let call_log = harness.provider.call_log.lock().await;
let has_tools = !call_log[0].tools.is_empty();
assert_eq!(
has_tools, should_have_tools,
"{} user: expected tools={}, got tools={}",
label, should_have_tools, has_tools
);
}
}