feat(tickets): ajoute les pièces jointes sur tickets (#108)
Stockage flat côté ticket + métadonnées d'attachments, lecture exposée côté backend/MCP, et UI minimale de liste/ajout dans TicketDetail. Traverse le domaine (Issue, ports), l'application (usecases + assistant de ticket), les adaptateurs infra/MCP (issues store, orchestrateur), les DTO backend/web- server/app-tauri, et le frontend (domain/ports/adapters/hooks/UI). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -172,6 +172,46 @@ describe("MockTicketGateway", () => {
|
||||
).rejects.toMatchObject({ message: expect.stringContaining("version conflict") });
|
||||
});
|
||||
|
||||
it("adds ticket attachments and marks them summarized (#108)", async () => {
|
||||
const t = await ticket.create(PROJECT_ID, { title: "With attachment" });
|
||||
|
||||
const attached = await ticket.addAttachment(
|
||||
PROJECT_ID,
|
||||
t.ref,
|
||||
"/tmp/spec.pdf",
|
||||
t.version,
|
||||
"application/pdf",
|
||||
);
|
||||
expect(attached.version).toBe(2);
|
||||
expect(attached.attachments).toMatchObject([
|
||||
{
|
||||
filename: "spec.pdf",
|
||||
mime: "application/pdf",
|
||||
summarizedInCarnet: false,
|
||||
},
|
||||
]);
|
||||
|
||||
const content = await ticket.readAttachment(
|
||||
PROJECT_ID,
|
||||
t.ref,
|
||||
attached.attachments[0].id,
|
||||
);
|
||||
expect(content.attachment.filename).toBe("spec.pdf");
|
||||
expect(content.contentBase64).toBeTruthy();
|
||||
|
||||
const summarized = await ticket.markAttachmentSummarized(
|
||||
PROJECT_ID,
|
||||
t.ref,
|
||||
attached.attachments[0].id,
|
||||
attached.version,
|
||||
);
|
||||
expect(summarized.version).toBe(3);
|
||||
expect(summarized.attachments[0]).toMatchObject({
|
||||
summarizedInCarnet: true,
|
||||
summarizedBy: { kind: "user" },
|
||||
});
|
||||
});
|
||||
|
||||
it("deletes a ticket, removing it and emitting issueDeleted with freedSprint (#6)", async () => {
|
||||
ticket._seedSprint(PROJECT_ID, { id: "s1", order: 1, name: "S" });
|
||||
const t = await ticket.create(PROJECT_ID, { title: "Doomed" });
|
||||
@ -515,6 +555,36 @@ describe("TicketsView", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("shows, adds, and marks ticket attachments from the detail (#108)", async () => {
|
||||
const t = await ticket.create(PROJECT_ID, { title: "Attachable" });
|
||||
renderView(ticket, system, agent);
|
||||
|
||||
fireEvent.click(await screen.findByText("Attachable"));
|
||||
const dialog = await screen.findByRole("dialog", { name: `ticket ${t.ref}` });
|
||||
|
||||
expect(within(dialog).getByText("Pièces jointes")).toBeTruthy();
|
||||
expect(within(dialog).getByText("Aucune pièce jointe.")).toBeTruthy();
|
||||
|
||||
fireEvent.click(within(dialog).getByText("Joindre"));
|
||||
|
||||
expect(await within(dialog).findByText("mock-attachment.txt")).toBeTruthy();
|
||||
expect(within(dialog).getByText(/text\/plain/)).toBeTruthy();
|
||||
expect(within(dialog).getByText("Résumé : non")).toBeTruthy();
|
||||
|
||||
const attached = await ticket.read(PROJECT_ID, t.ref);
|
||||
expect(attached.attachments).toHaveLength(1);
|
||||
|
||||
fireEvent.click(
|
||||
within(dialog).getByLabelText("marquer résumé mock-attachment.txt"),
|
||||
);
|
||||
|
||||
expect(await within(dialog).findByText("Résumé : oui")).toBeTruthy();
|
||||
await waitFor(async () => {
|
||||
const fresh = await ticket.read(PROJECT_ID, t.ref);
|
||||
expect(fresh.attachments[0].summarizedInCarnet).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("assigns only known project agents", async () => {
|
||||
const known = await seedAgent(agent, "Backend");
|
||||
const t = await ticket.create(PROJECT_ID, { title: "Assignable" });
|
||||
|
||||
Reference in New Issue
Block a user