release(main): 同步 dev 最新候选改动
Storage Persistence Guard / storage-persistence-audit (push) Has been cancelled
Client Quality Gates / Shared client and Web (push) Has been cancelled
Client Quality Gates / macOS Desktop (push) Has been cancelled
Client Quality Gates / Shared client and Web (pull_request) Has been cancelled
Client Quality Gates / macOS Desktop (pull_request) Has been cancelled
Storage Persistence Guard / storage-persistence-audit (pull_request) Has been cancelled
Storage Persistence Guard / storage-persistence-audit (push) Has been cancelled
Client Quality Gates / Shared client and Web (push) Has been cancelled
Client Quality Gates / macOS Desktop (push) Has been cancelled
Client Quality Gates / Shared client and Web (pull_request) Has been cancelled
Client Quality Gates / macOS Desktop (pull_request) Has been cancelled
Storage Persistence Guard / storage-persistence-audit (pull_request) Has been cancelled
This commit is contained in:
+103
-7
@@ -2,9 +2,45 @@ import { describe, expect, it } from "vitest";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
const readRouter = () => readFileSync(resolve(__dirname, "./router/index.ts"), "utf8");
|
||||
const readRouter = () => readFileSync(resolve(__dirname, "./router/index.ts"), "utf8").replace(/\r\n/g, "\n");
|
||||
|
||||
describe("admin project route permissions", () => {
|
||||
it("registers independent full-bleed routes for attachment and version Office previews", () => {
|
||||
const source = readRouter();
|
||||
|
||||
expect(source).toContain('path: "office-preview/attachment/:id"');
|
||||
expect(source).toContain('name: "OfficeAttachmentPreview"');
|
||||
expect(source).toContain('path: "office-preview/version/:id"');
|
||||
expect(source).toContain('name: "OfficeVersionPreview"');
|
||||
expect(source).toContain('path: "office-preview/collaboration/:fileId/revision/:id"');
|
||||
expect(source).toContain('name: "OfficeCollaborationRevisionPreview"');
|
||||
expect(source.match(/component: OfficePreviewWorkspace/g)).toHaveLength(3);
|
||||
expect(source.match(/fullBleed: true/g)?.length).toBeGreaterThanOrEqual(2);
|
||||
expect(source.match(/transientWorkspaceTask: true/g)?.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
||||
it("registers the independent collaboration library and transient editor workspace", () => {
|
||||
const source = readRouter();
|
||||
|
||||
expect(source).toContain('path: "knowledge/collaboration"');
|
||||
expect(source).toContain('name: "KnowledgeCollaboration"');
|
||||
expect(source).toContain('meta: { title: TEXT.menu.knowledgeCollaboration, requiresStudy: true, fullBleed: true }');
|
||||
expect(source).toContain('path: "knowledge/collaboration/:fileId"');
|
||||
expect(source).toContain('name: "KnowledgeCollaborationWorkspace"');
|
||||
expect(source).toContain("component: CollaborationWorkspace");
|
||||
expect(source).toContain("immersiveWorkspace: true");
|
||||
expect(source).toContain("workspaceTaskGroup: TEXT.menu.knowledgeCollaboration");
|
||||
});
|
||||
|
||||
it("registers the collaboration share page outside authenticated project layout", () => {
|
||||
const source = readRouter();
|
||||
expect(source).toContain('path: "/collaboration/share"');
|
||||
expect(source).toContain('name: "CollaborationPublicShare"');
|
||||
expect(source).toContain("component: CollaborationShareWorkspace");
|
||||
expect(source).toContain('meta: { public: true, title: "共享协作文件" }');
|
||||
expect(source).toContain("!to.meta.public && !auth.user");
|
||||
});
|
||||
|
||||
it("lets any authorized project role open the project management detail page", () => {
|
||||
const source = readRouter();
|
||||
|
||||
@@ -67,7 +103,7 @@ describe("admin project route permissions", () => {
|
||||
const monitoringRouteEnd = source.indexOf("],", monitoringRouteStart);
|
||||
const monitoringRoute = source.slice(monitoringRouteStart, monitoringRouteEnd);
|
||||
|
||||
expect(source).toContain('import SystemMonitoringPage from "../views/admin/SystemMonitoringPage.vue";');
|
||||
expect(source).toContain('const SystemMonitoringPage = () => import("../views/admin/SystemMonitoringPage.vue");');
|
||||
expect(source).toContain('path: "system-monitoring"');
|
||||
expect(monitoringRoute).toContain("component: SystemMonitoringPage");
|
||||
expect(monitoringRoute).toContain("requiresAdmin: true");
|
||||
@@ -78,22 +114,82 @@ describe("admin project route permissions", () => {
|
||||
expect(source).not.toContain("[SYSTEM_PERMISSION_MONITORING_METRICS]");
|
||||
});
|
||||
|
||||
it("keeps login landing independent from PM management backend access", () => {
|
||||
it("routes web and desktop login plus missing-project flows through the entry chooser", () => {
|
||||
const source = readRouter();
|
||||
|
||||
expect(source).not.toContain("findPmAdminLandingPath");
|
||||
expect(source).not.toContain("pmAdminLandingModules");
|
||||
expect(source).toContain(' : studyStore.currentStudy\n ? "/project/overview"\n : "/admin/projects",');
|
||||
expect(source).toContain('const WEB_WORKBENCH_ENTRY_PATH = "/workbench";');
|
||||
expect(source).toContain('const DESKTOP_PROJECT_ENTRY_PATH = "/desktop/project-entry";');
|
||||
expect(source).toContain("const resolveWorkbenchEntryPath = (isDesktopRuntime: boolean)");
|
||||
expect(source).toContain("const isWorkbenchEntryPath = (path: string)");
|
||||
expect(source).toContain('const WebWorkbenchEntry = () => import("../views/WebWorkbenchEntry.vue");');
|
||||
expect(source).toContain("path: WEB_WORKBENCH_ENTRY_PATH");
|
||||
expect(source).toContain('name: "WorkbenchEntry"');
|
||||
expect(source).toContain("component: WebWorkbenchEntry");
|
||||
expect(source).toContain("path: DESKTOP_PROJECT_ENTRY_PATH");
|
||||
expect(source).toContain("component: DesktopProjectEntry");
|
||||
expect(source).toContain("DesktopSessionRestore");
|
||||
expect(source).toContain("DESKTOP_SESSION_RESTORE_PATH");
|
||||
expect(source).toContain("if (isDesktopRuntime && to.path === WEB_WORKBENCH_ENTRY_PATH)");
|
||||
expect(source).toContain("if (!isDesktopRuntime && to.path === DESKTOP_PROJECT_ENTRY_PATH)");
|
||||
expect(source).toContain("const workbenchEntryPath = resolveWorkbenchEntryPath(isDesktopRuntime)");
|
||||
expect(source).toContain("next({ path: workbenchEntryPath });");
|
||||
expect(source).toContain('if (isDesktopRuntime && token && !isAdmin && to.path.startsWith("/admin"))');
|
||||
expect(source).toContain("const canAccessProjectManagement = studyStore.currentStudyRole === \"PM\"");
|
||||
expect(source).toContain("if (!canAccessProjectManagement)");
|
||||
expect(source).toContain('next({ path: DESKTOP_PROJECT_ENTRY_PATH });');
|
||||
expect(source).toContain("if (token && isWorkbenchEntryPath(to.path) && studyStore.currentStudy)");
|
||||
expect(source).not.toContain("if (token && !studyStore.currentStudy && !isDesktopRuntime)");
|
||||
expect(source).not.toContain("ensureDefaultActiveStudy();");
|
||||
expect(source).not.toContain("ensureDefaultStudy();");
|
||||
});
|
||||
|
||||
it("canonicalizes the legacy file version entry before rendering its loading fallback", () => {
|
||||
const source = readRouter();
|
||||
const guardIndex = source.indexOf('if (to.name === "FileVersionManagement" && studyStore.currentStudy?.id)');
|
||||
const requiresStudyIndex = source.indexOf("if (to.meta.requiresStudy && !studyStore.currentStudy)");
|
||||
const legacyRouteIndex = source.indexOf('name: "FileVersionManagement"');
|
||||
const legacyRouteEnd = source.indexOf('path: "trial/:trialId/documents"', legacyRouteIndex);
|
||||
const legacyRoute = source.slice(legacyRouteIndex, legacyRouteEnd);
|
||||
|
||||
expect(guardIndex).toBeGreaterThan(-1);
|
||||
expect(requiresStudyIndex).toBeGreaterThan(guardIndex);
|
||||
expect(legacyRoute).toContain("component: DocumentList");
|
||||
expect(source).not.toContain('import FileVersionManagement from "../views/ia/FileVersionManagement.vue";');
|
||||
expect(source).toContain('next({ name: "DocumentList", params: { trialId: studyStore.currentStudy.id }, replace: true });');
|
||||
});
|
||||
|
||||
it("validates restored session tokens through /me before entering protected routes", () => {
|
||||
const source = readRouter();
|
||||
const restoreGuardIndex = source.indexOf("if (!to.meta.public && !auth.user && getToken() && !isDesktopSessionRestoreRoute)");
|
||||
const fetchMeIndex = source.indexOf("await auth.fetchMe({ disableNetworkRetry: true, suppressErrorMessage: true })", restoreGuardIndex);
|
||||
const preserveIndex = source.indexOf("shouldPreserveDesktopSessionOnAuthCheckFailure(error)", fetchMeIndex);
|
||||
const restoreRedirectIndex = source.indexOf("next({ path: DESKTOP_SESSION_RESTORE_PATH", preserveIndex);
|
||||
const logoutIndex = source.indexOf("await auth.logout()", restoreRedirectIndex);
|
||||
const loginRedirectIndex = source.indexOf('next({ path: "/login" })', logoutIndex);
|
||||
const workbenchEntryIndex = source.indexOf("const workbenchEntryPath = resolveWorkbenchEntryPath(isDesktopRuntime)", restoreGuardIndex);
|
||||
|
||||
expect(restoreGuardIndex).toBeGreaterThan(-1);
|
||||
expect(fetchMeIndex).toBeGreaterThan(restoreGuardIndex);
|
||||
expect(preserveIndex).toBeGreaterThan(fetchMeIndex);
|
||||
expect(restoreRedirectIndex).toBeGreaterThan(preserveIndex);
|
||||
expect(logoutIndex).toBeGreaterThan(restoreRedirectIndex);
|
||||
expect(loginRedirectIndex).toBeGreaterThan(logoutIndex);
|
||||
expect(workbenchEntryIndex).toBeGreaterThan(fetchMeIndex);
|
||||
expect(source).toContain("const isDesktopSessionRestoreRoute = to.path === DESKTOP_SESSION_RESTORE_PATH");
|
||||
expect(source).toContain("!isDesktopSessionRestoreRoute");
|
||||
expect(source).toContain("disableNetworkRetry: true");
|
||||
expect(source).toContain("suppressErrorMessage: true");
|
||||
});
|
||||
|
||||
it("does not register the removed non-admin personal dashboard route", () => {
|
||||
const source = readRouter();
|
||||
const removedComponent = ["My", "Work", "bench"].join("");
|
||||
const removedPath = `/${["work", "bench"].join("")}`;
|
||||
|
||||
expect(source).not.toContain(removedComponent);
|
||||
expect(source).not.toContain(`path: "${removedPath.slice(1)}"`);
|
||||
expect(source).not.toContain(`"${removedPath}"`);
|
||||
expect(source).not.toContain(`component: ${removedComponent}`);
|
||||
expect(source).not.toContain('path: "workbench"');
|
||||
});
|
||||
|
||||
it("does not register legacy finance contract routes", () => {
|
||||
|
||||
Reference in New Issue
Block a user