test(tickets): renforce la couverture attachments web-server/app-tauri (#108)

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 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 11:17:05 +02:00
parent 66f0dca916
commit b55d12d035
2 changed files with 204 additions and 0 deletions

View File

@ -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());
}

View File

@ -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();