ocpp-ws-io 1.0.0-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ocpp-ws-io might be problematic. Click here for more details.

Files changed (45) hide show
  1. package/.github/workflows/publish.yml +52 -0
  2. package/LICENSE +21 -0
  3. package/README.md +773 -0
  4. package/dist/adapters/redis.d.mts +73 -0
  5. package/dist/adapters/redis.d.ts +73 -0
  6. package/dist/adapters/redis.js +96 -0
  7. package/dist/adapters/redis.js.map +1 -0
  8. package/dist/adapters/redis.mjs +71 -0
  9. package/dist/adapters/redis.mjs.map +1 -0
  10. package/dist/index.d.mts +268 -0
  11. package/dist/index.d.ts +268 -0
  12. package/dist/index.js +38919 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/index.mjs +38855 -0
  15. package/dist/index.mjs.map +1 -0
  16. package/dist/types-6LVUoXof.d.mts +284 -0
  17. package/dist/types-6LVUoXof.d.ts +284 -0
  18. package/package.json +59 -0
  19. package/src/adapters/adapter.ts +40 -0
  20. package/src/adapters/redis.ts +144 -0
  21. package/src/client.ts +882 -0
  22. package/src/errors.ts +183 -0
  23. package/src/event-buffer.ts +73 -0
  24. package/src/index.ts +68 -0
  25. package/src/queue.ts +65 -0
  26. package/src/schemas/ocpp1_6.json +2376 -0
  27. package/src/schemas/ocpp2_0_1.json +11878 -0
  28. package/src/schemas/ocpp2_1.json +23176 -0
  29. package/src/server-client.ts +65 -0
  30. package/src/server.ts +374 -0
  31. package/src/standard-validators.ts +18 -0
  32. package/src/types.ts +316 -0
  33. package/src/util.ts +119 -0
  34. package/src/validator.ts +148 -0
  35. package/src/ws-util.ts +186 -0
  36. package/test/adapter.test.ts +88 -0
  37. package/test/client.test.ts +297 -0
  38. package/test/errors.test.ts +132 -0
  39. package/test/queue.test.ts +133 -0
  40. package/test/server.test.ts +274 -0
  41. package/test/util.test.ts +103 -0
  42. package/test/ws-util.test.ts +93 -0
  43. package/tsconfig.json +25 -0
  44. package/tsup.config.ts +16 -0
  45. package/vitest.config.ts +10 -0
package/src/ws-util.ts ADDED
@@ -0,0 +1,186 @@
1
+ /**
2
+ * WebSocket utility functions for OCPP handshake handling.
3
+ *
4
+ * Subprotocol parsing follows RFC 6455 Section 4.1 and
5
+ * HTTP token rules from RFC 7230 Section 3.2.6.
6
+ * Close code validation per RFC 6455 Section 7.4.
7
+ */
8
+
9
+ import http from "node:http";
10
+ import type { Duplex } from "node:stream";
11
+
12
+ // ─── Subprotocol Parsing ────────────────────────────────────────
13
+
14
+ /**
15
+ * Determine if a character code is a valid HTTP token character.
16
+ * RFC 7230 Section 3.2.6: tchar = "!" / "#" / "$" / "%" / "&" / "'" /
17
+ * "*" / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
18
+ */
19
+ function isTChar(c: number): boolean {
20
+ // ALPHA (A-Z, a-z)
21
+ if ((c >= 0x41 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a)) return true;
22
+ // DIGIT (0-9)
23
+ if (c >= 0x30 && c <= 0x39) return true;
24
+ // Special tchar symbols
25
+ switch (c) {
26
+ case 0x21: // !
27
+ case 0x23: // #
28
+ case 0x24: // $
29
+ case 0x25: // %
30
+ case 0x26: // &
31
+ case 0x27: // '
32
+ case 0x2a: // *
33
+ case 0x2b: // +
34
+ case 0x2d: // -
35
+ case 0x2e: // .
36
+ case 0x5e: // ^
37
+ case 0x5f: // _
38
+ case 0x60: // `
39
+ case 0x7c: // |
40
+ case 0x7e: // ~
41
+ return true;
42
+ }
43
+ return false;
44
+ }
45
+
46
+ /**
47
+ * Parse the `Sec-WebSocket-Protocol` header into a Set of protocol names.
48
+ *
49
+ * Implements RFC 6455 Section 4.2.1 grammar for the header value:
50
+ * protocol-list = 1#token
51
+ * (see RFC 7230 Section 7 for the #rule list extension)
52
+ *
53
+ * Whitespace (SP/HTAB) is allowed around commas as per HTTP list rules.
54
+ * Duplicate protocol names and invalid token characters cause a SyntaxError.
55
+ */
56
+ export function parseSubprotocols(header: string): Set<string> {
57
+ if (header.length === 0) {
58
+ throw new SyntaxError("Unexpected end of input");
59
+ }
60
+
61
+ const protocols = new Set<string>();
62
+ let cursor = 0;
63
+
64
+ while (cursor < header.length) {
65
+ // Skip leading whitespace before token
66
+ while (
67
+ cursor < header.length &&
68
+ (header.charCodeAt(cursor) === 0x20 || header.charCodeAt(cursor) === 0x09)
69
+ ) {
70
+ cursor++;
71
+ }
72
+
73
+ // Expect at least one token character
74
+ const tokenStart = cursor;
75
+ while (cursor < header.length && isTChar(header.charCodeAt(cursor))) {
76
+ cursor++;
77
+ }
78
+
79
+ if (cursor === tokenStart) {
80
+ throw new SyntaxError(`Unexpected character at index ${cursor}`);
81
+ }
82
+
83
+ const token = header.substring(tokenStart, cursor);
84
+
85
+ if (protocols.has(token)) {
86
+ throw new SyntaxError(`The "${token}" subprotocol is duplicated`);
87
+ }
88
+ protocols.add(token);
89
+
90
+ // Skip trailing whitespace after token
91
+ while (
92
+ cursor < header.length &&
93
+ (header.charCodeAt(cursor) === 0x20 || header.charCodeAt(cursor) === 0x09)
94
+ ) {
95
+ cursor++;
96
+ }
97
+
98
+ // Expect end of string or comma separator
99
+ if (cursor >= header.length) break;
100
+
101
+ if (header.charCodeAt(cursor) !== 0x2c /* , */) {
102
+ throw new SyntaxError(`Unexpected character at index ${cursor}`);
103
+ }
104
+ cursor++; // consume comma
105
+
106
+ // After a comma, there must be another token — trailing comma is invalid
107
+ // (We'll check at the start of the next iteration)
108
+ // Peek ahead: if only whitespace remains, it's a trailing comma
109
+ let peek = cursor;
110
+ while (
111
+ peek < header.length &&
112
+ (header.charCodeAt(peek) === 0x20 || header.charCodeAt(peek) === 0x09)
113
+ ) {
114
+ peek++;
115
+ }
116
+ if (peek >= header.length || !isTChar(header.charCodeAt(peek))) {
117
+ throw new SyntaxError("Unexpected end of input");
118
+ }
119
+ }
120
+
121
+ // Ensure we actually got at least one protocol
122
+ if (protocols.size === 0) {
123
+ throw new SyntaxError("Unexpected end of input");
124
+ }
125
+
126
+ return protocols;
127
+ }
128
+
129
+ // ─── Close Code Validation ──────────────────────────────────────
130
+
131
+ /**
132
+ * Reserved close codes that MUST NOT be set in a Close frame.
133
+ * Per RFC 6455 Section 7.4.1.
134
+ */
135
+ const RESERVED_CLOSE_CODES = new Set([1004, 1005, 1006]);
136
+
137
+ /**
138
+ * Check if a WebSocket close status code is valid for use in a Close frame.
139
+ *
140
+ * Per RFC 6455 Section 7.4:
141
+ * - 1000–1014 are valid (except 1004, 1005, 1006 which are reserved)
142
+ * - 3000–4999 are available for application/library/framework use
143
+ */
144
+ export function isValidStatusCode(code: number): boolean {
145
+ if (code >= 1000 && code <= 1014 && !RESERVED_CLOSE_CODES.has(code)) {
146
+ return true;
147
+ }
148
+ if (code >= 3000 && code <= 4999) {
149
+ return true;
150
+ }
151
+ return false;
152
+ }
153
+
154
+ // ─── Handshake Abort ────────────────────────────────────────────
155
+
156
+ /**
157
+ * Reject a WebSocket upgrade by sending an HTTP error response
158
+ * to the raw socket and closing the connection.
159
+ */
160
+ export function abortHandshake(
161
+ socket: Duplex,
162
+ statusCode: number,
163
+ reason?: string,
164
+ extraHeaders?: Record<string, string>,
165
+ ): void {
166
+ if (!socket.writable) return;
167
+
168
+ const statusText = http.STATUS_CODES[statusCode] ?? "Unknown";
169
+ const body = reason ?? statusText;
170
+ const bodyBytes = Buffer.byteLength(body, "utf8");
171
+
172
+ const allHeaders: Record<string, string | number> = {
173
+ Connection: "close",
174
+ "Content-Type": "text/plain; charset=utf-8",
175
+ "Content-Length": bodyBytes,
176
+ ...extraHeaders,
177
+ };
178
+
179
+ const headerBlock = Object.entries(allHeaders)
180
+ .map(([k, v]) => `${k}: ${v}`)
181
+ .join("\r\n");
182
+
183
+ socket.end(
184
+ `HTTP/1.1 ${statusCode} ${statusText}\r\n${headerBlock}\r\n\r\n${body}`,
185
+ );
186
+ }
@@ -0,0 +1,88 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { InMemoryAdapter } from "../src/adapters/adapter.js";
3
+
4
+ describe("InMemoryAdapter", () => {
5
+ it("should publish and receive messages", async () => {
6
+ const adapter = new InMemoryAdapter();
7
+ let received: unknown;
8
+
9
+ await adapter.subscribe("test", (data) => {
10
+ received = data;
11
+ });
12
+
13
+ await adapter.publish("test", { hello: "world" });
14
+ expect(received).toEqual({ hello: "world" });
15
+ });
16
+
17
+ it("should support multiple subscribers", async () => {
18
+ const adapter = new InMemoryAdapter();
19
+ const received: unknown[] = [];
20
+
21
+ await adapter.subscribe("ch", (data) => received.push(`a:${data}`));
22
+ await adapter.subscribe("ch", (data) => received.push(`b:${data}`));
23
+
24
+ await adapter.publish("ch", "msg1");
25
+ expect(received).toEqual(["a:msg1", "b:msg1"]);
26
+ });
27
+
28
+ it("should not deliver to unrelated channels", async () => {
29
+ const adapter = new InMemoryAdapter();
30
+ let received = false;
31
+
32
+ await adapter.subscribe("channelA", () => {
33
+ received = true;
34
+ });
35
+ await adapter.publish("channelB", "data");
36
+
37
+ expect(received).toBe(false);
38
+ });
39
+
40
+ it("should unsubscribe from a channel", async () => {
41
+ const adapter = new InMemoryAdapter();
42
+ let count = 0;
43
+
44
+ await adapter.subscribe("ch", () => {
45
+ count++;
46
+ });
47
+ await adapter.publish("ch", "msg1");
48
+ expect(count).toBe(1);
49
+
50
+ await adapter.unsubscribe("ch");
51
+ await adapter.publish("ch", "msg2");
52
+ expect(count).toBe(1); // Still 1 after unsubscribe
53
+ });
54
+
55
+ it("should clear all channels on disconnect", async () => {
56
+ const adapter = new InMemoryAdapter();
57
+ let count = 0;
58
+
59
+ await adapter.subscribe("ch1", () => {
60
+ count++;
61
+ });
62
+ await adapter.subscribe("ch2", () => {
63
+ count++;
64
+ });
65
+
66
+ await adapter.disconnect();
67
+
68
+ await adapter.publish("ch1", "x");
69
+ await adapter.publish("ch2", "x");
70
+ expect(count).toBe(0);
71
+ });
72
+
73
+ it("should swallow handler errors silently", async () => {
74
+ const adapter = new InMemoryAdapter();
75
+ let secondCalled = false;
76
+
77
+ await adapter.subscribe("ch", () => {
78
+ throw new Error("oops");
79
+ });
80
+ await adapter.subscribe("ch", () => {
81
+ secondCalled = true;
82
+ });
83
+
84
+ // Should not throw
85
+ await adapter.publish("ch", "data");
86
+ expect(secondCalled).toBe(true);
87
+ });
88
+ });
@@ -0,0 +1,297 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { OCPPServer } from "../src/server.js";
3
+ import { OCPPClient } from "../src/client.js";
4
+ import { SecurityProfile } from "../src/types.js";
5
+ import type { OCPPServerClient } from "../src/server-client.js";
6
+
7
+ let server: OCPPServer;
8
+ let client: OCPPClient;
9
+ let port: number;
10
+
11
+ const getPort = (srv: import("node:http").Server): number => {
12
+ const addr = srv.address();
13
+ if (addr && typeof addr !== "string") return addr.port;
14
+ return 0;
15
+ };
16
+
17
+ describe("OCPPClient", () => {
18
+ beforeEach(async () => {
19
+ server = new OCPPServer({ protocols: ["ocpp1.6"] });
20
+ server.auth((accept, _reject, _handshake) => {
21
+ accept({ protocol: "ocpp1.6" });
22
+ });
23
+ const httpServer = await server.listen(0);
24
+ port = getPort(httpServer);
25
+ });
26
+
27
+ afterEach(async () => {
28
+ if (client) await client.close({ force: true }).catch(() => {});
29
+ await server.close({ force: true });
30
+ });
31
+
32
+ it("should throw if identity is missing", () => {
33
+ expect(
34
+ () =>
35
+ new OCPPClient({
36
+ identity: "",
37
+ endpoint: "ws://localhost:9999",
38
+ }),
39
+ ).toThrow("identity is required");
40
+ });
41
+
42
+ it("should be in CLOSED state initially", () => {
43
+ client = new OCPPClient({
44
+ identity: "CS001",
45
+ endpoint: `ws://localhost:${port}`,
46
+ protocols: ["ocpp1.6"],
47
+ reconnect: false,
48
+ });
49
+ expect(client.state).toBe(OCPPClient.CLOSED);
50
+ });
51
+
52
+ it("should connect successfully", async () => {
53
+ client = new OCPPClient({
54
+ identity: "CS001",
55
+ endpoint: `ws://localhost:${port}`,
56
+ protocols: ["ocpp1.6"],
57
+ reconnect: false,
58
+ });
59
+
60
+ await client.connect();
61
+ expect(client.state).toBe(OCPPClient.OPEN);
62
+ expect(client.protocol).toBe("ocpp1.6");
63
+ });
64
+
65
+ it('should emit "open" event on connect', async () => {
66
+ client = new OCPPClient({
67
+ identity: "CS001",
68
+ endpoint: `ws://localhost:${port}`,
69
+ protocols: ["ocpp1.6"],
70
+ reconnect: false,
71
+ });
72
+
73
+ let opened = false;
74
+ client.on("open", () => {
75
+ opened = true;
76
+ });
77
+ await client.connect();
78
+ expect(opened).toBe(true);
79
+ });
80
+
81
+ it("should reject connect when already connected", async () => {
82
+ client = new OCPPClient({
83
+ identity: "CS001",
84
+ endpoint: `ws://localhost:${port}`,
85
+ protocols: ["ocpp1.6"],
86
+ reconnect: false,
87
+ });
88
+
89
+ await client.connect();
90
+ await expect(client.connect()).rejects.toThrow("Cannot connect");
91
+ });
92
+
93
+ it("should close gracefully", async () => {
94
+ client = new OCPPClient({
95
+ identity: "CS001",
96
+ endpoint: `ws://localhost:${port}`,
97
+ protocols: ["ocpp1.6"],
98
+ reconnect: false,
99
+ });
100
+
101
+ await client.connect();
102
+ const result = await client.close();
103
+ expect(result.code).toBe(1000);
104
+ expect(client.state).toBe(OCPPClient.CLOSED);
105
+ });
106
+
107
+ it("should handle RPC call and response", async () => {
108
+ // Set up server handler BEFORE client connects
109
+ server.on("client", (serverClient: OCPPServerClient) => {
110
+ serverClient.handle("BootNotification", async () => {
111
+ return {
112
+ status: "Accepted",
113
+ currentTime: new Date().toISOString(),
114
+ interval: 300,
115
+ };
116
+ });
117
+ });
118
+
119
+ client = new OCPPClient({
120
+ identity: "CS001",
121
+ endpoint: `ws://localhost:${port}`,
122
+ protocols: ["ocpp1.6"],
123
+ reconnect: false,
124
+ });
125
+
126
+ await client.connect();
127
+
128
+ const result = await client.call<{ status: string }>("BootNotification", {
129
+ chargePointModel: "TestModel",
130
+ chargePointVendor: "TestVendor",
131
+ });
132
+
133
+ expect(result.status).toBe("Accepted");
134
+ });
135
+
136
+ it("should receive NotImplemented for unhandled calls", async () => {
137
+ // Server has no handler for UnhandledAction, so it returns NotImplemented
138
+ client = new OCPPClient({
139
+ identity: "CS001",
140
+ endpoint: `ws://localhost:${port}`,
141
+ protocols: ["ocpp1.6"],
142
+ reconnect: false,
143
+ callTimeoutMs: 2000,
144
+ });
145
+
146
+ await client.connect();
147
+
148
+ await expect(client.call("UnhandledAction", {})).rejects.toThrow(
149
+ /not known|NotImplemented/,
150
+ );
151
+ });
152
+
153
+ it("should support abort signal on calls", async () => {
154
+ client = new OCPPClient({
155
+ identity: "CS001",
156
+ endpoint: `ws://localhost:${port}`,
157
+ protocols: ["ocpp1.6"],
158
+ reconnect: false,
159
+ });
160
+
161
+ await client.connect();
162
+
163
+ const ac = new AbortController();
164
+ const callPromise = client.call("SlowAction", {}, { signal: ac.signal });
165
+ ac.abort();
166
+
167
+ await expect(callPromise).rejects.toThrow();
168
+ });
169
+
170
+ it("should receive calls from server", async () => {
171
+ let receivedMethod = "";
172
+
173
+ client = new OCPPClient({
174
+ identity: "CS001",
175
+ endpoint: `ws://localhost:${port}`,
176
+ protocols: ["ocpp1.6"],
177
+ reconnect: false,
178
+ });
179
+
180
+ client.handle("Reset", async (ctx) => {
181
+ receivedMethod = ctx.method;
182
+ return { status: "Accepted" };
183
+ });
184
+
185
+ // Set up server handler BEFORE connecting
186
+ const serverCallPromise = new Promise<void>((resolve, reject) => {
187
+ server.on("client", async (serverClient: OCPPServerClient) => {
188
+ try {
189
+ const result = await serverClient.call<{ status: string }>("Reset", {
190
+ type: "Hard",
191
+ });
192
+ expect(result.status).toBe("Accepted");
193
+ resolve();
194
+ } catch (e) {
195
+ reject(e);
196
+ }
197
+ });
198
+ });
199
+
200
+ await client.connect();
201
+ await serverCallPromise;
202
+ expect(receivedMethod).toBe("Reset");
203
+ });
204
+
205
+ it("should handle wildcard handlers", async () => {
206
+ let wildcardMethod = "";
207
+
208
+ client = new OCPPClient({
209
+ identity: "CS002",
210
+ endpoint: `ws://localhost:${port}`,
211
+ protocols: ["ocpp1.6"],
212
+ reconnect: false,
213
+ });
214
+
215
+ client.handle((method, _ctx) => {
216
+ wildcardMethod = method;
217
+ return { status: "Accepted" };
218
+ });
219
+
220
+ const serverCallPromise = new Promise<void>((resolve, reject) => {
221
+ server.on("client", async (serverClient: OCPPServerClient) => {
222
+ try {
223
+ await serverClient.call("AnyMethod", {});
224
+ resolve();
225
+ } catch (e) {
226
+ reject(e);
227
+ }
228
+ });
229
+ });
230
+
231
+ await client.connect();
232
+ await serverCallPromise;
233
+ expect(wildcardMethod).toBe("AnyMethod");
234
+ });
235
+
236
+ it("should remove specific handlers", async () => {
237
+ client = new OCPPClient({
238
+ identity: "CS001",
239
+ endpoint: `ws://localhost:${port}`,
240
+ protocols: ["ocpp1.6"],
241
+ reconnect: false,
242
+ });
243
+
244
+ client.handle("Test", async () => ({ result: "ok" }));
245
+ client.removeHandler("Test");
246
+
247
+ const serverCallPromise = new Promise<void>((resolve, reject) => {
248
+ server.on("client", async (serverClient: OCPPServerClient) => {
249
+ try {
250
+ await serverClient.call("Test", {});
251
+ reject(new Error("Should have thrown"));
252
+ } catch {
253
+ resolve();
254
+ }
255
+ });
256
+ });
257
+
258
+ await client.connect();
259
+ await serverCallPromise;
260
+ });
261
+ });
262
+
263
+ describe("OCPPClient - Security Profiles", () => {
264
+ afterEach(async () => {
265
+ if (client) await client.close({ force: true }).catch(() => {});
266
+ if (server) await server.close({ force: true });
267
+ });
268
+
269
+ it("should include Basic Auth header for Profile 1", async () => {
270
+ let receivedPassword: Buffer | undefined;
271
+
272
+ server = new OCPPServer({ protocols: ["ocpp1.6"] });
273
+ server.auth((accept, _reject, handshake) => {
274
+ receivedPassword = handshake.password;
275
+ accept({ protocol: "ocpp1.6" });
276
+ });
277
+ const httpServer = await server.listen(0);
278
+ port = getPort(httpServer);
279
+
280
+ client = new OCPPClient({
281
+ identity: "CS001",
282
+ endpoint: `ws://localhost:${port}`,
283
+ protocols: ["ocpp1.6"],
284
+ securityProfile: SecurityProfile.BASIC_AUTH,
285
+ password: "myPassword123",
286
+ reconnect: false,
287
+ });
288
+
289
+ await client.connect();
290
+
291
+ // Give the server a moment to process
292
+ await new Promise((r) => setTimeout(r, 100));
293
+
294
+ expect(receivedPassword).toBeDefined();
295
+ expect(receivedPassword!.toString()).toBe("myPassword123");
296
+ });
297
+ });
@@ -0,0 +1,132 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import {
3
+ RPCGenericError,
4
+ RPCNotImplementedError,
5
+ RPCNotSupportedError,
6
+ RPCInternalError,
7
+ RPCProtocolError,
8
+ RPCSecurityError,
9
+ RPCFormationViolationError,
10
+ RPCFormatViolationError,
11
+ RPCPropertyConstraintViolationError,
12
+ RPCOccurrenceConstraintViolationError,
13
+ RPCTypeConstraintViolationError,
14
+ RPCMessageTypeNotSupportedError,
15
+ RPCFrameworkError,
16
+ TimeoutError,
17
+ UnexpectedHttpResponse,
18
+ WebsocketUpgradeError,
19
+ } from "../src/errors.js";
20
+
21
+ describe("Error Classes", () => {
22
+ it("TimeoutError should have correct properties", () => {
23
+ const err = new TimeoutError("custom timeout");
24
+ expect(err.name).toBe("TimeoutError");
25
+ expect(err.message).toBe("custom timeout");
26
+ expect(err).toBeInstanceOf(Error);
27
+ });
28
+
29
+ it("TimeoutError should have default message", () => {
30
+ const err = new TimeoutError();
31
+ expect(err.message).toBe("Operation timed out");
32
+ });
33
+
34
+ it("UnexpectedHttpResponse should include status code and headers", () => {
35
+ const err = new UnexpectedHttpResponse("bad response", 401, {
36
+ "www-authenticate": "Basic",
37
+ });
38
+ expect(err.name).toBe("UnexpectedHttpResponse");
39
+ expect(err.statusCode).toBe(401);
40
+ expect(err.headers["www-authenticate"]).toBe("Basic");
41
+ });
42
+
43
+ it("WebsocketUpgradeError should have default message", () => {
44
+ const err = new WebsocketUpgradeError();
45
+ expect(err.name).toBe("WebsocketUpgradeError");
46
+ expect(err.message).toBe("WebSocket upgrade failed");
47
+ });
48
+
49
+ it("RPCGenericError should have correct code", () => {
50
+ const err = new RPCGenericError("test");
51
+ expect(err.rpcErrorCode).toBe("GenericError");
52
+ expect(err.name).toBe("RPCGenericError");
53
+ });
54
+
55
+ it("RPCNotImplementedError should have correct code and message", () => {
56
+ const err = new RPCNotImplementedError("test");
57
+ expect(err.rpcErrorCode).toBe("NotImplemented");
58
+ expect(err.rpcErrorMessage).toBe("Requested method is not known");
59
+ });
60
+
61
+ it("RPCNotSupportedError should have correct code", () => {
62
+ const err = new RPCNotSupportedError();
63
+ expect(err.rpcErrorCode).toBe("NotSupported");
64
+ });
65
+
66
+ it("RPCInternalError should have correct code", () => {
67
+ const err = new RPCInternalError();
68
+ expect(err.rpcErrorCode).toBe("InternalError");
69
+ });
70
+
71
+ it("RPCProtocolError should have correct code", () => {
72
+ const err = new RPCProtocolError();
73
+ expect(err.rpcErrorCode).toBe("ProtocolError");
74
+ });
75
+
76
+ it("RPCSecurityError should have correct code", () => {
77
+ const err = new RPCSecurityError();
78
+ expect(err.rpcErrorCode).toBe("SecurityError");
79
+ });
80
+
81
+ it("RPCFormationViolationError should have correct code", () => {
82
+ const err = new RPCFormationViolationError();
83
+ expect(err.rpcErrorCode).toBe("FormationViolation");
84
+ });
85
+
86
+ it("RPCFormatViolationError should have correct code", () => {
87
+ const err = new RPCFormatViolationError();
88
+ expect(err.rpcErrorCode).toBe("FormatViolation");
89
+ });
90
+
91
+ it("RPCPropertyConstraintViolationError should have correct code", () => {
92
+ const err = new RPCPropertyConstraintViolationError();
93
+ expect(err.rpcErrorCode).toBe("PropertyConstraintViolation");
94
+ });
95
+
96
+ it("RPCOccurrenceConstraintViolationError should have correct code", () => {
97
+ const err = new RPCOccurrenceConstraintViolationError();
98
+ expect(err.rpcErrorCode).toBe("OccurrenceConstraintViolation");
99
+ });
100
+
101
+ it("RPCTypeConstraintViolationError should have correct code", () => {
102
+ const err = new RPCTypeConstraintViolationError();
103
+ expect(err.rpcErrorCode).toBe("TypeConstraintViolation");
104
+ });
105
+
106
+ it("RPCMessageTypeNotSupportedError should have correct code", () => {
107
+ const err = new RPCMessageTypeNotSupportedError();
108
+ expect(err.rpcErrorCode).toBe("MessageTypeNotSupported");
109
+ });
110
+
111
+ it("RPCFrameworkError should have correct code", () => {
112
+ const err = new RPCFrameworkError();
113
+ expect(err.rpcErrorCode).toBe("RpcFrameworkError");
114
+ });
115
+
116
+ it("all RPC errors should extend RPCGenericError chain", () => {
117
+ const errors = [
118
+ new RPCGenericError(),
119
+ new RPCNotImplementedError(),
120
+ new RPCInternalError(),
121
+ new RPCFormatViolationError(),
122
+ ];
123
+ for (const err of errors) {
124
+ expect(err).toBeInstanceOf(Error);
125
+ }
126
+ });
127
+
128
+ it("RPC errors should carry details", () => {
129
+ const err = new RPCGenericError("msg", { foo: "bar" });
130
+ expect(err.details).toEqual({ foo: "bar" });
131
+ });
132
+ });