use std::path::PathBuf; use app_tauri_lib::state::AppState; use application::CreateProjectInput; use infrastructure::TicketToolProvider; use serde_json::json; use uuid::Uuid; fn temp_path(tag: &str) -> PathBuf { std::env::temp_dir().join(format!("idea-app-tauri-test-{tag}-{}", Uuid::new_v4())) } #[tokio::test] async fn idea_ticket_read_is_best_effort_but_read_carnet_stays_strict_when_carnet_file_is_missing() { let state = AppState::build(temp_path("appdata")); let project_root = temp_path("project").to_string_lossy().into_owned(); let project = state .create_project .execute(CreateProjectInput { name: "App Tauri Tickets".to_owned(), root: project_root, remote: None, default_profile_id: None, }) .await .expect("test project is created") .project; let created = state .ticket_tool_binder .handle_ticket_tool( &project, "qa-agent", "idea_ticket_create", json!({ "title": "Missing carnet fallback", "description": "Created by app-tauri integration test", "priority": "high", "status": "open" }), ) .await .expect("ticket create succeeds"); let ticket_ref = created["ref"] .as_str() .expect("ticket ref is present") .to_owned(); let read = state .ticket_tool_binder .handle_ticket_tool( &project, "qa-agent", "idea_ticket_read", json!({ "ref": ticket_ref, "includeCarnet": true }), ) .await .expect("ticket read succeeds"); assert!( read["carnet"].is_string(), "ticket read returns a string carnet" ); std::fs::remove_file( PathBuf::from(project.root.as_str()) .join(".ideai") .join("tickets") .join(ticket_ref.trim_start_matches('#')) .join("carnet.md"), ) .expect("test removes ticket carnet"); let read_without_carnet_file = state .ticket_tool_binder .handle_ticket_tool( &project, "qa-agent", "idea_ticket_read", json!({ "ref": ticket_ref, "includeCarnet": true }), ) .await .expect("ticket read stays best-effort"); assert_eq!(read_without_carnet_file["ref"], ticket_ref); assert_eq!(read_without_carnet_file["carnet"], ""); let strict_err = state .ticket_tool_binder .handle_ticket_tool( &project, "qa-agent", "idea_ticket_read_carnet", json!({ "ref": ticket_ref }), ) .await .expect_err("strict carnet read must fail when carnet.md is missing"); assert_eq!(strict_err.code, "notFound"); } #[tokio::test] async fn idea_ticket_bulk_delete_deletes_each_ref_and_reports_partial_errors() { let state = AppState::build(temp_path("appdata")); let project_root = temp_path("project").to_string_lossy().into_owned(); let project = state .create_project .execute(CreateProjectInput { name: "App Tauri Bulk Delete".to_owned(), root: project_root, remote: None, default_profile_id: None, }) .await .expect("test project is created") .project; let first = state .ticket_tool_binder .handle_ticket_tool( &project, "qa-agent", "idea_ticket_create", json!({ "title": "Bulk delete A" }), ) .await .expect("first ticket create succeeds"); let second = state .ticket_tool_binder .handle_ticket_tool( &project, "qa-agent", "idea_ticket_create", json!({ "title": "Bulk delete B" }), ) .await .expect("second ticket create succeeds"); let first_ref = first["ref"].as_str().expect("first ref").to_owned(); let second_ref = second["ref"].as_str().expect("second ref").to_owned(); let out = state .ticket_tool_binder .handle_ticket_tool( &project, "qa-agent", "idea_ticket_bulk_delete", json!({ "refs": [first_ref.clone(), "#999", second_ref.clone()] }), ) .await .expect("bulk delete dispatch succeeds"); let items = out["items"].as_array().expect("bulk items"); assert_eq!(items.len(), 3); assert_eq!(items[0]["ref"], first_ref); assert_eq!(items[0]["ok"], true); assert_eq!(items[1]["ref"], "#999"); assert_eq!(items[1]["ok"], false); assert_eq!(items[1]["error"]["code"], "NOT_FOUND"); assert_eq!(items[2]["ref"], second_ref); assert_eq!(items[2]["ok"], true); for deleted_ref in [first_ref, second_ref] { let err = state .ticket_tool_binder .handle_ticket_tool( &project, "qa-agent", "idea_ticket_read", json!({ "ref": deleted_ref }), ) .await .expect_err("deleted ticket read must fail"); assert_eq!(err.code, "notFound"); } }