934d114a29
- store/study 与 router 改为读取 /api-permissions/me 拉取当前用户在 当前项目的有效权限,菜单与路由守卫据此判定,并在 PM 项目缺省时 主动 ensureDefaultPmStudy。 - api/projectPermissions 暴露 fetchMyApiEndpointPermissions,并允许 调用方关闭统一错误提示,便于在批量项目权限拉取场景下静默失败。 - 项目管理:Projects 页根据每个项目的有效权限决定项目入口的可点击 状态,缺权限时禁用项目名跳转;ProjectDetail 据 setup_config 写权限 渲染保存/回填/清空/发布按钮,并在无读取权限时提示并退出页面。 - 权限管理:PermissionManagement 切换为按角色提交,Admin 视角才能 编辑 PM 行;按系统模块美化系统级权限展示并标注 PM 可访问项;项目 成员表的角色下拉受 PM 等级约束,自定义角色入口下线。 - 监控:PermissionMonitoring/PermissionAccessLogs 增加 isAdmin 与 showSecurityLog 入参,仅 ADMIN 才能看到底层安全日志与统计。 - ApiEndpointPermissions 表格根据当前用户角色禁用不可写行,避免 误操作 PM 行;MODULE_LABELS 增加 setup_config 的中文展示。 - 测试同步覆盖以上行为:新版 router/Layout/Projects/PermissionManagement /PermissionAccessLogs/PermissionMonitoring/PermissionIpLocations 等 组件均补足检测,并按需为相关组件提供 pinia + localStorage stub。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
83 lines
3.2 KiB
TypeScript
83 lines
3.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { readFileSync } from "node:fs";
|
|
import { resolve } from "node:path";
|
|
|
|
const readSource = () => readFileSync(resolve(__dirname, "./PermissionIpLocations.vue"), "utf8");
|
|
const readMapSource = () => readFileSync(resolve(__dirname, "./chinaProvinceMap.ts"), "utf8");
|
|
|
|
describe("PermissionIpLocations", () => {
|
|
it("renders IP location visualization without duplicate region detail table", () => {
|
|
const source = readSource();
|
|
|
|
expect(source).toContain("fetchIpLocations");
|
|
expect(source).toContain("unique_ip_count");
|
|
expect(source).not.toContain("来源地域明细");
|
|
expect(source).not.toContain("table-card");
|
|
expect(source).not.toContain("<el-table :data=\"items\"");
|
|
});
|
|
|
|
it("renders an interactive China map and primary metric cards", () => {
|
|
const source = readSource();
|
|
|
|
expect(source).toContain("VChart");
|
|
expect(source).toContain("chinaMapOption");
|
|
expect(source).toContain("用户分布图");
|
|
expect(source).toContain("访问次数");
|
|
expect(source).toContain("访问用户数");
|
|
expect(source).toContain("来源 IP 数");
|
|
expect(source).toContain("summary");
|
|
});
|
|
|
|
it("uses the standard geojson.cn China map data instead of generated rectangles", () => {
|
|
const source = readSource();
|
|
const mapSource = readMapSource();
|
|
|
|
expect(source).toContain("../assets/china.json");
|
|
expect(mapSource).toContain("geojson.cn/api/china/1.6.3/china.json");
|
|
expect(mapSource).not.toContain("ProvinceBox");
|
|
expect(mapSource).not.toContain("provinceBoxes");
|
|
});
|
|
|
|
it("keeps the map legend and disables map zoom interactions", () => {
|
|
const source = readSource();
|
|
|
|
expect(source).toContain("visualMap:");
|
|
expect(source).toContain("VisualMapComponent");
|
|
expect(source).toContain("max: maxValue.value");
|
|
expect(source).toContain("roam: false");
|
|
expect(source).not.toContain("scaleLimit");
|
|
});
|
|
|
|
it("uses narrow metric cards without helper subtitles", () => {
|
|
const source = readSource();
|
|
|
|
expect(source).toContain("min-height: 64px");
|
|
expect(source).toContain("padding: 10px 16px");
|
|
expect(source).toContain("right: -46px");
|
|
expect(source).toContain("bottom: -52px");
|
|
expect(source).toContain("border-radius: 18px");
|
|
expect(source).not.toContain("<small>{{ card.hint }}</small>");
|
|
expect(source).not.toContain("hint:");
|
|
expect(source).not.toContain("权限检查总量");
|
|
});
|
|
|
|
it("removes the duplicated hero copy above the map", () => {
|
|
const source = readSource();
|
|
|
|
expect(source).toContain("ip-locations-toolbar");
|
|
expect(source).not.toContain("权限监控 / IP属地");
|
|
expect(source).not.toContain("按访问日志聚合省市、用户与来源 IP");
|
|
expect(source).not.toContain("悬停省份查看访问次数、访问用户数和来源 IP 数");
|
|
expect(source).not.toContain("hero-desc");
|
|
expect(source).not.toContain("eyebrow");
|
|
});
|
|
|
|
it("places the period filter toolbar on the left", () => {
|
|
const source = readSource();
|
|
|
|
expect(source).toContain(".ip-hero");
|
|
expect(source).toContain("justify-content: flex-start");
|
|
expect(source).not.toContain("justify-content: flex-end");
|
|
});
|
|
});
|