CH-J Toolsspráva serverů přes SSH
Menu
Vydané zdroje

CH-J Tools

Procházejte adresáře a soubory konkrétního vydání aplikace.

Stáhnout zdroje ZIP
CH-J / Server Manager 406 souborů
core-20261024T135205Z-7c487d719de2 Přidáno: · Sestaveno:
smokeDisksUi.js Přidáno: Datum není evidováno 11,1 KB · 138 řádků
Kód Stáhnout soubor
1"use strict";
2// End-to-end UI check of chj.disks with the real plugin runtime, preload, DiskService and an isolated test VM.
3// Linux VM (default): CHJ_VM_HOST/_FINGERPRINT/_KEY and CHJ_VM_SUDO_PASSWORD or _FILE + _LINE. Reads the inventory
4// through the sudo dialog, then creates a GPT + ext4 partition on a dedicated loop image (/var/tmp/chj-disks-test)
5// through the context menu, queue, review dialog and job monitor. The system disk is never changed.
6// Windows: CHJ_SMOKE_WINDOWS=1 with CHJ_DISKS_WINDOWS_HOST/_FINGERPRINT/_KEY: the "unsupported platform" view.
7// Run (VS Code terminal): env -u ELECTRON_RUN_AS_NODE npx electron scripts/smokeDisksUi.js [screenshot-dir]
8const { app, BrowserWindow, ipcMain, protocol } = require("electron");
9const assert = require("node:assert/strict");
10const crypto = require("node:crypto");
11const fs = require("node:fs");
12const os = require("node:os");
13const path = require("node:path");
14const { hardenSession } = require("../src/main/bootstrap/createMainWindow");
15const { PluginRegistry } = require("../src/main/plugins/pluginRegistry");
16const { PluginRuntime } = require("../src/main/plugins/pluginRuntime");
17const { SessionManager } = require("../src/main/sessions/sessionManager");
18const { PrivilegedExecutor } = require("../src/main/sessions/privilegedExec");
19const { DiskService } = require("../src/main/disks/diskService");
21const env = process.env;
22const windows = env.CHJ_SMOKE_WINDOWS === "1";
23const sudoPassword = windows ? "" : env.CHJ_VM_SUDO_PASSWORD
24 || fs.readFileSync(env.CHJ_VM_SUDO_PASSWORD_FILE, "utf8").split("\n")[Number(env.CHJ_VM_SUDO_PASSWORD_LINE || 1) - 1].split("=").slice(1).join("=").trim();
25const FIXTURE = fs.readFileSync(path.join(__dirname, "../test/fixtures/disks/loopFixture.sh"), "utf8");
26const RUN = crypto.randomBytes(4).toString("hex");
27const root = fs.mkdtempSync(path.join(os.tmpdir(), "chj-disks-ui-"));
28const shots = process.argv.find((arg, index) => index > 1 && !arg.startsWith("-") && !arg.endsWith(".js")) || null;
29app.setPath("userData", root);
30protocol.registerSchemesAsPrivileged([{ scheme: "chj-plugin", privileges: { standard: true, secure: true, supportFetchAPI: true } }]);
31const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
32const errors = [];
33let window; let executor; let fixtureUsed = false;
34const js = (source) => window.webContents.executeJavaScript(source);
35async function until(source, timeout = 90000) {
36 const started = Date.now();
37 while (Date.now() - started < timeout) { if (await js(`(() => { try { return (${source}); } catch { return false; } })()`)) return; await delay(150); }
38 throw new Error(`UI condition timed out: ${source}\nstatus: ${await js("document.querySelector('#status').textContent + ' | ' + (document.querySelector('#formError')?.textContent || '') + ' | ' + (document.querySelector('#applyError')?.textContent || '')")}`);
40async function shot(name) { if (shots) { fs.mkdirSync(shots, { recursive: true }); fs.writeFileSync(path.join(shots, `${name}.png`), (await window.webContents.capturePage()).toPNG()); } }
41// Answers the sudo dialog whenever Core asks for the password while waiting for doneSource.
42async function untilWithSudo(doneSource, timeout = 120000) {
43 for (const started = Date.now(); Date.now() - started < timeout; await delay(150)) {
44 if (await js("document.querySelector('#sudoDialog').open")) await js(`document.querySelector('#sudoPassword').value = ${JSON.stringify(sudoPassword)}; document.querySelector('#sudoForm').requestSubmit();`);
45 if (await js(`(() => { try { return (${doneSource}); } catch { return false; } })()`)) return;
46 }
47 throw new Error(`UI condition timed out: ${doneSource}\nstatus: ${await js("document.querySelector('#status').textContent")}`);
49const fixture = async (...args) => {
50 fixtureUsed = true;
51 const run = await executor.run("ui", { script: FIXTURE, args, privileged: true, sudoPassword, purpose: "test.disks.fixture", timeoutMs: 120000, errorPrefix: "FIXTURE_" });
52 return Object.fromEntries(run.stdout.split("\n").filter((line) => line.includes("=")).map((line) => [line.slice(0, line.indexOf("=")), line.slice(line.indexOf("=") + 1)]));
53};
55async function run() {
56 const host = windows ? env.CHJ_DISKS_WINDOWS_HOST : env.CHJ_VM_HOST;
57 const fingerprint = windows ? env.CHJ_DISKS_WINDOWS_FINGERPRINT : env.CHJ_VM_FINGERPRINT;
58 const profile = { id: "p", label: windows ? "Windows test VM" : "Linux test VM", host, port: 22, username: (windows ? env.CHJ_DISKS_WINDOWS_USER : env.CHJ_VM_USER) || "ai", authMethod: "privateKey", privateKeyPath: windows ? env.CHJ_DISKS_WINDOWS_KEY : env.CHJ_VM_KEY };
59 await app.whenReady(); hardenSession();
60 const hostKeys = new Map([[22, Buffer.from(`${fingerprint.replace(/^SHA256:/, "")}=`, "base64").toString("hex")]]);
61 const profileService = { get: () => profile, getHostKey: (_h, p) => hostKeys.get(p) || null, trustHostKey: (_h, p, fp) => hostKeys.set(p, fp), getStoredPassword: () => "", markUsed() {} };
62 const sessionManager = new SessionManager({ profileService, latencyMonitor: { start: () => () => {} } });
63 await sessionManager.connect({ sessionId: "ui", profileId: "p" });
64 executor = new PrivilegedExecutor({ sessionManager });
65 const diskService = new DiskService({ sessionManager, privilegedExecutor: executor });
66 const registry = new PluginRegistry(root, null, { bundledRoot: path.join(__dirname, "../src/main/firstPartyPlugins") });
67 const language = env.CHJ_SMOKE_LANGUAGE || "cs";
68 const runtime = new PluginRuntime({ BrowserWindow, registry, sessionManager, diskService, getLanguage: () => language, preload: path.join(__dirname, "../src/preload/pluginPreload.js"), getMainWindow: () => null, isVaultUnlocked: () => true });
69 runtime.registerIpc(ipcMain);
70 runtime.open("chj.disks");
71 window = runtime.windows.get("chj.disks");
72 window.webContents.on("console-message", (event) => { if (event.level === "error") errors.push(event.message); });
73 window.setSize(1360, 1000);
74 try {
75 if (windows) {
76 await until("!document.querySelector('#unsupported').hidden");
77 assert.match(await js("document.querySelector('#unsupported').textContent"), /Windows/);
78 assert.equal(await js("document.querySelector('#workspace').hidden"), true);
79 await shot("d01-windows-unsupported");
80 console.log("chj.disks UI smoke (Windows unsupported view) passed");
81 return;
82 }
83 await until("!document.querySelector('#workspace').hidden && document.querySelectorAll('#diskMap .segment').length > 0");
84 if (language === "cs") assert.equal(await js("document.querySelector('h1').textContent"), "Správce disků");
85 await shot("d01-readonly");
86 const image = await fixture("create", RUN, "1024");
87 const loop = image.dev.slice(5);
88 await js("document.querySelector('#privilegedButton').click()");
89 await untilWithSudo("document.querySelector('#privilegeBadge').classList.contains('ok')");
90 await until(`Array.from(document.querySelectorAll('#diskList li strong')).some((node) => node.textContent === '${image.dev}')`);
91 // System disk: map widths follow the real sizes, rows show the root mount.
92 assert.ok(await js("document.querySelector('#partitionRows').textContent.includes('/')"));
93 const widths = await js("Array.from(document.querySelectorAll('#diskMap .segment')).map((node) => [node.getBoundingClientRect().width, Number(node.style.flexGrow)])");
94 const big = widths.reduce((best, entry) => entry[1] > best[1] ? entry : best);
95 assert.ok(big[0] > 300, "the largest partition gets most of the map width");
96 await shot("d02-system-disk");
97 // Context menu on the system root offers no destructive action.
98 await js("(() => { const seg = Array.from(document.querySelectorAll('#diskMap .segment')).find((node) => node.title.includes('/dev/')); const box = seg.getBoundingClientRect(); seg.dispatchEvent(new MouseEvent('contextmenu', { bubbles: true, clientX: box.left + 20, clientY: box.top + 20 })); })()");
99 await until("!document.querySelector('#contextMenu').hidden");
100 await shot("d03-context-menu");
101 await js("document.body.click()");
102 // Dedicated loop image: new GPT, then a new ext4 partition through the context menu and the queue.
103 await js(`Array.from(document.querySelectorAll('#diskList li')).find((node) => node.querySelector('strong').textContent === '${image.dev}').click()`);
104 await until(`document.querySelector('#diskTitle').textContent.startsWith('${image.dev}')`);
105 await js("document.querySelector('#tableButton').click()");
106 await until("document.querySelector('#formDialog').open");
107 await js("document.querySelector('#formForm').requestSubmit()");
108 await untilWithSudo("!document.querySelector('#queuePanel').hidden && !document.querySelector('#previewBadge').hidden");
109 await until("document.querySelectorAll('#diskMap .segment.fs-free').length === 1");
110 await js("(() => { const seg = document.querySelector('#diskMap .segment.fs-free'); const box = seg.getBoundingClientRect(); seg.dispatchEvent(new MouseEvent('contextmenu', { bubbles: true, clientX: box.left + 30, clientY: box.top + 20 })); })()");
111 await until("!document.querySelector('#contextMenu').hidden");
112 await js("document.querySelector('#contextMenu button:not([disabled])').click()");
113 await until("document.querySelector('#formDialog').open && document.querySelector('#createSize')");
114 await js("document.querySelector('#createSize').value = '512'; document.querySelector('#createSizeUnit').value = 'MiB'; document.querySelector('#createLabel').value = 'chjui'; document.querySelector('#formForm').requestSubmit();");
115 await untilWithSudo("document.querySelectorAll('#queueList li').length === 2 && document.querySelector('#diskMap .segment.created')");
116 await shot("d04-queue-preview");
117 await js("document.querySelector('#queueApply').click()");
118 await untilWithSudo("document.querySelector('#applyDialog').open");
119 assert.ok(await js("document.querySelectorAll('#applyBody .step-list li').length >= 5"), "review shows the steps");
120 assert.ok(await js("document.querySelector('#applyBody .warnings').textContent.length > 50"), "review shows the risks");
121 await shot("d05-review");
122 await js("document.querySelector('#applyBackup').checked = true; document.querySelector('#applyForm').requestSubmit();");
123 await untilWithSudo("!document.querySelector('#jobPanel').hidden");
124 await until("document.querySelector('#jobState').classList.contains('succeeded')", 120000);
125 await shot("d06-job-done");
126 await until(`document.querySelector('#partitionRows').textContent.includes('${loop}p1') && document.querySelector('#partitionRows').textContent.includes('chjui')`);
127 assert.equal(await js("document.querySelectorAll('#diskMap .segment.fs-ext4').length"), 1, "new ext4 partition drawn");
128 await shot("d07-result");
129 const unexpected = errors.filter((message) => !/Autofill/.test(message));
130 assert.deepEqual(unexpected, [], "no console errors");
131 console.log("chj.disks UI smoke (Linux VM) passed");
132 } finally {
133 if (fixtureUsed) { try { console.log("cleanup", JSON.stringify(await fixture("cleanup", RUN))); } catch (error) { console.error("cleanup failed", error.code || error.message); } }
134 await sessionManager.disconnectAll("smoke");
135 }
138run().then(() => app.exit(0), (error) => { console.error(error); app.exit(1); });
SHA-25689f0f30f68c7a92335c62632a3e275198e908a2a97745cf97571bc4ebb338b57

CH-J Proprietary Software License 1.14 · Zdroje jsou zveřejněny pod CH-J Proprietary Software License 1.14. Jejich dostupnost nemění licenční podmínky ani neposkytuje další oprávnění.

SHA-256 archivu476ed8aef0f1e96a5acdf223127b6692acdf4492eeaf5d0816b6e2a634fbbda6
Soubory