use super::*;
use tempfile::TempDir;
fn make_state() -> AppState {
let tmp = TempDir::new().unwrap();
let tmp_path = tmp.into_path();
let session_manager = SessionManager::new(tmp_path.clone()).unwrap();
let config = AppConfig::default();
let user_store = UserStore::new(tmp_path.clone()).unwrap();
let model_registry = ModelRegistry::new();
AppState::new(
session_manager,
config,
"/tmp/test".to_string(),
user_store,
model_registry,
)
}
#[tokio::test]
async fn test_mode_default() {
let state = make_state();
assert_eq!(state.mode().await, OperationMode::Normal);
}
#[tokio::test]
async fn test_set_mode() {
let state = make_state();
state.set_mode(OperationMode::Plan).await;
assert_eq!(state.mode().await, OperationMode::Plan);
}
#[tokio::test]
async fn test_autonomy_level() {
let state = make_state();
assert_eq!(state.autonomy_level().await, "Manual");
state.set_autonomy_level("Auto".to_string()).await;
assert_eq!(state.autonomy_level().await, "Auto");
}
#[tokio::test]
async fn test_interrupt_flag() {
let state = make_state();
assert!(!state.is_interrupt_requested().await);
state.request_interrupt().await;
assert!(state.is_interrupt_requested().await);
state.clear_interrupt().await;
assert!(!state.is_interrupt_requested().await);
}
#[tokio::test]
async fn test_session_running() {
let state = make_state();
assert!(!state.is_session_running("s1").await);
state.set_session_running("s1".to_string()).await;
assert!(state.is_session_running("s1").await);
state.set_session_idle("s1").await;
assert!(!state.is_session_running("s1").await);
}
#[tokio::test]
async fn test_approval_oneshot_lifecycle() {
let state = make_state();
let approval = PendingApproval {
tool_name: "bash".to_string(),
arguments: serde_json::json!({"command": "ls"}),
session_id: Some("s1".to_string()),
};
let rx = state.add_pending_approval("a1".to_string(), approval).await;
let pending = state.get_pending_approval("a1").await;
assert!(pending.is_some());
assert_eq!(pending.unwrap().tool_name, "bash");
let resolved = state.resolve_approval("a1", true, false).await;
assert!(resolved.is_some());
let result = rx.await.unwrap();
assert!(result.approved);
assert!(!result.auto_approve);
assert!(state.resolve_approval("a1", false, false).await.is_none());
}
#[tokio::test]
async fn test_interrupt_denies_pending_approvals() {
let state = make_state();
let approval = PendingApproval {
tool_name: "bash".to_string(),
arguments: serde_json::json!({}),
session_id: Some("s1".to_string()),
};
let rx = state.add_pending_approval("a1".to_string(), approval).await;
state.request_interrupt().await;
let result = rx.await.unwrap();
assert!(!result.approved);
}
#[tokio::test]
async fn test_clear_session_approvals() {
let state = make_state();
let approval_s1 = PendingApproval {
tool_name: "bash".to_string(),
arguments: serde_json::json!({}),
session_id: Some("s1".to_string()),
};
let approval_s2 = PendingApproval {
tool_name: "edit".to_string(),
arguments: serde_json::json!({}),
session_id: Some("s2".to_string()),
};
let rx_s1 = state
.add_pending_approval("a1".to_string(), approval_s1)
.await;
let _rx_s2 = state
.add_pending_approval("a2".to_string(), approval_s2)
.await;
state.clear_session_approvals("s1").await;
let result = rx_s1.await.unwrap();
assert!(!result.approved);
assert!(state.get_pending_approval("a2").await.is_some());
}
#[tokio::test]
async fn test_ask_user_oneshot_lifecycle() {
let state = make_state();
let ask = PendingAskUser {
prompt: "What is your name?".to_string(),
session_id: Some("s1".to_string()),
};
let rx = state.add_pending_ask_user("q1".to_string(), ask).await;
let pending = state.get_pending_ask_user("q1").await;
assert!(pending.is_some());
let resolved = state
.resolve_ask_user("q1", Some(serde_json::json!({"name": "Alice"})), false)
.await;
assert!(resolved.is_some());
let result = rx.await.unwrap();
assert!(!result.cancelled);
assert_eq!(
result.answers.unwrap(),
serde_json::json!({"name": "Alice"})
);
}
#[tokio::test]
async fn test_interrupt_cancels_ask_users() {
let state = make_state();
let ask = PendingAskUser {
prompt: "question".to_string(),
session_id: None,
};
let rx = state.add_pending_ask_user("q1".to_string(), ask).await;
state.request_interrupt().await;
let result = rx.await.unwrap();
assert!(result.cancelled);
}
#[tokio::test]
async fn test_plan_approval_oneshot_lifecycle() {
let state = make_state();
let plan = PendingPlanApproval {
data: serde_json::json!({"plan": "do something"}),
session_id: Some("s1".to_string()),
};
let rx = state
.add_pending_plan_approval("p1".to_string(), plan)
.await;
let pending = state.get_pending_plan_approval("p1").await;
assert!(pending.is_some());
let resolved = state
.resolve_plan_approval("p1", "approve".to_string(), "looks good".to_string())
.await;
assert!(resolved.is_some());
let result = rx.await.unwrap();
assert_eq!(result.action, "approve");
assert_eq!(result.feedback, "looks good");
assert!(
state
.resolve_plan_approval("p1", "reject".to_string(), String::new())
.await
.is_none()
);
}
#[tokio::test]
async fn test_interrupt_rejects_plan_approvals() {
let state = make_state();
let plan = PendingPlanApproval {
data: serde_json::json!({"plan": "test"}),
session_id: None,
};
let rx = state
.add_pending_plan_approval("p1".to_string(), plan)
.await;
state.request_interrupt().await;
let result = rx.await.unwrap();
assert_eq!(result.action, "reject");
}
#[tokio::test]
async fn test_clear_session_plan_approvals() {
let state = make_state();
let plan_s1 = PendingPlanApproval {
data: serde_json::json!({}),
session_id: Some("s1".to_string()),
};
let plan_s2 = PendingPlanApproval {
data: serde_json::json!({}),
session_id: Some("s2".to_string()),
};
let rx_s1 = state
.add_pending_plan_approval("p1".to_string(), plan_s1)
.await;
let _rx_s2 = state
.add_pending_plan_approval("p2".to_string(), plan_s2)
.await;
state.clear_session_plan_approvals("s1").await;
let result = rx_s1.await.unwrap();
assert_eq!(result.action, "reject");
assert!(state.get_pending_plan_approval("p2").await.is_some());
}
#[tokio::test]
async fn test_bridge_mode() {
let state = make_state();
assert!(!state.is_bridge_mode().await);
assert!(state.bridge_session_id().await.is_none());
assert!(!state.is_bridge_guarded("s1").await);
state.set_bridge_session("s1".to_string()).await;
assert!(state.is_bridge_mode().await);
assert_eq!(state.bridge_session_id().await.unwrap(), "s1");
assert!(state.is_bridge_guarded("s1").await);
assert!(!state.is_bridge_guarded("s2").await);
state.clear_bridge_session().await;
assert!(!state.is_bridge_mode().await);
assert!(!state.is_bridge_guarded("s1").await);
}
#[tokio::test]
async fn test_injection_queue() {
let state = make_state();
let (tx, rx) = state.get_or_create_injection_queue("s1").await;
assert!(rx.is_some());
let mut rx = rx.unwrap();
let (tx2, rx2) = state.get_or_create_injection_queue("s1").await;
assert!(rx2.is_none());
tx.try_send("hello".to_string()).unwrap();
tx2.try_send("world".to_string()).unwrap();
assert_eq!(rx.recv().await.unwrap(), "hello");
assert_eq!(rx.recv().await.unwrap(), "world");
state
.try_inject_message("s1", "via state".to_string())
.await
.unwrap();
assert_eq!(rx.recv().await.unwrap(), "via state");
state.clear_injection_queue("s1").await;
assert!(
state
.try_inject_message("s1", "fail".to_string())
.await
.is_err()
);
}
#[tokio::test]
async fn test_broadcast() {
let state = make_state();
let mut rx = state.ws_subscribe();
state.broadcast(WsBroadcast {
msg_type: "test".to_string(),
data: serde_json::json!({"hello": "world"}),
});
let msg = rx.recv().await.unwrap();
assert_eq!(msg.msg_type, "test");
}
#[tokio::test]
async fn test_user_store_access() {
let state = make_state();
assert_eq!(state.user_store().count(), 0);
}
#[tokio::test]
async fn test_model_registry_access() {
let state = make_state();
let registry = state.model_registry().await;
assert!(registry.providers.is_empty());
}