//! Integration test for the orchestrator wiring in the composition root //! (ARCHITECTURE §14.3). //! //! These tests prove that [`AppState`] actually *starts and stops* per-project //! orchestrator watchers — the gap that previously left the whole §14.3 feature //! dormant (the `OrchestratorService`/watcher existed but were never constructed //! at runtime). The per-file request→dispatch→response behaviour is covered by //! the infrastructure watcher tests; here we assert the lifecycle the open/close //! commands rely on: registration is idempotent, projects are isolated, and //! stopping unregisters. use std::path::PathBuf; use app_tauri_lib::state::AppState; use domain::ports::IdGenerator; use domain::project::{Project, ProjectPath}; use domain::remote::RemoteRef; use domain::ProjectId; use infrastructure::UuidGenerator; /// A unique, absolute temp path (never written to at build time — the stores are /// lazy — so it need not exist). fn temp_path(tag: &str) -> PathBuf { let ids = UuidGenerator::new(); std::env::temp_dir().join(format!("idea-orch-test-{tag}-{}", ids.new_uuid())) } /// Builds a domain [`Project`] rooted at a fresh temp path. fn make_project() -> Project { let ids = UuidGenerator::new(); let root = temp_path("root"); Project::new( ProjectId::from_uuid(ids.new_uuid()), "demo", ProjectPath::new(root.to_string_lossy().into_owned()).unwrap(), RemoteRef::local(), 1_700_000_000_000, ) .unwrap() } fn watcher_count(state: &AppState) -> usize { state.orchestrator_watchers.lock().unwrap().len() } fn has_watcher(state: &AppState, id: &ProjectId) -> bool { state.orchestrator_watchers.lock().unwrap().contains_key(id) } #[tokio::test] async fn ensure_watch_registers_a_watcher_and_is_idempotent() { let state = AppState::build(temp_path("appdata")); let project = make_project(); assert_eq!(watcher_count(&state), 0, "no watcher before open"); state.ensure_orchestrator_watch(&project); assert!(has_watcher(&state, &project.id)); assert_eq!(watcher_count(&state), 1); // Opening the same project again must not spawn a second watcher. state.ensure_orchestrator_watch(&project); assert_eq!(watcher_count(&state), 1, "ensure is idempotent per project"); } #[tokio::test] async fn stop_watch_unregisters_the_watcher() { let state = AppState::build(temp_path("appdata")); let project = make_project(); state.ensure_orchestrator_watch(&project); assert!(has_watcher(&state, &project.id)); state.stop_orchestrator_watch(&project.id); assert!(!has_watcher(&state, &project.id), "watcher removed on close"); assert_eq!(watcher_count(&state), 0); // Stopping an unknown project is a no-op (does not panic). state.stop_orchestrator_watch(&project.id); } #[tokio::test] async fn watchers_are_isolated_per_project() { let state = AppState::build(temp_path("appdata")); let a = make_project(); let b = make_project(); state.ensure_orchestrator_watch(&a); state.ensure_orchestrator_watch(&b); assert_eq!(watcher_count(&state), 2); assert!(has_watcher(&state, &a.id)); assert!(has_watcher(&state, &b.id)); // Closing one leaves the other running. state.stop_orchestrator_watch(&a.id); assert!(!has_watcher(&state, &a.id)); assert!(has_watcher(&state, &b.id)); assert_eq!(watcher_count(&state), 1); }