fix(api): a write that hit a connection failure is still reported, and every failure is reported once
Deep-review round 4, the "one report per failure" contract round 3 introduced. - `req` marked gateway (502/503/504) and network failures "already reported" because they raise the connection notice. But that notice is about connectivity and removes itself on the very next answer — which every caller's own resync refetch fetches at once — so a delete that hit a keepalive reset flashed the notice and was then reported nowhere, the list still there. Only the durable reports (the error dialog, the 401 sign-out) count now; the caller says the action failed. - A connection notice the user closed swallowed every later one for the rest of the visit; it is raised again. - `req` and `uploadReq` share one failure policy (`reportFailure`). The upload copy had drifted: a 502 opened the blocking "Server error (502)" dialog and cleared a notice that was still true. - A taken Plex collection name is a structured 409, localized, and the create/rename calls are quiet: it was an English dialog plus a Hungarian toast. `notifyCollectionEditError` is gone. - The mutation onErrors that toasted unconditionally after a non-quiet request (invite approve / deny / add, demo whitelist, audit clear, scheduler, share, full history, edit, config tests, tag attach) go through `notifyActionError`; the playlist delete's hand-rolled copy of it too (it takes a level now). The user role / suspend / delete actions had no onError at all: a user deleted in another tab failed silently and stayed listed — now said, and the list refetched.
This commit is contained in:
@@ -5,28 +5,37 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
|
||||
// Spy on the toast sink so we can assert exactly what each error maps to.
|
||||
vi.mock("./notifications", () => ({ notify: vi.fn(() => 0) }));
|
||||
vi.mock("./errorDialog", () => ({ reportError: vi.fn() }));
|
||||
|
||||
import { HttpError, api } from "./api";
|
||||
import { notify } from "./notifications";
|
||||
import { notifyActionError, notifyCollectionEditError } from "./actionErrors";
|
||||
import { reportError } from "./errorDialog";
|
||||
import { notifyActionError } from "./actionErrors";
|
||||
import i18n from "../i18n";
|
||||
|
||||
const notifyMock = vi.mocked(notify);
|
||||
beforeEach(() => notifyMock.mockClear());
|
||||
beforeEach(() => {
|
||||
notifyMock.mockClear();
|
||||
vi.mocked(reportError).mockClear();
|
||||
});
|
||||
|
||||
describe("notifyCollectionEditError", () => {
|
||||
it("maps a backend 409 to the friendly nameTaken message (not the raw server detail)", () => {
|
||||
notifyCollectionEditError(new HttpError(409, "already exists"), "EDIT_FAILED", "NAME_TAKEN");
|
||||
expect(notifyMock).toHaveBeenCalledWith({ level: "error", message: "NAME_TAKEN" });
|
||||
});
|
||||
|
||||
it("falls back to the server detail for a non-409 HttpError", () => {
|
||||
notifyCollectionEditError(new HttpError(500, "server boom"), "EDIT_FAILED", "NAME_TAKEN");
|
||||
expect(notifyMock).toHaveBeenCalledWith({ level: "error", message: "server boom" });
|
||||
});
|
||||
|
||||
it("falls back to the generic edit-failed message for a non-HttpError", () => {
|
||||
notifyCollectionEditError(new Error("network"), "EDIT_FAILED", "NAME_TAKEN");
|
||||
expect(notifyMock).toHaveBeenCalledWith({ level: "error", message: "EDIT_FAILED" });
|
||||
describe("a Plex collection name that is already taken", () => {
|
||||
it("is reported once, in the reader's language", async () => {
|
||||
// The 409 was a bare English string: the global error dialog showed it in English, and the
|
||||
// editor's own toast said the same thing again, translated (deep-review round 4).
|
||||
vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
||||
new Response(JSON.stringify({ detail: { code: "collection", reason: "name_taken" } }), {
|
||||
status: 409,
|
||||
headers: { "content-type": "application/json" },
|
||||
}),
|
||||
);
|
||||
const err = await api.plexCreateCollection("1", "Marvel", "m1").catch((e: unknown) => e);
|
||||
notifyMock.mockClear();
|
||||
notifyActionError(err, "EDIT_FAILED");
|
||||
expect(notifyMock.mock.calls).toEqual([
|
||||
[{ level: "error", message: i18n.t("plex.collection.nameTaken") }],
|
||||
]);
|
||||
expect(reportError).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user