返回 DeepSeek-Reasonix
handshake.test.ts
根目录 / desktop / electron / src / main / handshake.test.ts
1 import assert from "node:assert/strict";
2 import { test } from "node:test";
3 import { buildHelloParams, describeHandshakeFailure, HANDSHAKE_CODES, HandshakeError, validateHelloResult } from "./handshake.js";
4 import { RpcError } from "./rpc.js";
5
6 const goodResult = {
7 protocolVersion: 11,
8 contractDigest: "sha256:abc",
9 service: { version: "v1.30.0", channel: "stable", commit: "abc123", pid: 4242 },
10 runtimeGeneration: "g-01J",
11 runId: "run-1",
12 incidentId: "incident-1",
13 diagnosticsEnabled: true,
14 resources: { origin: "http://127.0.0.1:51234", token: "secret" },
15 window: { width: 1280, height: 820, minWidth: 760, minHeight: 480, frameless: false, zoomFactor: 1 },
16 instance: { identityVersion: 2, identityDigest: "sha256:def", legacyId: "com.reasonix.desktop.0123456789abcdef" },
17 };
18
19 test("hello params carry the documented shape", () => {
20 const params = buildHelloParams({
21 protocolVersion: 11,
22 contractDigest: "sha256:abc",
23 version: "v1.30.0",
24 channel: "stable",
25 commit: "abc123",
26 hostVersion: "44.2.0",
27 chromeVersion: "152.0.0",
28 platform: "darwin",
29 arch: "arm64",
30 home: "/Users/x/.reasonix",
31 dev: false,
32 });
33 assert.deepEqual(params, {
34 protocolVersion: 11,
35 contractDigest: "sha256:abc",
36 build: { version: "v1.30.0", channel: "stable", commit: "abc123" },
37 host: { name: "electron", version: "44.2.0", chrome: "152.0.0", platform: "darwin", arch: "arm64" },
38 instance: { home: "/Users/x/.reasonix", dev: false },
39 });
40 });
41
42 test("a valid hello result is accepted and normalised", () => {
43 const result = validateHelloResult(goodResult);
44 assert.equal(result.runtimeGeneration, "g-01J");
45 assert.equal(result.window.zoomFactor, 1);
46 assert.equal(result.instance?.identityVersion, 2);
47 assert.equal(validateHelloResult({ ...goodResult, instance: undefined }).instance, undefined, "old services remain readable");
48 const noZoom = validateHelloResult({ ...goodResult, window: { ...goodResult.window, zoomFactor: 0 } });
49 assert.equal(noZoom.window.zoomFactor, 1, "a non-positive zoom factor falls back to 1");
50 });
51
52 test("invalid hello results are rejected with a precise message", () => {
53 assert.throws(
54 () => validateHelloResult({ ...goodResult, protocolVersion: 2 }),
55 (error: unknown) => error instanceof HandshakeError && /protocolVersion 2/.test(error.message),
56 );
57 assert.equal(validateHelloResult({ ...goodResult, protocolVersion: 4 }, 4).protocolVersion, 4, "the embedded contract selects the expected protocol");
58 assert.throws(() => validateHelloResult({ ...goodResult, resources: { origin: "" } }), /resources\.origin/);
59 assert.throws(() => validateHelloResult({ ...goodResult, window: undefined }), /result\.window must be an object/);
60 assert.throws(() => validateHelloResult({ ...goodResult, window: { ...goodResult.window, width: 0 } }), /positive/);
61 assert.throws(() => validateHelloResult({ ...goodResult, runtimeGeneration: "" }), /runtimeGeneration/);
62 assert.throws(() => validateHelloResult({ ...goodResult, instance: { ...goodResult.instance, identityVersion: 0 } }), /identityVersion/);
63 assert.throws(() => validateHelloResult("nope"), /result must be an object/);
64 });
65
66 test("optional saved position survives handshake including zero and negative origins", () => {
67 assert.equal(validateHelloResult(goodResult).window.position, undefined);
68 for (const position of [
69 { x: 0, y: 0 },
70 { x: -1800, y: -900 },
71 ]) {
72 assert.deepEqual(validateHelloResult({ ...goodResult, window: { ...goodResult.window, position } }).window.position, position);
73 }
74 for (const position of [{ x: 1 }, { x: NaN, y: 0 }, { x: 0, y: Infinity }]) {
75 assert.throws(() => validateHelloResult({ ...goodResult, window: { ...goodResult.window, position } }), /position/);
76 }
77 });
78
79 test("handshake failures map every documented code and keep the real error text", () => {
80 for (const [name, code] of Object.entries(HANDSHAKE_CODES)) {
81 const failure = describeHandshakeFailure(new RpcError(code, `real text for ${name}`));
82 assert.equal(failure.code, code);
83 assert.equal(failure.name, name);
84 assert.equal(failure.detail, `real text for ${name}`);
85 assert.notEqual(failure.title, "");
86 }
87 const unknownCode = describeHandshakeFailure(new RpcError(-32000, "boom"));
88 assert.equal(unknownCode.name, "rpc_error");
89 const invalid = describeHandshakeFailure(new HandshakeError("bad window"));
90 assert.equal(invalid.name, "invalid_result");
91 assert.equal(invalid.detail, "bad window");
92 const spawn = describeHandshakeFailure(new Error("spawn ENOENT"));
93 assert.equal(spawn.name, "service_failure");
94 assert.equal(spawn.code, null);
95 assert.equal(spawn.detail, "spawn ENOENT");
96 });
97
98 test("a service without lifecycle diagnostics reports empty run and incident ids", () => {
99 const result = validateHelloResult({ ...goodResult, runId: "", incidentId: "", diagnosticsEnabled: false });
100 assert.equal(result.runId, "");
101 assert.equal(result.incidentId, "");
102 assert.equal(result.diagnosticsEnabled, false);
103 assert.throws(() => validateHelloResult({ ...goodResult, runId: undefined }), /result\.runId/);
104 });
105
105 lines TYPESCRIPT