feat(08-05-business-detail-routing): 完成采购与资产独立路由

这个提交包含在:
2026-08-07 11:00:56 +08:00
父节点 969b426e64
当前提交 1118bfadd7
共修改 24 个文件,包含 891 行新增和 196 行删除
@@ -0,0 +1,94 @@
import { expect, test } from "@playwright/test";
import {
buildAssetDetailPath,
buildPurchaseRequestDetailPath,
readAssetListFilters,
readPurchaseListFilters,
resolveAssetLegacyRedirect,
resolvePurchaseRequestLegacyRedirect,
} from "../../src/lib/businessDetailRoutes";
import { resolveNotificationTargetLink } from "../../src/lib/notificationLinks";
import { resolvePcDetailSource } from "../../src/lib/pcDetailNavigation";
import { resolveMobileEquivalentPath } from "../../src/lib/runtimeContext";
import { resolvePageMeta } from "../../src/features/navigation/schema";
test("采购与资产 canonical 路径统一编码实体 ID,并对空 ID 回落列表", () => {
expect(buildPurchaseRequestDetailPath("purchase/001")).toBe("/purchase-requests/purchase%2F001");
expect(buildPurchaseRequestDetailPath(" ")).toBe("/purchase-requests");
expect(buildAssetDetailPath("asset/001")).toBe("/assets/asset%2F001");
expect(buildAssetDetailPath("")).toBe("/assets");
expect(resolveNotificationTargetLink({ targetType: "purchase_request", targetId: "purchase/001" })).toBe(
"/purchase-requests/purchase%2F001",
);
expect(resolveNotificationTargetLink({ targetType: "asset_card", targetId: "asset/001" })).toBe(
"/assets/asset%2F001",
);
});
test("旧查询链接只保留白名单筛选,并 replace 到 canonical 详情", () => {
const purchaseRedirect = resolvePurchaseRequestLegacyRedirect(new URLSearchParams(
"id=purchase%2F001&status=approved&scope=all&supplierId=supplier-1&page=2&unknown=secret",
));
expect(purchaseRedirect).toEqual({
to: "/purchase-requests/purchase%2F001",
from: "/purchase-requests?status=approved&scope=all&supplierId=supplier-1&page=2",
});
const assetRedirect = resolveAssetLegacyRedirect(new URLSearchParams(
"id=asset-1&q=MacBook&status=in_use&assetType=fixed_asset&sensitivity=internal&scope=own&unknown=secret",
));
expect(assetRedirect).toEqual({
to: "/assets/asset-1",
from: "/assets?q=MacBook&status=in_use&assetType=fixed_asset&sensitivity=internal&scope=own",
});
expect(resolvePurchaseRequestLegacyRedirect(new URLSearchParams("id=%20%20"))).toBeNull();
expect(resolveAssetLegacyRedirect(new URLSearchParams("id="))).toBeNull();
});
test("列表筛选读取会收紧非法枚举、页码和越权范围", () => {
expect(readPurchaseListFilters(new URLSearchParams("status=unknown&scope=all&page=-2"), false)).toMatchObject({
status: "draft",
mine: true,
page: 1,
});
expect(readPurchaseListFilters(new URLSearchParams("status=all&scope=all&page=3"), true)).toMatchObject({
status: "",
mine: false,
page: 3,
});
expect(readAssetListFilters(new URLSearchParams("status=unknown&assetType=unknown&sensitivity=unknown&scope=all"), false)).toMatchObject({
status: "",
assetType: "",
sensitivity: "",
mine: true,
});
expect(readAssetListFilters(new URLSearchParams(), true).mine).toBe(false);
expect(readAssetListFilters(new URLSearchParams("scope=own"), true).mine).toBe(true);
});
test("详情返回来源按模块白名单校验,拒绝外部、跨模块和当前页", () => {
expect(resolvePcDetailSource(
"/purchase-requests?status=approved&scope=own",
"/purchase-requests/purchase-1",
"purchase-request",
)).toBe("/purchase-requests?status=approved&scope=own");
expect(resolvePcDetailSource("/projects/project-1", "/purchase-requests/purchase-1", "purchase-request")).toBe(
"/projects/project-1",
);
expect(resolvePcDetailSource("/assets?scope=own", "/assets/asset-1", "asset")).toBe("/assets?scope=own");
expect(resolvePcDetailSource("/projects/project-1", "/assets/asset-1", "asset")).toBeNull();
expect(resolvePcDetailSource("//example.com/assets", "/assets/asset-1", "asset")).toBeNull();
expect(resolvePcDetailSource("/assets/asset-1", "/assets/asset-1", "asset")).toBeNull();
});
test("页面元数据与移动工作台回落覆盖新路由", () => {
expect(resolvePageMeta("/purchase-requests/purchase-1")).toMatchObject({ title: "采购详情", group: "业务" });
expect(resolvePageMeta("/assets/new")).toMatchObject({ title: "新增资产", group: "业务" });
expect(resolvePageMeta("/assets/asset-1")).toMatchObject({ title: "资产详情", group: "业务" });
expect(resolveMobileEquivalentPath("/purchase-requests/purchase-1")).toBe("/mobile/purchase-requests/purchase-1");
expect(resolveMobileEquivalentPath("/assets/new")).toBe("/mobile/assets/scan");
expect(resolveMobileEquivalentPath("/assets/asset-1")).toBe("/mobile/assets/scan");
});