Veröffentlichte Quellen
Quellen als ZIP herunterladen
CH-J Tools
Durchsuchen Sie Verzeichnisse und Dateien einer bestimmten Anwendungsausgabe.
CH-J / Server Manager
406 Dateien
core-20261024T135205Z-7c487d719de2
Hinzugefügt: · Erstellt:
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 inventory4
// 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]8
const { app, BrowserWindow, ipcMain, protocol } = require("electron");9
const assert = require("node:assert/strict");10
const crypto = require("node:crypto");11
const fs = require("node:fs");12
const os = require("node:os");13
const path = require("node:path");14
const { hardenSession } = require("../src/main/bootstrap/createMainWindow");15
const { PluginRegistry } = require("../src/main/plugins/pluginRegistry");16
const { PluginRuntime } = require("../src/main/plugins/pluginRuntime");17
const { SessionManager } = require("../src/main/sessions/sessionManager");18
const { PrivilegedExecutor } = require("../src/main/sessions/privilegedExec");19
const { DiskService } = require("../src/main/disks/diskService");21
const env = process.env;22
const windows = env.CHJ_SMOKE_WINDOWS === "1";23
const sudoPassword = windows ? "" : env.CHJ_VM_SUDO_PASSWORD24
|| 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();25
const FIXTURE = fs.readFileSync(path.join(__dirname, "../test/fixtures/disks/loopFixture.sh"), "utf8");26
const RUN = crypto.randomBytes(4).toString("hex");27
const root = fs.mkdtempSync(path.join(os.tmpdir(), "chj-disks-ui-"));28
const shots = process.argv.find((arg, index) => index > 1 && !arg.startsWith("-") && !arg.endsWith(".js")) || null;29
app.setPath("userData", root);30
protocol.registerSchemesAsPrivileged([{ scheme: "chj-plugin", privileges: { standard: true, secure: true, supportFetchAPI: true } }]);31
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));32
const errors = [];33
let window; let executor; let fixtureUsed = false;34
const js = (source) => window.webContents.executeJavaScript(source);35
async 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 || '')")}`);39
}40
async 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.42
async 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")}`);48
}49
const 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
};55
async 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
}136
}138
run().then(() => app.exit(0), (error) => { console.error(error); app.exit(1); });SHA-256
89f0f30f68c7a92335c62632a3e275198e908a2a97745cf97571bc4ebb338b57CH-J Proprietary Software License 1.14 · Die Quellen werden unter der CH-J Proprietary Software License 1.14 bereitgestellt. Ihre Verfügbarkeit ändert die Lizenzbedingungen nicht und gewährt keine zusätzlichen Rechte.
SHA-256 des Archivs
476ed8aef0f1e96a5acdf223127b6692acdf4492eeaf5d0816b6e2a634fbbda6