import assert from "node:assert/strict"; import test from "node:test"; import { ensureWorkbenchAuth } from "./auth.ts"; test("server auth is authoritative when restoring the workbench", async () => { const harness = installAuthHarness({ localSession: validLocalSession(), responses: { "/auth/session": { authenticated: true, user: { username: "server-admin" }, expiresAt: futureIso() } } }); try { const session = await ensureWorkbenchAuth({ loginShell: harness.loginShell, appShell: harness.appShell }); assert.equal(session.authenticated, true); assert.equal(session.mode, "server"); assert.deepEqual(session.user, { username: "server-admin" }); assert.deepEqual(harness.fetchCalls.map((call) => call.path), ["/auth/session"]); assert.deepEqual(harness.fetchCalls.map((call) => call.method), ["GET"]); assert.equal(harness.loginShell.hidden, true); assert.equal(harness.appShell.hidden, false); assert.equal(globalThis.document.body.dataset.authState, "authenticated"); } finally { harness.restore(); } }); test("login uses cloud-api auth instead of local storage fallback", async () => { const harness = installAuthHarness({ localSession: validLocalSession(), responses: { "/auth/session": { authenticated: false }, "/auth/login": { authenticated: true, user: { username: "admin" }, expiresAt: futureIso() } } }); try { const pendingSession = ensureWorkbenchAuth({ loginShell: harness.loginShell, appShell: harness.appShell }); await harness.waitForLoginReady(); assert.equal(harness.loginShell.hidden, false); assert.equal(harness.appShell.hidden, true); harness.usernameInput.value = "admin"; harness.passwordInput.value = "hwlab2026"; await harness.submitLogin(); const session = await pendingSession; assert.equal(session.authenticated, true); assert.equal(session.mode, "server"); assert.deepEqual(harness.fetchCalls.map((call) => call.path), ["/auth/session", "/auth/login"]); assert.deepEqual(JSON.parse(harness.fetchCalls[1].body), { username: "admin", password: "hwlab2026" }); assert.equal(harness.loginShell.hidden, true); assert.equal(harness.appShell.hidden, false); assert.equal(globalThis.document.body.dataset.authState, "authenticated"); } finally { harness.restore(); } }); function installAuthHarness({ localSession, responses }) { const previous = { window: globalThis.window, document: globalThis.document, fetch: globalThis.fetch, config: globalThis.HWLAB_CLOUD_WEB_CONFIG }; const storage = new Map(); if (localSession) storage.set("hwlab.cloudWorkbench.auth.v1", JSON.stringify(localSession)); const fetchCalls = []; const loginShell = elementStub(); const appShell = elementStub(); const loginForm = formStub(); const usernameInput = inputStub(); const passwordInput = inputStub(); const submitButton = elementStub(); const loginError = elementStub(); loginShell.querySelector = (selector) => ({ "#login-form": loginForm, "#login-username": usernameInput, "#login-password": passwordInput, "#login-submit": submitButton, "#login-error": loginError })[selector] ?? null; globalThis.HWLAB_CLOUD_WEB_CONFIG = { auth: { mode: "auto", username: "admin", password: "hwlab2026" } }; globalThis.document = { body: { dataset: {} } }; globalThis.window = { localStorage: { getItem: (key) => storage.get(key) ?? null, setItem: (key, value) => storage.set(key, String(value)), removeItem: (key) => storage.delete(key) }, requestAnimationFrame: (callback) => { callback(); return 0; }, setTimeout: () => 0, clearTimeout: () => {} }; globalThis.fetch = async (path, options = {}) => { const response = responses[path] ?? { status: 404, authenticated: false }; fetchCalls.push({ path, method: options.method ?? "GET", body: options.body ?? null, credentials: options.credentials ?? null }); const status = Number(response.status ?? (response.authenticated === false && response.error ? 401 : 200)); return { status, ok: status >= 200 && status < 300, json: async () => ({ ...response, status: undefined }) }; }; return { loginShell, appShell, usernameInput, passwordInput, fetchCalls, waitForLoginReady: () => loginForm.waitForListener("submit"), submitLogin: () => loginForm.dispatchSubmit(), restore() { globalThis.window = previous.window; globalThis.document = previous.document; globalThis.fetch = previous.fetch; globalThis.HWLAB_CLOUD_WEB_CONFIG = previous.config; } }; } function validLocalSession() { return { version: 1, username: "admin", issuedAt: Date.now() - 60_000, expiresAt: Date.now() + 60 * 60 * 1000 }; } function futureIso() { return new Date(Date.now() + 60 * 60 * 1000).toISOString(); } function elementStub() { return { hidden: false, attributes: new Map(), setAttribute(name, value) { this.attributes.set(name, String(value)); }, removeAttribute(name) { this.attributes.delete(name); }, focus() {}, select() {} }; } function inputStub() { return { ...elementStub(), value: "" }; } function formStub() { const listeners = new Map(); const waiters = new Map(); return { ...elementStub(), addEventListener(type, nextListener) { listeners.set(type, nextListener); const resolve = waiters.get(type); if (resolve) { waiters.delete(type); resolve(); } }, waitForListener(type) { if (listeners.has(type)) return Promise.resolve(); return new Promise((resolve) => waiters.set(type, resolve)); }, async dispatchSubmit() { const listener = listeners.get("submit"); assert.equal(typeof listener, "function"); await listener({ preventDefault() {} }); } }; }