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 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 {
|
||||
return {
|
||||
...ticket,
|
||||
@ -294,6 +304,8 @@ export class HttpTicketGateway implements TicketGateway {
|
||||
? ticket.assignedAgentIds
|
||||
: [],
|
||||
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)
|
||||
? 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({
|
||||
items: [
|
||||
{
|
||||
@ -64,7 +64,6 @@ describe("TauriTicketGateway invoke payloads", () => {
|
||||
title: "Legacy row",
|
||||
status: "open",
|
||||
priority: "medium",
|
||||
createdBy: { kind: "user" },
|
||||
updatedAt: 1,
|
||||
},
|
||||
],
|
||||
@ -73,6 +72,7 @@ describe("TauriTicketGateway invoke payloads", () => {
|
||||
const list = await new TauriTicketGateway().list("proj-1");
|
||||
|
||||
expect(list.items[0]?.assignedAgentIds).toEqual([]);
|
||||
expect(list.items[0]?.createdBy).toEqual({ kind: "user" });
|
||||
});
|
||||
|
||||
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({
|
||||
id: "issue-1",
|
||||
ref: "#12",
|
||||
@ -127,8 +127,6 @@ describe("TauriTicketGateway invoke payloads", () => {
|
||||
description: "",
|
||||
status: "open",
|
||||
priority: "medium",
|
||||
createdBy: { kind: "user" },
|
||||
updatedBy: { kind: "user" },
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
version: 1,
|
||||
@ -139,5 +137,29 @@ describe("TauriTicketGateway invoke payloads", () => {
|
||||
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 () => {
|
||||
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");
|
||||
}
|
||||
|
||||
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 {
|
||||
return {
|
||||
...ticket,
|
||||
@ -50,6 +62,8 @@ function normalizeTicket(ticket: Ticket): Ticket {
|
||||
? ticket.assignedAgentIds
|
||||
: [],
|
||||
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)
|
||||
? item.assignedAgentIds
|
||||
: [],
|
||||
createdBy: normalizeTicketActor(item.createdBy),
|
||||
}))
|
||||
: [],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user