//! Integration tests for project CRUD lifecycle. use dirigent_projects::{ CreateProjectParams, FileBasedProjectStore, ProjectFilter, ProjectStore, ProjectUpdate, }; use uuid::Uuid; async fn make_store() -> FileBasedProjectStore { let dir = tempfile::tempdir().unwrap(); FileBasedProjectStore::new(dir.into_path()).await.unwrap() } #[tokio::test] async fn test_create_and_get_project() { let store = make_store().await; let owner = Uuid::now_v7(); let project = store .create_project(CreateProjectParams { name: "Test Project".to_string(), description: "A test".to_string(), icon: Some("🚀".to_string()), owner, tags: vec!["rust".to_string()], languages: vec!["Rust".to_string()], metadata: serde_json::json!({}), }) .await .unwrap(); assert_eq!(project.name, "Test Project"); assert_eq!(project.owner, owner); let fetched = store.get_project(&project.id).await.unwrap(); assert_eq!(fetched.id, project.id); assert_eq!(fetched.name, "Test Project"); assert_eq!(fetched.icon, Some("🚀".to_string())); } #[tokio::test] async fn test_list_projects_empty() { let store = make_store().await; let projects = store.list_projects(ProjectFilter::default()).await.unwrap(); assert!(projects.is_empty()); } #[tokio::test] async fn test_list_projects_with_filter() { let store = make_store().await; let owner1 = Uuid::now_v7(); let owner2 = Uuid::now_v7(); store .create_project(CreateProjectParams { name: "Alpha".to_string(), owner: owner1, tags: vec!["web".to_string()], ..default_params() }) .await .unwrap(); store .create_project(CreateProjectParams { name: "Beta".to_string(), owner: owner2, tags: vec!["cli".to_string()], ..default_params() }) .await .unwrap(); // Filter by owner let filtered = store .list_projects(ProjectFilter { owner: Some(owner1), ..Default::default() }) .await .unwrap(); assert_eq!(filtered.len(), 1); assert_eq!(filtered[0].name, "Alpha"); // Filter by name let filtered = store .list_projects(ProjectFilter { name_contains: Some("bet".to_string()), ..Default::default() }) .await .unwrap(); assert_eq!(filtered.len(), 1); assert_eq!(filtered[0].name, "Beta"); // Filter by tag let filtered = store .list_projects(ProjectFilter { tags: vec!["web".to_string()], ..Default::default() }) .await .unwrap(); assert_eq!(filtered.len(), 1); assert_eq!(filtered[0].name, "Alpha"); // No filter returns all, sorted by name let all = store.list_projects(ProjectFilter::default()).await.unwrap(); assert_eq!(all.len(), 2); assert_eq!(all[0].name, "Alpha"); assert_eq!(all[1].name, "Beta"); } #[tokio::test] async fn test_update_project() { let store = make_store().await; let project = store .create_project(CreateProjectParams { name: "Original".to_string(), ..default_params() }) .await .unwrap(); let updated = store .update_project( &project.id, ProjectUpdate { name: Some("Renamed".to_string()), description: Some("New description".to_string()), tags: Some(vec!["new-tag".to_string()]), ..Default::default() }, ) .await .unwrap(); assert_eq!(updated.name, "Renamed"); assert_eq!(updated.description, "New description"); assert_eq!(updated.tags, vec!["new-tag"]); assert!(updated.updated_at > project.created_at); // Verify persistence let fetched = store.get_project(&project.id).await.unwrap(); assert_eq!(fetched.name, "Renamed"); } #[tokio::test] async fn test_delete_project() { let store = make_store().await; let project = store .create_project(CreateProjectParams { name: "ToDelete".to_string(), ..default_params() }) .await .unwrap(); store.delete_project(&project.id).await.unwrap(); let err = store.get_project(&project.id).await.unwrap_err(); assert!(matches!(err, dirigent_projects::ProjectError::NotFound(_))); } #[tokio::test] async fn test_get_nonexistent_project() { let store = make_store().await; let err = store.get_project(&Uuid::now_v7()).await.unwrap_err(); assert!(matches!(err, dirigent_projects::ProjectError::NotFound(_))); } #[tokio::test] async fn test_create_empty_name_fails() { let store = make_store().await; let err = store .create_project(CreateProjectParams { name: " ".to_string(), ..default_params() }) .await .unwrap_err(); assert!(matches!( err, dirigent_projects::ProjectError::Validation(_) )); } #[tokio::test] async fn test_update_empty_name_fails() { let store = make_store().await; let project = store .create_project(CreateProjectParams { name: "Valid".to_string(), ..default_params() }) .await .unwrap(); let err = store .update_project( &project.id, ProjectUpdate { name: Some("".to_string()), ..Default::default() }, ) .await .unwrap_err(); assert!(matches!( err, dirigent_projects::ProjectError::Validation(_) )); } fn default_params() -> CreateProjectParams { CreateProjectParams { name: String::new(), description: String::new(), icon: None, owner: Uuid::now_v7(), tags: vec![], languages: vec![], metadata: serde_json::json!({}), } }