返回 DeepSeek-Reasonix
browserControlHost.test.ts
根目录 / desktop / electron / src / main / browserControlHost.test.ts
1 import assert from "node:assert/strict";
2 import { createCipheriv, pbkdf2Sync } from "node:crypto";
3 import { mkdirSync, mkdtempSync, readdirSync } from "node:fs";
4 import { tmpdir } from "node:os";
5 import { join } from "node:path";
6 import { DatabaseSync } from "node:sqlite";
7 import { test } from "node:test";
8 import { BrowserControlStore, loadBrowserControlBootstrap, type BrowserSession } from "./browserControl.js";
9 import { BrowserControlHost } from "./browserControlHost.js";
10 import type { ChromeCookie } from "./chromeImport.js";
11
12 const log = { info: () => {}, warn: () => {}, error: () => {} };
13
14 function fakeSession() {
15 return {
16 certificateProcs: [] as (((request: unknown, callback: (result: number) => void) => void) | null)[],
17 storageClears: [] as ({ storages?: string[] } | undefined)[],
18 cacheClears: 0,
19 cookies: { set: async (_cookie: ChromeCookie) => {} },
20 setCertificateVerifyProc(proc: ((request: unknown, callback: (result: number) => void) => void) | null) {
21 this.certificateProcs.push(proc);
22 },
23 async clearCache() {
24 this.cacheClears++;
25 },
26 async clearStorageData(options?: { storages?: string[] }) {
27 this.storageClears.push(options);
28 },
29 };
30 }
31
32 function hostFor(root: string, overrides: Partial<Parameters<typeof buildHost>[0]> = {}) {
33 return buildHost({ root, ...overrides });
34 }
35
36 function buildHost(input: {
37 root: string;
38 platform?: NodeJS.Platform;
39 home?: string;
40 run?: (command: string, args: string[]) => Promise<string>;
41 list?: (path: string) => string[];
42 sessions?: ReturnType<typeof fakeSession>[];
43 pushed?: boolean[];
44 }) {
45 const bootstrap = loadBrowserControlBootstrap(input.root);
46 const store = new BrowserControlStore(bootstrap.configPath, bootstrap);
47 const shared = fakeSession();
48 const pushed = input.pushed ?? [];
49 const host = new BrowserControlHost({
50 store,
51 sharedSession: () => shared as unknown as BrowserSession & { cookies: { set(cookie: ChromeCookie): Promise<void> } },
52 log,
53 platform: input.platform ?? "darwin",
54 home: input.home ?? input.root,
55 env: {},
56 run: input.run ?? (async () => "safe-storage-password"),
57 list: input.list ?? ((path: string) => readdirSync(path)),
58 onControlEnabled: (enabled) => void pushed.push(enabled),
59 });
60 return { host, shared, pushed, store };
61 }
62
63 function chromeHome(): string {
64 const home = mkdtempSync(join(tmpdir(), "reasonix-chrome-home-"));
65 const profile = join(home, "Library", "Application Support", "Google", "Chrome", "Default");
66 mkdirSync(profile, { recursive: true });
67 const key = pbkdf2Sync("safe-storage-password", "saltysalt", 1003, 16, "sha1");
68 const cipher = createCipheriv("aes-128-cbc", key, Buffer.alloc(16, 0x20));
69 const value = Buffer.concat([Buffer.from("v10"), cipher.update("token", "utf8"), cipher.final()]);
70 const database = new DatabaseSync(join(profile, "Cookies"));
71 database.exec(`CREATE TABLE cookies (
72 host_key TEXT, name TEXT, encrypted_value BLOB, path TEXT,
73 expires_utc INTEGER, is_secure INTEGER, is_httponly INTEGER, samesite INTEGER)`);
74 database
75 .prepare("INSERT INTO cookies VALUES (?, ?, ?, ?, ?, ?, ?, ?)")
76 .run(".example.test", "sid", value, "/", 13_600_000_000_000_000, 1, 1, 1);
77 database.close();
78 return home;
79 }
80
81 test("the control switch persists and is pushed to the host", async () => {
82 const root = mkdtempSync(join(tmpdir(), "reasonix-browser-control-"));
83 const { host, pushed } = hostFor(root);
84 assert.equal(host.state().controlEnabled, true);
85 assert.deepEqual(pushed, []);
86 await host.setControlEnabled(false);
87 assert.equal(host.state().controlEnabled, false);
88 assert.deepEqual(pushed, [false]);
89 assert.equal(new BrowserControlStore(join(root, "browser-control.json"), loadBrowserControlBootstrap(root)).current.controlEnabled, false);
90 });
91
92 test("the certificate policy follows every guest session, including later ones", async () => {
93 const root = mkdtempSync(join(tmpdir(), "reasonix-browser-control-"));
94 const { host } = hostFor(root);
95 const first = fakeSession();
96 host.trackSession("persist:browser", first as unknown as BrowserSession);
97 assert.deepEqual(first.certificateProcs, [null]);
98
99 await host.setIgnoreCertificateErrors(true);
100 assert.equal(typeof first.certificateProcs[1], "function");
101
102 const second = fakeSession();
103 host.trackSession("temp:tab-2", second as unknown as BrowserSession);
104 assert.equal(typeof second.certificateProcs[0], "function");
105
106 await host.setIgnoreCertificateErrors(false);
107 assert.deepEqual(first.certificateProcs[2], null);
108 assert.deepEqual(second.certificateProcs[1], null);
109 });
110
111 test("cache and data actions target the built-in browser partition", async () => {
112 const root = mkdtempSync(join(tmpdir(), "reasonix-browser-control-"));
113 const { host, shared } = hostFor(root);
114 await host.clearCache();
115 await host.clearAllData();
116 assert.equal(shared.cacheClears, 2);
117 assert.deepEqual(shared.storageClears, [{ storages: ["shadercache", "cachestorage", "serviceworkers"] }, undefined]);
118 });
119
120 test("import reports missing Chrome and missing profiles separately", async () => {
121 const empty = mkdtempSync(join(tmpdir(), "reasonix-browser-control-"));
122 const missing = hostFor(empty);
123 assert.deepEqual(await missing.host.importChromeLogin(), { ok: false, reason: "chrome-missing" });
124
125 const home = mkdtempSync(join(tmpdir(), "reasonix-browser-control-"));
126 mkdirSync(join(home, "Library", "Application Support", "Google", "Chrome"), { recursive: true });
127 const noProfile = hostFor(home);
128 assert.deepEqual(await noProfile.host.importChromeLogin(), { ok: false, reason: "profile-not-found" });
129 });
130
131 test("import copies cookies into the built-in browser session", async () => {
132 const home = chromeHome();
133 const seen: ChromeCookie[] = [];
134 const { host, shared } = hostFor(home);
135 shared.cookies.set = async (cookie: ChromeCookie) => void seen.push(cookie);
136 assert.deepEqual(await host.importChromeLogin(), { ok: true, profile: "Default", cookies: 1, skipped: 0 });
137 assert.equal(seen.length, 1);
138 assert.equal(seen[0].name, "sid");
139 assert.equal(seen[0].value, "token");
140 });
141
142 test("a denied keychain prompt becomes a typed outcome", async () => {
143 const home = chromeHome();
144 const { host } = hostFor(home, { run: async () => Promise.reject(new Error("User interaction is not allowed.")) });
145 assert.deepEqual(await host.importChromeLogin(), { ok: false, reason: "safe-storage-denied" });
146 });
147
147 lines TYPESCRIPT