merge fix/tickets-createdby-legacy-normalization dans develop (fenêtre tickets noircissant sur createdBy legacy)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -286,6 +286,16 @@ export class HttpTicketGateway implements TicketGateway {
|
|||||||
private readonly ws: WsLiveClient,
|
private readonly ws: WsLiveClient,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
private normalizeTicketActor(actor: unknown): Ticket["createdBy"] {
|
||||||
|
if (!actor || typeof actor !== "object") return { kind: "user" };
|
||||||
|
const rec = actor as Record<string, unknown>;
|
||||||
|
if (rec.kind === "user" || rec.kind === "system") return { kind: rec.kind };
|
||||||
|
if (rec.kind === "agent" && typeof rec.agentId === "string" && rec.agentId) {
|
||||||
|
return { kind: "agent", agentId: rec.agentId };
|
||||||
|
}
|
||||||
|
return { kind: "user" };
|
||||||
|
}
|
||||||
|
|
||||||
private normalizeTicket(ticket: Ticket): Ticket {
|
private normalizeTicket(ticket: Ticket): Ticket {
|
||||||
return {
|
return {
|
||||||
...ticket,
|
...ticket,
|
||||||
@ -294,6 +304,8 @@ export class HttpTicketGateway implements TicketGateway {
|
|||||||
? ticket.assignedAgentIds
|
? ticket.assignedAgentIds
|
||||||
: [],
|
: [],
|
||||||
attachments: Array.isArray(ticket.attachments) ? ticket.attachments : [],
|
attachments: Array.isArray(ticket.attachments) ? ticket.attachments : [],
|
||||||
|
createdBy: this.normalizeTicketActor(ticket.createdBy),
|
||||||
|
updatedBy: this.normalizeTicketActor(ticket.updatedBy),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,6 +318,7 @@ export class HttpTicketGateway implements TicketGateway {
|
|||||||
assignedAgentIds: Array.isArray(item.assignedAgentIds)
|
assignedAgentIds: Array.isArray(item.assignedAgentIds)
|
||||||
? item.assignedAgentIds
|
? item.assignedAgentIds
|
||||||
: [],
|
: [],
|
||||||
|
createdBy: this.normalizeTicketActor(item.createdBy),
|
||||||
}))
|
}))
|
||||||
: [],
|
: [],
|
||||||
};
|
};
|
||||||
|
|||||||
82
frontend/src/adapters/http/ticketGateway.test.ts
Normal file
82
frontend/src/adapters/http/ticketGateway.test.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { describe, it, expect, vi } from "vitest";
|
||||||
|
|
||||||
|
import { HttpTicketGateway } from "./streamGateways";
|
||||||
|
import type { HttpInvoker } from "./httpInvoker";
|
||||||
|
import type { WsLiveClient } from "./wsLiveClient";
|
||||||
|
|
||||||
|
function gateway(result: unknown): {
|
||||||
|
gw: HttpTicketGateway;
|
||||||
|
invoke: ReturnType<typeof vi.fn>;
|
||||||
|
} {
|
||||||
|
const invoke = vi.fn().mockResolvedValue(result);
|
||||||
|
const http = { invoke } as unknown as HttpInvoker;
|
||||||
|
const ws = {} as unknown as WsLiveClient;
|
||||||
|
return { gw: new HttpTicketGateway(http, ws), invoke };
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("HttpTicketGateway normalization", () => {
|
||||||
|
it("normalizes legacy ticket list rows that omit assignedAgentIds and createdBy", async () => {
|
||||||
|
const { gw } = gateway({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
ref: "#12",
|
||||||
|
path: ".ideai/tickets/12/issue.md",
|
||||||
|
title: "Legacy row",
|
||||||
|
status: "open",
|
||||||
|
priority: "medium",
|
||||||
|
updatedAt: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const list = await gw.list("proj-1");
|
||||||
|
|
||||||
|
expect(list.items[0]?.assignedAgentIds).toEqual([]);
|
||||||
|
expect(list.items[0]?.createdBy).toEqual({ kind: "user" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("normalizes legacy ticket payloads that omit array fields and actors", async () => {
|
||||||
|
const { gw } = gateway({
|
||||||
|
id: "issue-1",
|
||||||
|
ref: "#12",
|
||||||
|
number: 12,
|
||||||
|
title: "Legacy",
|
||||||
|
description: "",
|
||||||
|
status: "open",
|
||||||
|
priority: "medium",
|
||||||
|
createdAt: 1,
|
||||||
|
updatedAt: 1,
|
||||||
|
version: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const ticket = await gw.read("proj-1", "#12", true);
|
||||||
|
|
||||||
|
expect(ticket.links).toEqual([]);
|
||||||
|
expect(ticket.assignedAgentIds).toEqual([]);
|
||||||
|
expect(ticket.attachments).toEqual([]);
|
||||||
|
expect(ticket.createdBy).toEqual({ kind: "user" });
|
||||||
|
expect(ticket.updatedBy).toEqual({ kind: "user" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("normalizes malformed ticket actors", async () => {
|
||||||
|
const { gw } = gateway({
|
||||||
|
id: "issue-1",
|
||||||
|
ref: "#12",
|
||||||
|
number: 12,
|
||||||
|
title: "Legacy",
|
||||||
|
description: "",
|
||||||
|
status: "open",
|
||||||
|
priority: "medium",
|
||||||
|
createdBy: { kind: "agent" },
|
||||||
|
updatedBy: { kind: "unknown" },
|
||||||
|
createdAt: 1,
|
||||||
|
updatedAt: 1,
|
||||||
|
version: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const ticket = await gw.read("proj-1", "#12", true);
|
||||||
|
|
||||||
|
expect(ticket.createdBy).toEqual({ kind: "user" });
|
||||||
|
expect(ticket.updatedBy).toEqual({ kind: "user" });
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -55,7 +55,7 @@ describe("TauriTicketGateway invoke payloads", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("normalizes legacy ticket list rows that omit assignedAgentIds", async () => {
|
it("normalizes legacy ticket list rows that omit assignedAgentIds and createdBy", async () => {
|
||||||
invoke.mockResolvedValueOnce({
|
invoke.mockResolvedValueOnce({
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
@ -64,7 +64,6 @@ describe("TauriTicketGateway invoke payloads", () => {
|
|||||||
title: "Legacy row",
|
title: "Legacy row",
|
||||||
status: "open",
|
status: "open",
|
||||||
priority: "medium",
|
priority: "medium",
|
||||||
createdBy: { kind: "user" },
|
|
||||||
updatedAt: 1,
|
updatedAt: 1,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -73,6 +72,7 @@ describe("TauriTicketGateway invoke payloads", () => {
|
|||||||
const list = await new TauriTicketGateway().list("proj-1");
|
const list = await new TauriTicketGateway().list("proj-1");
|
||||||
|
|
||||||
expect(list.items[0]?.assignedAgentIds).toEqual([]);
|
expect(list.items[0]?.assignedAgentIds).toEqual([]);
|
||||||
|
expect(list.items[0]?.createdBy).toEqual({ kind: "user" });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("wraps ticket attachment commands in the request DTO", async () => {
|
it("wraps ticket attachment commands in the request DTO", async () => {
|
||||||
@ -118,7 +118,7 @@ describe("TauriTicketGateway invoke payloads", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("normalizes legacy ticket payloads that omit array fields", async () => {
|
it("normalizes legacy ticket payloads that omit array fields and actors", async () => {
|
||||||
invoke.mockResolvedValueOnce({
|
invoke.mockResolvedValueOnce({
|
||||||
id: "issue-1",
|
id: "issue-1",
|
||||||
ref: "#12",
|
ref: "#12",
|
||||||
@ -127,8 +127,6 @@ describe("TauriTicketGateway invoke payloads", () => {
|
|||||||
description: "",
|
description: "",
|
||||||
status: "open",
|
status: "open",
|
||||||
priority: "medium",
|
priority: "medium",
|
||||||
createdBy: { kind: "user" },
|
|
||||||
updatedBy: { kind: "user" },
|
|
||||||
createdAt: 1,
|
createdAt: 1,
|
||||||
updatedAt: 1,
|
updatedAt: 1,
|
||||||
version: 1,
|
version: 1,
|
||||||
@ -139,5 +137,29 @@ describe("TauriTicketGateway invoke payloads", () => {
|
|||||||
expect(ticket.links).toEqual([]);
|
expect(ticket.links).toEqual([]);
|
||||||
expect(ticket.assignedAgentIds).toEqual([]);
|
expect(ticket.assignedAgentIds).toEqual([]);
|
||||||
expect(ticket.attachments).toEqual([]);
|
expect(ticket.attachments).toEqual([]);
|
||||||
|
expect(ticket.createdBy).toEqual({ kind: "user" });
|
||||||
|
expect(ticket.updatedBy).toEqual({ kind: "user" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("normalizes malformed ticket actors", async () => {
|
||||||
|
invoke.mockResolvedValueOnce({
|
||||||
|
id: "issue-1",
|
||||||
|
ref: "#12",
|
||||||
|
number: 12,
|
||||||
|
title: "Legacy",
|
||||||
|
description: "",
|
||||||
|
status: "open",
|
||||||
|
priority: "medium",
|
||||||
|
createdBy: { kind: "agent" },
|
||||||
|
updatedBy: { kind: "unknown" },
|
||||||
|
createdAt: 1,
|
||||||
|
updatedAt: 1,
|
||||||
|
version: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const ticket = await new TauriTicketGateway().read("proj-1", "#12", true);
|
||||||
|
|
||||||
|
expect(ticket.createdBy).toEqual({ kind: "user" });
|
||||||
|
expect(ticket.updatedBy).toEqual({ kind: "user" });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -42,6 +42,18 @@ export function isTicketVersionConflict(error: unknown): boolean {
|
|||||||
return typeof message === "string" && message.includes("version conflict");
|
return typeof message === "string" && message.includes("version conflict");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LEGACY_TICKET_ACTOR: Ticket["createdBy"] = { kind: "user" };
|
||||||
|
|
||||||
|
function normalizeTicketActor(actor: unknown): Ticket["createdBy"] {
|
||||||
|
if (!actor || typeof actor !== "object") return LEGACY_TICKET_ACTOR;
|
||||||
|
const rec = actor as Record<string, unknown>;
|
||||||
|
if (rec.kind === "user" || rec.kind === "system") return { kind: rec.kind };
|
||||||
|
if (rec.kind === "agent" && typeof rec.agentId === "string" && rec.agentId) {
|
||||||
|
return { kind: "agent", agentId: rec.agentId };
|
||||||
|
}
|
||||||
|
return LEGACY_TICKET_ACTOR;
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeTicket(ticket: Ticket): Ticket {
|
function normalizeTicket(ticket: Ticket): Ticket {
|
||||||
return {
|
return {
|
||||||
...ticket,
|
...ticket,
|
||||||
@ -50,6 +62,8 @@ function normalizeTicket(ticket: Ticket): Ticket {
|
|||||||
? ticket.assignedAgentIds
|
? ticket.assignedAgentIds
|
||||||
: [],
|
: [],
|
||||||
attachments: Array.isArray(ticket.attachments) ? ticket.attachments : [],
|
attachments: Array.isArray(ticket.attachments) ? ticket.attachments : [],
|
||||||
|
createdBy: normalizeTicketActor(ticket.createdBy),
|
||||||
|
updatedBy: normalizeTicketActor(ticket.updatedBy),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,6 +76,7 @@ function normalizeTicketList(list: TicketList): TicketList {
|
|||||||
assignedAgentIds: Array.isArray(item.assignedAgentIds)
|
assignedAgentIds: Array.isArray(item.assignedAgentIds)
|
||||||
? item.assignedAgentIds
|
? item.assignedAgentIds
|
||||||
: [],
|
: [],
|
||||||
|
createdBy: normalizeTicketActor(item.createdBy),
|
||||||
}))
|
}))
|
||||||
: [],
|
: [],
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user