返回 DeepSeek-Reasonix
remote-secret-dialog.test.tsx
根目录 / desktop / frontend / src / __tests__ / remote-secret-dialog.test.tsx
1 // Run: tsx src/__tests__/remote-secret-dialog.test.tsx
2
3 import { JSDOM } from "jsdom";
4 import React from "react";
5 import { act } from "react";
6
7 import type { AppBindings } from "../lib/bridge";
8
9 let passed = 0;
10 let failed = 0;
11 function ok(value: boolean, label: string) {
12 if (value) {
13 process.stdout.write(` PASS ${label}\n`);
14 passed += 1;
15 } else {
16 process.stdout.write(` FAIL ${label}\n`);
17 failed += 1;
18 }
19 }
20
21 console.log("\nRemote SSH one-shot credential prompt");
22 const dom = new JSDOM("<!doctype html><html><body><div id=\"root\"></div></body></html>", {
23 pretendToBeVisual: true,
24 url: "http://localhost/",
25 });
26 (globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
27 globalThis.window = dom.window as unknown as Window & typeof globalThis;
28 globalThis.document = dom.window.document;
29 Object.defineProperty(globalThis, "navigator", { configurable: true, value: dom.window.navigator });
30 globalThis.HTMLElement = dom.window.HTMLElement;
31 globalThis.Event = dom.window.Event;
32 globalThis.KeyboardEvent = dom.window.KeyboardEvent;
33 // React's legacy input-event fallback expects these IE hooks when JSDOM does
34 // not advertise native InputEvent support.
35 Object.defineProperty(dom.window.HTMLElement.prototype, "attachEvent", { configurable: true, value: () => {} });
36 Object.defineProperty(dom.window.HTMLElement.prototype, "detachEvent", { configurable: true, value: () => {} });
37
38 // Import ReactDOM and the component only after installing the DOM so React
39 // selects its native input-event path instead of the legacy IE polyfill.
40 const [{ createRoot }, { RemoteSecretDialog }, { LocaleProvider }, { useRemoteStore }] = await Promise.all([
41 import("react-dom/client"),
42 import("../components/RemoteSecretDialog"),
43 import("../lib/i18n"),
44 import("../store/remote"),
45 ]);
46
47 const calls: Array<{ hostId: string; promptId: string; secret: string; accept: boolean }> = [];
48 window.go = { main: { App: {
49 async ConfirmRemoteSecret(hostId: string, promptId: string, secret: string, accept: boolean) {
50 calls.push({ hostId, promptId, secret, accept });
51 },
52 } as Partial<AppBindings> as AppBindings } };
53
54 const rootElement = document.getElementById("root");
55 if (!rootElement) throw new Error("missing root");
56 const root = createRoot(rootElement);
57 await act(async () => root.render(<LocaleProvider><RemoteSecretDialog /></LocaleProvider>));
58
59 await act(async () => {
60 useRemoteStore.getState().applyStatus({
61 hostId: "box",
62 state: "pending_secret",
63 secretPrompt: { promptId: "prompt-1", hostId: "box", host: "dev@box.test", kind: "password" },
64 });
65 });
66 const input = document.querySelector<HTMLInputElement>('input[type="password"]');
67 ok(Boolean(input), "password request opens a masked input dialog");
68 ok(document.body.textContent?.includes("dev@box.test") === true, "dialog identifies the SSH target");
69
70 await act(async () => {
71 if (!input) return;
72 const setter = Object.getOwnPropertyDescriptor(dom.window.HTMLInputElement.prototype, "value")?.set;
73 setter?.call(input, "one-shot-secret");
74 input.dispatchEvent(new dom.window.Event("input", { bubbles: true }));
75 input.dispatchEvent(new dom.window.Event("change", { bubbles: true }));
76 await Promise.resolve();
77 });
78 await act(async () => {
79 if (!input) return;
80 input.closest("form")?.dispatchEvent(new dom.window.Event("submit", { bubbles: true, cancelable: true }));
81 await Promise.resolve();
82 });
83 ok(calls.length === 1 && calls[0]?.promptId === "prompt-1" && calls[0]?.secret === "one-shot-secret" && calls[0]?.accept === true, "submit sends the prompt ID and secret once to the native bridge");
84 ok(useRemoteStore.getState().pendingSecretPrompt === null, "resolved prompt is removed from shared UI state");
85 ok(document.body.textContent?.includes("one-shot-secret") === false, "secret plaintext is never rendered into the page");
86
87 await act(async () => root.unmount());
88 dom.window.close();
89 process.stdout.write(`\n${passed} passed, ${failed} failed\n`);
90 if (failed > 0) process.exit(1);
91
91 lines Plain Text