返回 DeepSeek-Reasonix
rpc.test.ts
根目录 / desktop / electron / src / main / rpc.test.ts
1 import assert from "node:assert/strict";
2 import { test } from "node:test";
3 import { LineDecoder, OversizeFrameError, RpcClient, RpcError } from "./rpc.js";
4
5 function harness(limit?: number) {
6 const written: string[] = [];
7 const requests: Array<{ method: string; params: unknown }> = [];
8 const notifications: Array<{ method: string; params: unknown }> = [];
9 const errors: string[] = [];
10 const client = new RpcClient(
11 { write: (line) => written.push(line) },
12 {
13 onRequest: async (method, params) => {
14 requests.push({ method, params });
15 if (method === "host/fail") throw new RpcError(-32601, "nope");
16 return { echoed: params };
17 },
18 onNotification: (method, params) => notifications.push({ method, params }),
19 onProtocolError: (kind) => errors.push(kind),
20 },
21 limit,
22 );
23 const feed = (text: string) => client.feed(Buffer.from(text));
24 const lastFrame = () => JSON.parse(written[written.length - 1] as string) as Record<string, unknown>;
25 return { client, written, requests, notifications, errors, feed, lastFrame };
26 }
27
28 test("LineDecoder reassembles partial lines and strips CR", () => {
29 const decoder = new LineDecoder();
30 assert.deepEqual(decoder.push(Buffer.from('{"a":')), []);
31 assert.deepEqual(decoder.push(Buffer.from('1}\r\n{"b":2}\n{"c"')), ['{"a":1}', '{"b":2}']);
32 assert.deepEqual(decoder.push(Buffer.from(":3}\n")), ['{"c":3}']);
33 });
34
35 test("LineDecoder rejects a line above the limit, with and without a newline", () => {
36 const decoder = new LineDecoder(8);
37 assert.throws(() => decoder.push(Buffer.from("0123456789\n")), OversizeFrameError);
38 assert.deepEqual(decoder.push(Buffer.from("ok\n")), ["ok"], "the decoder resynchronises after the failure");
39 assert.throws(() => decoder.push(Buffer.from("0123456789")), OversizeFrameError);
40 });
41
42 test("responses resolve their own request regardless of arrival order", async () => {
43 const h = harness();
44 const first = h.client.request("desktop/invoke", { method: "A" });
45 const second = h.client.request("desktop/invoke", { method: "B" });
46 assert.equal(h.written.length, 2);
47 h.feed('{"jsonrpc":"2.0","id":2,"result":"b"}\n{"jsonrpc":"2.0","id":1,"result":"a"}\n');
48 assert.equal(await second, "b");
49 assert.equal(await first, "a");
50 assert.equal(h.client.pendingCount, 0);
51 });
52
53 test("non-JSON and non-2.0 lines are counted and ignored", () => {
54 const h = harness();
55 h.feed("plain log line\n{\"id\":1,\"result\":true}\n\n");
56 assert.equal(h.client.stats.ignoredLines, 2);
57 assert.deepEqual(h.errors, ["non-json", "invalid"]);
58 });
59
60 test("a response arriving after the timeout is ignored", async () => {
61 const h = harness();
62 await assert.rejects(h.client.request("desktop/start", {}, 5), (error: unknown) => error instanceof RpcError && /timed out/.test(error.message));
63 h.feed('{"jsonrpc":"2.0","id":1,"result":{}}\n');
64 assert.equal(h.client.stats.orphanResponses, 1);
65 assert.deepEqual(h.errors, ["orphan-response"]);
66 });
67
68 test("error responses reject with the service's code and message", async () => {
69 const h = harness();
70 const pending = h.client.request("desktop/hello", {});
71 h.feed('{"jsonrpc":"2.0","id":1,"error":{"code":-32003,"message":"contract digest differs","data":{"x":1}}}\n');
72 await assert.rejects(pending, (error: unknown) => error instanceof RpcError && error.code === -32003 && error.message === "contract digest differs");
73 });
74
75 test("reverse requests are answered and notifications dispatched", async () => {
76 const h = harness();
77 h.feed('{"jsonrpc":"2.0","id":"r1","method":"host/window.show","params":{"reason":"tray"}}\n');
78 h.feed('{"jsonrpc":"2.0","method":"desktop/event","params":{"seq":1}}\n');
79 await new Promise((resolve) => setImmediate(resolve));
80 assert.deepEqual(h.requests, [{ method: "host/window.show", params: { reason: "tray" } }]);
81 assert.deepEqual(h.lastFrame(), { jsonrpc: "2.0", id: "r1", result: { echoed: { reason: "tray" } } });
82 assert.deepEqual(h.notifications, [{ method: "desktop/event", params: { seq: 1 } }]);
83 h.feed('{"jsonrpc":"2.0","id":7,"method":"host/fail","params":{}}\n');
84 await new Promise((resolve) => setImmediate(resolve));
85 assert.deepEqual(h.lastFrame(), { jsonrpc: "2.0", id: 7, error: { code: -32601, message: "nope" } });
86 });
87
88 test("an oversize frame closes the client and rejects everything in flight", async () => {
89 const h = harness(32);
90 const pending = h.client.request("desktop/invoke", {});
91 assert.throws(() => h.feed("x".repeat(64) + "\n"), OversizeFrameError);
92 await assert.rejects(pending, OversizeFrameError);
93 assert.equal(h.client.closed, true);
94 await assert.rejects(h.client.request("desktop/invoke", {}), OversizeFrameError);
95 });
96
96 lines TYPESCRIPT