Files
pikasTech-HWLAB/web/hwlab-cloud-web/auth.test.ts
T
2026-05-30 10:23:59 +08:00

202 lines
5.9 KiB
TypeScript

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() {} });
}
};
}