fix(chat): distinguish auth outages from model failures

Runtime model checks previously mapped every authenticated endpoint failure to models_unavailable. Treat request failures as service outages and retry failed checks after five seconds; empty model configurations remain distinct.
This commit is contained in:
2026-08-06 19:35:24 +08:00
parent 0a47534ddb
commit 0dd521d8c9
2 changed files with 67 additions and 10 deletions
+46 -1
View File
@@ -6,6 +6,7 @@ import { GlobalChatbox } from "./GlobalChatbox";
const createSession = jest.fn();
const mockFetchAgentRuntimeHealth = jest.fn();
const mockFetchAgentModels = jest.fn();
let mockCurrentProjectId = "project-1";
jest.mock("@refinedev/core", () => ({
@@ -13,7 +14,7 @@ jest.mock("@refinedev/core", () => ({
}));
jest.mock("@/lib/chatModels", () => ({
fetchAgentModels: jest.fn(() => new Promise(() => {})),
fetchAgentModels: (...args: unknown[]) => mockFetchAgentModels(...args),
}));
jest.mock("@/lib/agentRuntime", () => ({
@@ -100,12 +101,15 @@ describe("GlobalChatbox lifecycle", () => {
createSession.mockClear();
mockFetchAgentRuntimeHealth.mockReset();
mockFetchAgentRuntimeHealth.mockImplementation(() => new Promise(() => {}));
mockFetchAgentModels.mockReset();
mockFetchAgentModels.mockImplementation(() => new Promise(() => {}));
mockCurrentProjectId = "project-1";
});
afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
jest.restoreAllMocks();
});
it("keeps content mounted and preserves the session across close and reopen", async () => {
@@ -146,4 +150,45 @@ describe("GlobalChatbox lifecycle", () => {
expect(screen.getByText("Workspace state: unavailable")).toBeInTheDocument();
});
it("reserves models unavailable for an empty model configuration", async () => {
mockFetchAgentRuntimeHealth.mockResolvedValueOnce(true);
mockFetchAgentModels.mockResolvedValueOnce({ models: [] });
render(<GlobalChatbox open onClose={jest.fn()} />);
await act(async () => {
await Promise.resolve();
await Promise.resolve();
});
expect(screen.getByText("Workspace state: models_unavailable")).toBeInTheDocument();
});
it("reports an auth dependency failure as unavailable and retries quickly", async () => {
jest.spyOn(console, "error").mockImplementation(() => undefined);
mockFetchAgentRuntimeHealth.mockResolvedValue(true);
mockFetchAgentModels
.mockRejectedValueOnce(new Error("authentication service unavailable"))
.mockResolvedValueOnce({
defaultModel: "provider/model",
models: [{ id: "provider/model", label: "Model" }],
});
render(<GlobalChatbox open onClose={jest.fn()} />);
await act(async () => {
await Promise.resolve();
await Promise.resolve();
});
expect(screen.getByText("Workspace state: unavailable")).toBeInTheDocument();
expect(mockFetchAgentModels).toHaveBeenCalledTimes(1);
await act(async () => {
jest.advanceTimersByTime(5_000);
await Promise.resolve();
await Promise.resolve();
});
expect(screen.getByText("Workspace state: ready")).toBeInTheDocument();
expect(mockFetchAgentModels).toHaveBeenCalledTimes(2);
});
});