-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathhttpErrorHandling.test.js
More file actions
102 lines (91 loc) · 3.37 KB
/
Copy pathhttpErrorHandling.test.js
File metadata and controls
102 lines (91 loc) · 3.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// Offline tests for HTTP/JSON-RPC error handling: error:null successes,
// non-JSON error bodies, and enveloped-but-empty responses must surface
// useful diagnostics instead of TypeErrors or JSON parse errors.
const ak = require("../../dist/index.cjs");
function mockFetch(status, body, statusText = "") {
return async () => ({
ok: status >= 200 && status < 300,
status,
statusText,
text: async () => (typeof body === "string" ? body : JSON.stringify(body)),
json: async () => JSON.parse(typeof body === "string" ? body : JSON.stringify(body)),
});
}
function transportWith(status, body, statusText) {
return new ak.HttpTransport("https://example.test/rpc", {
fetch: mockFetch(status, body, statusText),
});
}
describe("HttpTransport error handling", () => {
test("treats error:null alongside result as success", async () => {
const t = transportWith(200, { jsonrpc: "2.0", id: 1, result: "0x1", error: null });
expect(await t.request({ method: "eth_chainId", params: [] })).toBe("0x1");
});
test("surfaces the HTTP status for a non-JSON error body", async () => {
const t = transportWith(502, "<html>Bad Gateway</html>", "Bad Gateway");
await expect(t.request({ method: "eth_chainId", params: [] })).rejects.toMatchObject({
name: "TransportRpcError",
message: expect.stringContaining("502"),
});
});
test("surfaces the HTTP status for a JSON error body without an envelope", async () => {
const t = transportWith(401, { message: "unauthorized" }, "Unauthorized");
await expect(t.request({ method: "eth_chainId", params: [] })).rejects.toMatchObject({
name: "TransportRpcError",
message: expect.stringContaining("401"),
});
});
test("still reports the server's JSON-RPC error from an HTTP failure", async () => {
const t = transportWith(429, {
jsonrpc: "2.0",
id: 1,
error: { code: -32005, message: "rate limited" },
});
await expect(t.request({ method: "eth_chainId", params: [] })).rejects.toMatchObject({
code: -32005,
message: "rate limited",
});
});
});
describe("sendJsonRpcRequest error handling", () => {
const realFetch = globalThis.fetch;
afterEach(() => {
globalThis.fetch = realFetch;
});
test("surfaces the HTTP status instead of crashing on a non-envelope body", async () => {
globalThis.fetch = mockFetch(401, { message: "unauthorized" }, "Unauthorized");
await expect(
ak.sendJsonRpcRequest("https://example.test/rpc", "eth_chainId", []),
).rejects.toMatchObject({
name: "TransportRpcError",
message: expect.stringContaining("401"),
});
});
test("surfaces the HTTP status for a non-JSON body", async () => {
globalThis.fetch = mockFetch(502, "<html>Bad Gateway</html>", "Bad Gateway");
await expect(
ak.sendJsonRpcRequest("https://example.test/rpc", "eth_chainId", []),
).rejects.toMatchObject({
name: "TransportRpcError",
message: expect.stringContaining("502"),
});
});
test.each([['"ok"'], ["null"], ["42"]])(
"reports a scalar/null JSON payload (%s) as malformed instead of a TypeError",
async (body) => {
globalThis.fetch = async () => ({
ok: true,
status: 200,
statusText: "OK",
text: async () => body,
json: async () => JSON.parse(body),
});
await expect(
ak.sendJsonRpcRequest("https://example.test/rpc", "eth_chainId", []),
).rejects.toMatchObject({
name: "TransportRpcError",
message: expect.stringContaining("malformed"),
});
},
);
});