mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-05-31 12:58:07 +00:00
* fix: ignore stale run reconnect conflicts * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix: ignore stale run reconnect conflicts --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
124 lines
3.3 KiB
TypeScript
124 lines
3.3 KiB
TypeScript
import { afterEach, expect, test, vi } from "vitest";
|
|
|
|
import {
|
|
clearReconnectRun,
|
|
getAPIClient,
|
|
isInactiveRunStreamError,
|
|
} from "@/core/api/api-client";
|
|
|
|
function makeSessionStorage() {
|
|
const values = new Map<string, string>();
|
|
return {
|
|
getItem: vi.fn((key: string) => values.get(key) ?? null),
|
|
removeItem: vi.fn((key: string) => {
|
|
values.delete(key);
|
|
}),
|
|
setItem: vi.fn((key: string, value: string) => {
|
|
values.set(key, value);
|
|
}),
|
|
};
|
|
}
|
|
|
|
afterEach(() => {
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
test("identifies inactive run stream errors", () => {
|
|
const error = Object.assign(
|
|
new Error(
|
|
'HTTP 409: {"detail":"Run run-1 is not active on this worker and cannot be streamed"}',
|
|
),
|
|
{ status: 409 },
|
|
);
|
|
|
|
expect(isInactiveRunStreamError(error)).toBe(true);
|
|
});
|
|
|
|
test("does not classify unrelated conflict errors as inactive streams", () => {
|
|
const error = Object.assign(new Error("HTTP 409: run is still active"), {
|
|
status: 409,
|
|
});
|
|
|
|
expect(isInactiveRunStreamError(error)).toBe(false);
|
|
});
|
|
|
|
test("clears matching reconnect metadata", () => {
|
|
const sessionStorage = makeSessionStorage();
|
|
sessionStorage.setItem("lg:stream:thread-1", "run-1");
|
|
vi.stubGlobal("window", { sessionStorage });
|
|
|
|
clearReconnectRun("thread-1", "run-1");
|
|
|
|
expect(sessionStorage.removeItem).toHaveBeenCalledWith("lg:stream:thread-1");
|
|
});
|
|
|
|
test("keeps newer reconnect metadata", () => {
|
|
const sessionStorage = makeSessionStorage();
|
|
sessionStorage.setItem("lg:stream:thread-1", "newer-run");
|
|
vi.stubGlobal("window", { sessionStorage });
|
|
|
|
clearReconnectRun("thread-1", "stale-run");
|
|
|
|
expect(sessionStorage.removeItem).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test("ignores reconnect metadata storage access failures", () => {
|
|
vi.stubGlobal("window", {
|
|
get sessionStorage() {
|
|
throw new DOMException("Blocked", "SecurityError");
|
|
},
|
|
});
|
|
|
|
expect(() => clearReconnectRun("thread-1", "run-1")).not.toThrow();
|
|
});
|
|
|
|
test("clears stale reconnect metadata when join stream cannot be resumed", async () => {
|
|
const sessionStorage = makeSessionStorage();
|
|
sessionStorage.setItem("lg:stream:thread-1", "run-1");
|
|
vi.stubGlobal("window", {
|
|
location: { origin: "http://localhost:2026" },
|
|
sessionStorage,
|
|
});
|
|
vi.stubGlobal(
|
|
"fetch",
|
|
vi.fn(async () => {
|
|
return new Response(
|
|
JSON.stringify({
|
|
detail:
|
|
"Run run-1 is not active on this worker and cannot be streamed",
|
|
}),
|
|
{ status: 409 },
|
|
);
|
|
}),
|
|
);
|
|
|
|
await expect(
|
|
getAPIClient(true).runs.joinStream("thread-1", "run-1").next(),
|
|
).resolves.toMatchObject({ done: true });
|
|
|
|
expect(sessionStorage.removeItem).toHaveBeenCalledWith("lg:stream:thread-1");
|
|
});
|
|
|
|
test("rethrows unrelated streaming errors", async () => {
|
|
const sessionStorage = makeSessionStorage();
|
|
sessionStorage.setItem("lg:stream:thread-1", "run-1");
|
|
vi.stubGlobal("window", {
|
|
location: { origin: "http://localhost:2026" },
|
|
sessionStorage,
|
|
});
|
|
vi.stubGlobal(
|
|
"fetch",
|
|
vi.fn(async () => {
|
|
return new Response(JSON.stringify({ detail: "run is still active" }), {
|
|
status: 409,
|
|
});
|
|
}),
|
|
);
|
|
|
|
await expect(
|
|
getAPIClient(true).runs.joinStream("thread-1", "run-1").next(),
|
|
).rejects.toThrow("HTTP 409");
|
|
|
|
expect(sessionStorage.removeItem).not.toHaveBeenCalled();
|
|
});
|