use domain::ports::{ChatDraftStore, IdGenerator}; use domain::{ChatDraft, ChatDraftKey, LayoutId, NodeId, ProjectId, ProjectPath}; use infrastructure::{FsChatDraftStore, UuidGenerator}; use uuid::Uuid; fn temp_dir(tag: &str) -> std::path::PathBuf { std::env::temp_dir().join(format!( "idea-chat-draft-{tag}-{}", UuidGenerator::new().new_uuid() )) } fn pid(n: u128) -> ProjectId { ProjectId::from_uuid(Uuid::from_u128(n)) } fn lid(n: u128) -> LayoutId { LayoutId::from_uuid(Uuid::from_u128(n)) } fn nid(n: u128) -> NodeId { NodeId::from_uuid(Uuid::from_u128(n)) } fn key(layout: u128, node: u128) -> ChatDraftKey { ChatDraftKey::new(pid(1), lid(layout), nid(node)) } #[tokio::test] async fn fs_chat_draft_store_saves_reads_and_replaces_one_scope() { let project_root = temp_dir("roundtrip"); tokio::fs::create_dir_all(&project_root).await.unwrap(); let root = ProjectPath::new(project_root.to_string_lossy().into_owned()).unwrap(); let store = FsChatDraftStore::new(); store .save(&root, ChatDraft::new(key(2, 3), "first", 10).unwrap()) .await .unwrap(); store .save(&root, ChatDraft::new(key(2, 3), "second", 11).unwrap()) .await .unwrap(); let draft = store.read(&root, key(2, 3)).await.unwrap().unwrap(); assert_eq!(draft.text, "second"); assert_eq!(draft.updated_at_ms, 11); assert!( tokio::fs::metadata(project_root.join(".ideai/chat-drafts.json")) .await .is_ok() ); let _ = tokio::fs::remove_dir_all(project_root).await; } #[tokio::test] async fn fs_chat_draft_store_scopes_by_layout_and_node() { let project_root = temp_dir("scoped"); tokio::fs::create_dir_all(&project_root).await.unwrap(); let root = ProjectPath::new(project_root.to_string_lossy().into_owned()).unwrap(); let store = FsChatDraftStore::new(); store .save(&root, ChatDraft::new(key(2, 2), "node two", 10).unwrap()) .await .unwrap(); store .save(&root, ChatDraft::new(key(2, 3), "node three", 11).unwrap()) .await .unwrap(); assert_eq!( store.read(&root, key(2, 2)).await.unwrap().unwrap().text, "node two" ); assert_eq!( store.read(&root, key(2, 3)).await.unwrap().unwrap().text, "node three" ); let _ = tokio::fs::remove_dir_all(project_root).await; } #[tokio::test] async fn fs_chat_draft_store_clear_removes_only_target_scope() { let project_root = temp_dir("clear"); tokio::fs::create_dir_all(&project_root).await.unwrap(); let root = ProjectPath::new(project_root.to_string_lossy().into_owned()).unwrap(); let store = FsChatDraftStore::new(); store .save(&root, ChatDraft::new(key(2, 2), "clear", 10).unwrap()) .await .unwrap(); store .save(&root, ChatDraft::new(key(2, 3), "keep", 11).unwrap()) .await .unwrap(); store.clear(&root, key(2, 2)).await.unwrap(); assert!(store.read(&root, key(2, 2)).await.unwrap().is_none()); assert_eq!( store.read(&root, key(2, 3)).await.unwrap().unwrap().text, "keep" ); let _ = tokio::fs::remove_dir_all(project_root).await; }