ba58493909
Потребляет реальный контракт #487 на клиенте (без выдумывания кодов): - error-message.ts: ветки для 409 A_RUN_ALREADY_ACTIVE / SUPERSEDE_TARGET_MISMATCH / SUPERSEDE_TIMEOUT / SUPERSEDE_INVALID — человеческие тексты СТРОГО ДО generic- веток по статусу (иначе юзер видит сырой JSON {"code":"A_RUN_ALREADY_ACTIVE"}); - extractRunId(message): чтение runId из start-метаданных (зеркало extractServerChatId) — live-обновление run-факта для FSM; - getRun(chatId): POST /ai-chat/run — first-class run-факт с сервера (init на маунте + verify после supersede-mismatch). Плумбинг под FSM-обвязку коммитов 2–5. Тесты: классификатор (все 4 кода + order- guard), extractRunId. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
88 lines
2.8 KiB
TypeScript
88 lines
2.8 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import {
|
|
resolveAdoptedChatId,
|
|
newlyAddedChatIds,
|
|
extractServerChatId,
|
|
extractRunId,
|
|
} from "./adopt-chat-id";
|
|
|
|
describe("resolveAdoptedChatId", () => {
|
|
it("adopts the server id for a brand-new chat (activeChatId null + id)", () => {
|
|
expect(resolveAdoptedChatId(null, "chat-1")).toBe("chat-1");
|
|
});
|
|
|
|
it("returns null for an existing chat even with a server id", () => {
|
|
expect(resolveAdoptedChatId("chat-existing", "chat-1")).toBeNull();
|
|
});
|
|
|
|
it("returns null for a new chat with no server id", () => {
|
|
expect(resolveAdoptedChatId(null, undefined)).toBeNull();
|
|
expect(resolveAdoptedChatId(null, null)).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("newlyAddedChatIds", () => {
|
|
it("returns the single new id", () => {
|
|
expect([...newlyAddedChatIds(["a", "b"], ["a", "b", "c"])]).toEqual(["c"]);
|
|
});
|
|
|
|
it("returns an empty set when nothing was added", () => {
|
|
expect(newlyAddedChatIds(["a", "b"], ["b", "a"]).size).toBe(0);
|
|
});
|
|
|
|
it("returns both new ids when two were added", () => {
|
|
expect(newlyAddedChatIds(["a"], ["a", "b", "c"])).toEqual(
|
|
new Set(["b", "c"]),
|
|
);
|
|
});
|
|
|
|
it("keeps only the new id across an add+delete in the same window", () => {
|
|
// before [a,b] -> after [b,new]: a was deleted, new was added.
|
|
expect([...newlyAddedChatIds(["a", "b"], ["b", "new"])]).toEqual(["new"]);
|
|
});
|
|
|
|
it("dedupes a repeated new id to a single entry", () => {
|
|
expect(newlyAddedChatIds(["a"], ["a", "new", "new"])).toEqual(
|
|
new Set(["new"]),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("extractServerChatId", () => {
|
|
it("returns the chatId when present on metadata", () => {
|
|
expect(extractServerChatId({ metadata: { chatId: "chat-1" } })).toBe(
|
|
"chat-1",
|
|
);
|
|
});
|
|
|
|
it("returns undefined when the message has no metadata", () => {
|
|
expect(extractServerChatId({})).toBeUndefined();
|
|
});
|
|
|
|
it("returns undefined when metadata lacks chatId", () => {
|
|
expect(extractServerChatId({ metadata: { other: 1 } })).toBeUndefined();
|
|
});
|
|
|
|
it("returns undefined for a non-string chatId", () => {
|
|
expect(extractServerChatId({ metadata: { chatId: 42 } })).toBeUndefined();
|
|
});
|
|
|
|
it("returns undefined for an undefined message", () => {
|
|
expect(extractServerChatId(undefined)).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("extractRunId", () => {
|
|
it("reads a string runId from the start metadata", () => {
|
|
expect(extractRunId({ metadata: { runId: "run-1" } })).toBe("run-1");
|
|
});
|
|
it("returns undefined when runId is absent", () => {
|
|
expect(extractRunId({ metadata: { chatId: "c" } })).toBeUndefined();
|
|
expect(extractRunId({})).toBeUndefined();
|
|
expect(extractRunId(undefined)).toBeUndefined();
|
|
});
|
|
it("returns undefined for a non-string runId", () => {
|
|
expect(extractRunId({ metadata: { runId: 7 } })).toBeUndefined();
|
|
});
|
|
});
|