7df9ebf22c
Sync labels / sync (push) Failing after 1m9s
Publish Container Image / Build and publish container image (push) Has been cancelled
Deploy Website / Deploy to GitHub Pages (push) Has been cancelled
Deploy Website / Build website and demo (push) Has been cancelled
Deploy viewer / Deploy viewer proxy to Cloudflare Workers (push) Has been cancelled
Deploy tiles / Deploy planetary tile proxy to Cloudflare Workers (push) Has been cancelled
Deploy collab / Deploy collaboration relay to Cloudflare Workers (push) Has been cancelled
CI / Dependency audit (push) Has been cancelled
CI / E2E smoke (Playwright) (push) Has been cancelled
CI / Validate CITATION.cff (push) Has been cancelled
CI / Build and test (push) Has been cancelled
104 lines
4.0 KiB
TypeScript
104 lines
4.0 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { describe, it } from "node:test";
|
|
|
|
import { classifyFetchFailure } from "../apps/geolibre-desktop/src/lib/fetch-error";
|
|
|
|
describe("classifyFetchFailure", () => {
|
|
it("classifies an AbortError as an abort with no hint", () => {
|
|
const error = new DOMException("aborted", "AbortError");
|
|
const result = classifyFetchFailure(error);
|
|
assert.equal(result.kind, "abort");
|
|
assert.equal(result.hint, null);
|
|
});
|
|
|
|
it("classifies a TimeoutError DOMException as a timeout with a hint", () => {
|
|
const error = new DOMException("timed out", "TimeoutError");
|
|
const result = classifyFetchFailure(error);
|
|
assert.equal(result.kind, "timeout");
|
|
assert.ok(result.hint && result.hint.length > 0);
|
|
});
|
|
|
|
it("classifies a wrapped 'timed out' message as a timeout", () => {
|
|
const result = classifyFetchFailure(new Error("The request timed out."));
|
|
assert.equal(result.kind, "timeout");
|
|
});
|
|
|
|
it("classifies the browser 'Failed to fetch' TypeError as network/TLS/CORS", () => {
|
|
const result = classifyFetchFailure(new TypeError("Failed to fetch"));
|
|
assert.equal(result.kind, "network");
|
|
assert.equal(result.label, "network/TLS/CORS");
|
|
// The browser hint keeps the CORS / "try the desktop app" advice.
|
|
assert.ok(result.hint?.includes("CORS"));
|
|
assert.ok(result.hint?.includes("desktop app"));
|
|
});
|
|
|
|
it("classifies the WebKit 'Load failed' TypeError as network", () => {
|
|
assert.equal(classifyFetchFailure(new TypeError("Load failed")).kind, "network");
|
|
});
|
|
|
|
it("classifies a native reqwest TLS certificate error string as network without the CORS advice", () => {
|
|
const result = classifyFetchFailure(
|
|
"Request failed: error sending request: invalid peer certificate",
|
|
);
|
|
assert.equal(result.kind, "network");
|
|
assert.equal(result.label, "network");
|
|
// The native path already runs in the desktop app, so its hint drops the
|
|
// CORS / "try the desktop app" sentence.
|
|
assert.ok(result.hint);
|
|
assert.ok(!result.hint?.includes("CORS"));
|
|
assert.ok(!result.hint?.includes("desktop app"));
|
|
});
|
|
|
|
it("classifies native DNS/connection error strings as network", () => {
|
|
assert.equal(
|
|
classifyFetchFailure("Request failed: dns error: failed to lookup host")
|
|
.kind,
|
|
"network",
|
|
);
|
|
assert.equal(
|
|
classifyFetchFailure("Request failed: connection refused (os error 111)")
|
|
.kind,
|
|
"network",
|
|
);
|
|
});
|
|
|
|
it("does not misclassify a network failure whose URL has a 'timeout' query param", () => {
|
|
// The real cause is a refused connection; `timeout=30` is only a URL param,
|
|
// so stripping the embedded URL must keep this classified as network.
|
|
const result = classifyFetchFailure(
|
|
"error sending request for url (https://host/wfs?timeout=30): connection refused",
|
|
);
|
|
assert.equal(result.kind, "network");
|
|
});
|
|
|
|
it("still classifies a genuine native timeout message", () => {
|
|
assert.equal(
|
|
classifyFetchFailure("error sending request: operation timed out").kind,
|
|
"timeout",
|
|
);
|
|
});
|
|
|
|
it("does not misclassify a non-network error whose URL contains a network word", () => {
|
|
// "connect" appears only as a URL path substring, not as a real error cause,
|
|
// so the tightened phrase matching must leave it unclassified.
|
|
const result = classifyFetchFailure(
|
|
"error decoding response body for url (https://host/api/connect): invalid json",
|
|
);
|
|
assert.equal(result.kind, "unknown");
|
|
assert.equal(result.hint, null);
|
|
});
|
|
|
|
it("leaves an unrecognized error as unknown with no hint", () => {
|
|
const result = classifyFetchFailure(
|
|
new Error("Request failed with status 500"),
|
|
);
|
|
assert.equal(result.kind, "unknown");
|
|
assert.equal(result.hint, null);
|
|
});
|
|
|
|
it("reads the message from a non-Error, non-DOMException value safely", () => {
|
|
assert.equal(classifyFetchFailure({}).kind, "unknown");
|
|
assert.equal(classifyFetchFailure(undefined).kind, "unknown");
|
|
});
|
|
});
|