//! Integration test for the **session-limit** wiring in the composition root //! (ARCHITECTURE §21, LS7). //! //! Proves that [`AppState`] actually *constructs and wires* the //! `SessionLimitService` (the gap LS7 closes: the LS1–LS4 domain/application //! machinery existed but was never instantiated at runtime). The detect→plan→ //! resume→cancel logic itself is covered by the application/infrastructure unit //! tests (LS3/LS4); here we assert the **composition** the commands rely on: //! the service is present, `on_rate_limited` arms a cancellable resume that emits //! the domain events on the real bus, and `cancel_resume` is a clean no-op for an //! unknown agent (never a panic). use std::path::PathBuf; use std::time::Duration; use app_tauri_lib::state::AppState; use domain::events::DomainEvent; use domain::ids::NodeId; use domain::AgentId; use infrastructure::UuidGenerator; use domain::ports::IdGenerator; fn temp_path(tag: &str) -> PathBuf { let ids = UuidGenerator::new(); std::env::temp_dir().join(format!("idea-sl-test-{tag}-{}", ids.new_uuid())) } fn agent(n: u128) -> AgentId { AgentId::from_uuid(uuid::Uuid::from_u128(n)) } fn node(n: u128) -> NodeId { NodeId::from_uuid(uuid::Uuid::from_u128(n)) } /// `cancel_resume` on an agent with no armed resume returns `false` (and never /// panics): the service is wired and behaves as a clean no-op. #[tokio::test] async fn cancel_resume_is_a_clean_noop_for_unknown_agent() { let state = AppState::build(temp_path("appdata")); assert!( !state.session_limit_service.cancel_resume(agent(1)), "no armed resume ⇒ cancel returns false" ); } /// A structured rate-limit signal with a **future** reset arms a cancellable /// resume: the wired service publishes `AgentRateLimited` then /// `AgentResumeScheduled` on the **real** event bus, and `cancel_resume` then /// publishes `AgentResumeCancelled` and returns `true`. #[tokio::test] async fn on_rate_limited_arms_a_cancellable_resume_over_the_real_bus() { let state = AppState::build(temp_path("appdata")); let mut rx = state.event_bus.raw_receiver(); // Reset far in the future so the scheduler does NOT fire before we cancel. let resets_at_ms = i64::MAX; state .session_limit_service .on_rate_limited(agent(7), node(70), None, Some(resets_at_ms)); // The two arming events, in order, on the bus. let mut saw_rate_limited = false; let mut saw_scheduled = false; for _ in 0..32 { match tokio::time::timeout(Duration::from_secs(2), rx.recv()).await { Ok(Ok(DomainEvent::AgentRateLimited { agent_id, .. })) if agent_id == agent(7) => { saw_rate_limited = true; } Ok(Ok(DomainEvent::AgentResumeScheduled { agent_id, .. })) if agent_id == agent(7) => { saw_scheduled = true; break; } Ok(Ok(_)) => continue, _ => break, } } assert!(saw_rate_limited, "AgentRateLimited relayed on the bus"); assert!(saw_scheduled, "AgentResumeScheduled relayed on the bus"); // The window is cancellable: cancel succeeds and emits AgentResumeCancelled. assert!( state.session_limit_service.cancel_resume(agent(7)), "an armed resume is cancellable" ); let mut saw_cancelled = false; for _ in 0..32 { match tokio::time::timeout(Duration::from_secs(2), rx.recv()).await { Ok(Ok(DomainEvent::AgentResumeCancelled { agent_id })) if agent_id == agent(7) => { saw_cancelled = true; break; } Ok(Ok(_)) => continue, _ => break, } } assert!(saw_cancelled, "AgentResumeCancelled relayed on the bus"); }