From b55d12d0358f0db403e96182068da97c1dfdc170 Mon Sep 17 00:00:00 2001 From: Blomios Date: Wed, 29 Jul 2026 11:17:05 +0200 Subject: [PATCH] test(tickets): renforce la couverture attachments web-server/app-tauri (#108) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Levée de la réserve QA sur #108 : aucun test ciblé ne matchait réellement sur web-server et app-tauri malgré le câblage attachments tickets. - web-server: test fonctionnel via /api/invoke pour ticket_create, ticket_attachment_add, ticket_attachment_read, ticket_attachment_mark_summarized. - app-tauri: test fonctionnel via la surface publique tool/provider pour idea_ticket_create, idea_ticket_attachment_add, idea_ticket_attachment_read, idea_ticket_attachment_mark_summarized. Pas de changement de wiring produit ; assertions alignées sur le contrat public réel (contentBase64 sans padding =, summarizedBy.kind = user côté app-tauri). Co-Authored-By: Claude Opus 4.8 --- crates/app-tauri/tests/ticket_attachments.rs | 106 +++++++++++++++++++ crates/web-server/src/lib.rs | 98 +++++++++++++++++ 2 files changed, 204 insertions(+) create mode 100644 crates/app-tauri/tests/ticket_attachments.rs diff --git a/crates/app-tauri/tests/ticket_attachments.rs b/crates/app-tauri/tests/ticket_attachments.rs new file mode 100644 index 0000000..d878f51 --- /dev/null +++ b/crates/app-tauri/tests/ticket_attachments.rs @@ -0,0 +1,106 @@ +use std::path::PathBuf; + +use app_tauri_lib::state::AppState; +use application::CreateProjectInput; +use base64::Engine as _; +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_attachment_tools_add_read_and_mark_summarized() { + let state = AppState::build(temp_path("appdata")); + let project_root = temp_path("project"); + let project = state + .create_project + .execute(CreateProjectInput { + name: "App Tauri Attachments".to_owned(), + root: project_root.to_string_lossy().into_owned(), + 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": "Attachment over tool provider" }), + ) + .await + .expect("ticket create succeeds"); + let ticket_ref = created["ref"].as_str().expect("ticket ref").to_owned(); + let ticket_version = created["version"].as_u64().expect("ticket version"); + + let source = project_root.join("tool-attachment.txt"); + std::fs::write(&source, b"app tauri attachment payload") + .expect("test writes source attachment"); + + let attached = state + .ticket_tool_binder + .handle_ticket_tool( + &project, + "qa-agent", + "idea_ticket_attachment_add", + json!({ + "ref": ticket_ref, + "path": source.to_string_lossy(), + "mime": "text/plain", + "expectedVersion": ticket_version + }), + ) + .await + .expect("attachment add succeeds"); + let attachment = &attached["attachments"].as_array().expect("attachments")[0]; + assert_eq!(attachment["filename"], "tool-attachment.txt"); + assert_eq!(attachment["mime"], "text/plain"); + assert_eq!(attachment["summarizedInCarnet"], false); + let attachment_id = attachment["id"].as_str().expect("attachment id").to_owned(); + let updated_version = attached["version"].as_u64().expect("updated version"); + + let content = state + .ticket_tool_binder + .handle_ticket_tool( + &project, + "qa-agent", + "idea_ticket_attachment_read", + json!({ + "ref": ticket_ref, + "attachmentId": attachment_id + }), + ) + .await + .expect("attachment read succeeds"); + assert_eq!(content["attachment"]["filename"], "tool-attachment.txt"); + assert_eq!( + content["contentBase64"], + base64::engine::general_purpose::STANDARD_NO_PAD.encode(b"app tauri attachment payload") + ); + + let summarized = state + .ticket_tool_binder + .handle_ticket_tool( + &project, + "qa-agent", + "idea_ticket_attachment_mark_summarized", + json!({ + "ref": ticket_ref, + "attachmentId": content["attachment"]["id"], + "expectedVersion": updated_version + }), + ) + .await + .expect("mark summarized succeeds"); + let summarized_attachment = &summarized["attachments"].as_array().expect("attachments")[0]; + assert_eq!(summarized_attachment["summarizedInCarnet"], true); + assert_eq!(summarized_attachment["summarizedBy"]["kind"], "user"); + assert!(summarized_attachment["summarizedAt"].as_u64().is_some()); +} diff --git a/crates/web-server/src/lib.rs b/crates/web-server/src/lib.rs index 4b4cf23..1827ef8 100644 --- a/crates/web-server/src/lib.rs +++ b/crates/web-server/src/lib.rs @@ -8176,6 +8176,104 @@ mod tests { assert_eq!(deleted, Value::Null); } + #[tokio::test] + async fn authorized_ticket_invoke_routes_attachment_commands() { + let state = state(); + let project_id = create_project_for_test(&state, "Web Ticket Attachments").await; + let cookie = pair_and_cookie(Arc::clone(&state)).await; + + let (status, created) = invoke_request_for_test( + Arc::clone(&state), + &cookie, + "ticket_create", + json!({ + "request": { + "projectId": project_id, + "title": "Wire attachment ticket" + } + }), + ) + .await; + assert_eq!(status, StatusCode::OK); + let ticket_ref = created["ref"].as_str().unwrap().to_owned(); + let ticket_version = created["version"].as_u64().unwrap(); + + let project = state + .app + .open_project + .execute(OpenProjectInput { + project_id: ProjectId::from_uuid(Uuid::parse_str(&project_id).unwrap()), + }) + .await + .expect("test project is readable") + .project; + let source = std::path::PathBuf::from(project.root.as_str()).join("web-attachment.txt"); + std::fs::write(&source, b"web attachment payload").expect("test writes source attachment"); + + let (status, attached) = invoke_request_for_test( + Arc::clone(&state), + &cookie, + "ticket_attachment_add", + json!({ + "request": { + "projectId": project_id, + "ref": ticket_ref, + "path": source.to_string_lossy(), + "mime": "text/plain", + "expectedVersion": ticket_version + } + }), + ) + .await; + assert_eq!(status, StatusCode::OK); + let attachment = &attached["attachments"].as_array().unwrap()[0]; + assert_eq!(attachment["filename"], "web-attachment.txt"); + assert_eq!(attachment["mime"], "text/plain"); + assert_eq!(attachment["summarizedInCarnet"], false); + let attachment_id = attachment["id"].as_str().unwrap().to_owned(); + let updated_version = attached["version"].as_u64().unwrap(); + + let (status, content) = invoke_request_for_test( + Arc::clone(&state), + &cookie, + "ticket_attachment_read", + json!({ + "request": { + "projectId": project_id, + "ref": ticket_ref, + "attachmentId": attachment_id + } + }), + ) + .await; + assert_eq!(status, StatusCode::OK); + assert_eq!(content["attachment"]["filename"], "web-attachment.txt"); + assert_eq!( + content["contentBase64"], + base64::engine::general_purpose::STANDARD_NO_PAD.encode(b"web attachment payload") + ); + + let (status, summarized) = invoke_request_for_test( + Arc::clone(&state), + &cookie, + "ticket_attachment_mark_summarized", + json!({ + "request": { + "projectId": project_id, + "ref": ticket_ref, + "attachmentId": content["attachment"]["id"], + "expectedVersion": updated_version + } + }), + ) + .await; + assert_eq!(status, StatusCode::OK); + let summarized_attachment = &summarized["attachments"].as_array().unwrap()[0]; + assert_eq!(summarized_attachment["summarizedInCarnet"], true); + assert_eq!(summarized_attachment["summarizedBy"]["kind"], "user"); + assert!(summarized_attachment["summarizedAt"].as_u64().is_some()); + } + #[tokio::test] async fn authorized_sprint_invoke_routes_sprint_and_ticket_sprint_commands() { let state = state();