2026-05-22 01:47:40 +08:00
|
|
|
#!/usr/bin/env node
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
|
|
const assert = require("node:assert");
|
|
|
|
|
const fs = require("node:fs");
|
|
|
|
|
const { spawn } = require("node:child_process");
|
|
|
|
|
const { chromium } = require("playwright");
|
|
|
|
|
const {
|
|
|
|
|
findFreePort,
|
|
|
|
|
waitForGateway,
|
|
|
|
|
} = require("./task114-rust-web-gateway-entry-smoke.js");
|
|
|
|
|
|
|
|
|
|
const UI_TIMEOUT_MS = Number(process.env.UI_TIMEOUT_MS || 10_000);
|
|
|
|
|
|
|
|
|
|
function chromiumExecutablePath() {
|
|
|
|
|
return [
|
|
|
|
|
process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH || "",
|
|
|
|
|
"/usr/bin/google-chrome-stable",
|
|
|
|
|
"/usr/bin/google-chrome",
|
|
|
|
|
"/snap/bin/chromium",
|
|
|
|
|
"/usr/bin/chromium",
|
|
|
|
|
"/usr/bin/chromium-browser",
|
|
|
|
|
].find((candidate) => candidate && fs.existsSync(candidate)) || undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function startGateway(port) {
|
|
|
|
|
return spawn("cargo", ["run", "-q", "-p", "mnote-web", "--bin", "mnote-web"], {
|
|
|
|
|
cwd: "/mnt/Data1T/mnote/rust",
|
|
|
|
|
env: {
|
|
|
|
|
...process.env,
|
|
|
|
|
MNOTE_WEB_ALLOW_DEV_FIXTURES: "1",
|
|
|
|
|
MNOTE_WEB_BIND: `127.0.0.1:${port}`,
|
|
|
|
|
MNOTE_WEB_PUBLIC_BIND: "127.0.0.1:3000",
|
|
|
|
|
MNOTE_WEB_ENABLE_LEGACY_NEXT_COMPAT: "1",
|
|
|
|
|
MNOTE_WEB_LEGACY_NEXT_BASE_URL: "http://127.0.0.1:3100",
|
|
|
|
|
},
|
|
|
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function main() {
|
|
|
|
|
let baseUrl = (process.env.MNOTE_UI_BASE_URL || "").replace(/\/+$/, "");
|
|
|
|
|
let gateway = null;
|
|
|
|
|
if (!baseUrl) {
|
|
|
|
|
const port = await findFreePort();
|
|
|
|
|
baseUrl = `http://127.0.0.1:${port}`;
|
|
|
|
|
gateway = startGateway(port);
|
|
|
|
|
await waitForGateway(baseUrl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const executablePath = chromiumExecutablePath();
|
|
|
|
|
const browser = await chromium.launch({
|
|
|
|
|
headless: true,
|
|
|
|
|
...(executablePath ? { executablePath } : {}),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const authPage = await browser.newPage();
|
|
|
|
|
await authPage.goto(`${baseUrl}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
|
|
|
|
|
await authPage.locator('[data-testid="mnote-auth-page"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
|
|
|
|
assert(await authPage.locator('#account').isVisible(), "登录态应显示邮箱或用户名输入框");
|
|
|
|
|
assert(!(await authPage.locator('#email').isVisible()), "登录态不应显示注册邮箱输入框");
|
|
|
|
|
await authPage.locator('[data-auth-switch]').click();
|
|
|
|
|
await authPage.locator('#email').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
|
|
|
|
assert(await authPage.locator('#username').isVisible(), "注册态应显示用户名输入框");
|
|
|
|
|
assert(!(await authPage.locator('#account').isVisible()), "注册态不应显示登录账号输入框");
|
|
|
|
|
await authPage.close();
|
|
|
|
|
|
|
|
|
|
const userContext = await browser.newContext({
|
|
|
|
|
extraHTTPHeaders: {
|
|
|
|
|
"x-mnote-actor-id": "user_profile_smoke",
|
|
|
|
|
"x-mnote-actor-type": "user",
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
const userPage = await userContext.newPage();
|
|
|
|
|
await userPage.goto(baseUrl, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
|
|
|
|
|
await userPage.locator('[data-testid="mnote-account-menu-trigger"]').click();
|
|
|
|
|
await userPage.locator('[data-testid="mnote-account-menu"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
2026-07-10 10:54:34 +08:00
|
|
|
assert.equal(await userPage.locator('[data-testid="mnote-account-access-policy"]').count(), 0, "授权管理入口应已退役");
|
|
|
|
|
await userPage.locator('[data-testid="mnote-account-ai-management"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
2026-05-22 01:47:40 +08:00
|
|
|
await userPage.locator('[data-testid="mnote-account-profile"]').click();
|
|
|
|
|
await userPage.locator('[data-testid="mnote-profile-dialog"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
|
|
|
|
const profileText = await userPage.locator('[data-testid="mnote-profile-dialog"]').innerText();
|
|
|
|
|
assert(profileText.includes("user_profile_smoke"), "个人信息弹窗应显示完整用户 ID");
|
|
|
|
|
|
2026-07-10 10:54:34 +08:00
|
|
|
await userPage.goto(`${baseUrl}/user/access-policy`, { waitUntil: "commit", timeout: UI_TIMEOUT_MS });
|
|
|
|
|
await userPage.waitForURL((url) => url.pathname === "/user/ai" && url.hash === "#ai-admin-access", { timeout: UI_TIMEOUT_MS });
|
|
|
|
|
await userPage.locator("#ai-admin-access.is-active").waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
|
|
|
|
const userAccessText = await userPage.locator("#ai-admin-access").innerText();
|
|
|
|
|
assert(userAccessText.includes("目录权限"), "旧用户授权页应跳转到用户 AI 设置的目录权限面板");
|
2026-05-22 01:47:40 +08:00
|
|
|
await userContext.close();
|
|
|
|
|
|
|
|
|
|
const adminContext = await browser.newContext({
|
|
|
|
|
extraHTTPHeaders: {
|
|
|
|
|
"x-mnote-actor-id": "admin_profile_smoke",
|
|
|
|
|
"x-mnote-actor-type": "admin",
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
const adminPage = await adminContext.newPage();
|
|
|
|
|
await adminPage.goto(baseUrl, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
|
|
|
|
|
await adminPage.locator('[data-testid="mnote-account-menu-trigger"]').click();
|
|
|
|
|
await adminPage.locator('[data-testid="mnote-account-menu"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
2026-07-10 10:54:34 +08:00
|
|
|
assert.equal(await adminPage.locator('[data-testid="mnote-account-access-policy"]').count(), 0, "管理员账号菜单不应再出现授权管理入口");
|
|
|
|
|
await adminPage.locator('[data-testid="mnote-account-ai-management"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
|
|
|
|
await adminPage.goto(`${baseUrl}/admin/access-policy`, { waitUntil: "commit", timeout: UI_TIMEOUT_MS });
|
|
|
|
|
await adminPage.waitForURL((url) => url.pathname === "/admin/ai" && url.hash === "#ai-admin-access", { timeout: UI_TIMEOUT_MS });
|
|
|
|
|
await adminPage.locator("#ai-admin-access.is-active").waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
|
|
|
|
const adminAccessText = await adminPage.locator("#ai-admin-access").innerText();
|
|
|
|
|
assert(adminAccessText.includes("目录权限"), "旧管理员授权页应跳转到管理员 AI 设置的目录权限面板");
|
2026-05-22 01:47:40 +08:00
|
|
|
await adminContext.close();
|
|
|
|
|
|
|
|
|
|
console.log(JSON.stringify({ ok: true, task: "task489-auth-profile-access-ui", baseUrl }, null, 2));
|
|
|
|
|
} finally {
|
|
|
|
|
await browser.close().catch(() => {});
|
|
|
|
|
if (gateway) {
|
|
|
|
|
gateway.kill("SIGTERM");
|
|
|
|
|
setTimeout(() => gateway.kill("SIGKILL"), 2_000).unref();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
main().catch((error) => {
|
|
|
|
|
console.error(error);
|
|
|
|
|
process.exitCode = 1;
|
|
|
|
|
});
|