前端权限管理:添加单元测试

新增测试文件:
- ApiPermissions.test.ts: 权限管理主页面测试
- ApiEndpointPermissions.test.ts: 接口级权限矩阵组件测试
- PermissionMonitoring.test.ts: 权限监控仪表板组件测试

测试覆盖:
- 组件渲染验证
- 事件发出验证
- 搜索和筛选功能测试
- 数据显示验证

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
Cheng Zhou
2026-05-14 09:23:17 +08:00
parent c73c00d932
commit 35acf96d6b
4 changed files with 345 additions and 5 deletions
@@ -0,0 +1,92 @@
import { describe, it, expect } from "vitest";
import { mount } from "@vue/test-utils";
import ApiEndpointPermissions from "@/components/ApiEndpointPermissions.vue";
import type { ApiEndpointPermissionsResponse } from "@/types/api";
describe("ApiEndpointPermissions.vue", () => {
const mockMatrix: ApiEndpointPermissionsResponse = {
PM: {
"POST:/subjects": true,
"GET:/subjects": true,
"GET:/subjects/{id}": true,
"PATCH:/subjects/{id}": true,
"DELETE:/subjects/{id}": true,
},
CRA: {
"POST:/subjects": true,
"GET:/subjects": true,
"GET:/subjects/{id}": true,
"PATCH:/subjects/{id}": true,
"DELETE:/subjects/{id}": false,
},
};
it("renders permission matrix table", () => {
const wrapper = mount(ApiEndpointPermissions, {
props: {
project: { id: "test-id", name: "Test Project" },
matrix: mockMatrix,
},
global: {
stubs: {
ElTable: false,
ElTableColumn: false,
ElCheckbox: false,
},
},
});
expect(wrapper.find(".api-permissions").exists()).toBe(true);
});
it("emits update event when permission changes", async () => {
const wrapper = mount(ApiEndpointPermissions, {
props: {
project: { id: "test-id", name: "Test Project" },
matrix: mockMatrix,
},
global: {
stubs: {
ElTable: false,
ElTableColumn: false,
ElCheckbox: false,
},
},
});
// 模拟权限变更
const checkboxes = wrapper.findAll("input[type='checkbox']");
if (checkboxes.length > 0) {
await checkboxes[0].setValue(false);
}
// 验证是否发出了 update 事件
expect(wrapper.emitted("update")).toBeTruthy();
});
it("filters endpoints by search text", async () => {
const wrapper = mount(ApiEndpointPermissions, {
props: {
project: { id: "test-id", name: "Test Project" },
matrix: mockMatrix,
},
global: {
stubs: {
ElTable: false,
ElTableColumn: false,
ElCheckbox: false,
ElInput: false,
ElSelect: false,
},
},
});
// 获取搜索输入框
const searchInput = wrapper.find("input[placeholder*='搜索']");
if (searchInput.exists()) {
await searchInput.setValue("subjects");
// 验证过滤逻辑
expect(wrapper.vm.searchText).toBe("subjects");
}
});
});
@@ -0,0 +1,198 @@
import { describe, it, expect } from "vitest";
import { mount } from "@vue/test-utils";
import PermissionMonitoring from "@/components/PermissionMonitoring.vue";
import type { PermissionMetricsResponse, HealthResponse, AlertsResponse } from "@/types/api";
describe("PermissionMonitoring.vue", () => {
const mockMetrics: PermissionMetricsResponse = {
check_metrics: {
total_checks: 1000,
allowed_checks: 950,
denied_checks: 50,
total_time: 5.234,
min_time: 0.001,
max_time: 0.05,
avg_time: 0.005,
allow_rate: 95.0,
deny_rate: 5.0,
error_rate: 0.1,
errors: 1,
},
cache_metrics: {
total_accesses: 1000,
cache_hits: 850,
cache_misses: 150,
cache_invalidations: 5,
hit_rate: 85.0,
miss_rate: 15.0,
},
uptime_seconds: 3600,
};
const mockHealth: HealthResponse = {
status: "healthy",
health_score: 95,
issues: [],
metrics: mockMetrics,
cache_stats: {
cache_items: {
project_permissions_count: 10,
member_role_count: 50,
total_count: 60,
},
cache_metrics: mockMetrics.cache_metrics,
},
};
const mockAlerts: AlertsResponse = {
total: 2,
alerts: [
{
timestamp: 1715692800.123,
level: "warning",
type: "slow_permission_check",
message: "权限检查耗时过长: 52.34ms",
data: { elapsed_time: 0.05234 },
},
{
timestamp: 1715692799.456,
level: "error",
type: "permission_check_error",
message: "权限检查出错: database connection timeout",
data: { error: "database connection timeout" },
},
],
};
it("renders monitoring dashboard", () => {
const wrapper = mount(PermissionMonitoring, {
props: {
metrics: mockMetrics,
health: mockHealth,
alerts: mockAlerts,
},
global: {
stubs: {
ElTag: false,
ElProgress: false,
ElTable: false,
ElEmpty: false,
},
},
});
expect(wrapper.find(".permission-monitoring").exists()).toBe(true);
});
it("displays health status", () => {
const wrapper = mount(PermissionMonitoring, {
props: {
metrics: mockMetrics,
health: mockHealth,
alerts: mockAlerts,
},
global: {
stubs: {
ElTag: false,
ElProgress: false,
ElTable: false,
ElEmpty: false,
},
},
});
const healthCard = wrapper.find(".health-card");
expect(healthCard.exists()).toBe(true);
expect(healthCard.text()).toContain("系统健康状态");
});
it("displays metric cards", () => {
const wrapper = mount(PermissionMonitoring, {
props: {
metrics: mockMetrics,
health: mockHealth,
alerts: mockAlerts,
},
global: {
stubs: {
ElTag: false,
ElProgress: false,
ElTable: false,
ElEmpty: false,
},
},
});
const metricCards = wrapper.findAll(".metric-card");
expect(metricCards.length).toBeGreaterThan(0);
});
it("displays cache efficiency", () => {
const wrapper = mount(PermissionMonitoring, {
props: {
metrics: mockMetrics,
health: mockHealth,
alerts: mockAlerts,
},
global: {
stubs: {
ElTag: false,
ElProgress: false,
ElTable: false,
ElEmpty: false,
},
},
});
const cacheCard = wrapper.find(".cache-card");
expect(cacheCard.exists()).toBe(true);
expect(cacheCard.text()).toContain("缓存效率");
});
it("displays alerts list", () => {
const wrapper = mount(PermissionMonitoring, {
props: {
metrics: mockMetrics,
health: mockHealth,
alerts: mockAlerts,
},
global: {
stubs: {
ElTag: false,
ElProgress: false,
ElTable: false,
ElEmpty: false,
},
},
});
const alertsCard = wrapper.find(".alerts-card");
expect(alertsCard.exists()).toBe(true);
expect(alertsCard.text()).toContain("最近告警");
});
it("emits refresh event when refresh button clicked", async () => {
const wrapper = mount(PermissionMonitoring, {
props: {
metrics: mockMetrics,
health: mockHealth,
alerts: mockAlerts,
},
global: {
stubs: {
ElTag: false,
ElProgress: false,
ElTable: false,
ElEmpty: false,
ElButton: false,
},
},
});
const refreshButton = wrapper.find("button");
if (refreshButton.exists()) {
await refreshButton.trigger("click");
expect(wrapper.emitted("refresh")).toBeTruthy();
}
});
});
@@ -135,11 +135,6 @@ interface Emits {
defineProps<Props>();
defineEmits<Emits>();
const cacheStats = computed(() => {
// 从 health 中提取 cache_stats,如果没有则返回 null
return null;
});
const formatTime = (timestamp: number): string => {
return new Date(timestamp * 1000).toLocaleString("zh-CN");
};
@@ -0,0 +1,55 @@
import { describe, it, expect, beforeEach, vi } from "vitest";
import { mount } from "@vue/test-utils";
import ApiPermissions from "@/views/admin/ApiPermissions.vue";
import { createPinia, setActivePinia } from "pinia";
describe("ApiPermissions.vue", () => {
beforeEach(() => {
setActivePinia(createPinia());
});
it("renders permission management page", () => {
const wrapper = mount(ApiPermissions, {
global: {
stubs: {
ProjectPermissionsModule: true,
ApiEndpointPermissions: true,
PermissionMonitoring: true,
},
},
});
expect(wrapper.find(".permission-page").exists()).toBe(true);
expect(wrapper.find(".permission-title h2").text()).toBe("权限管理");
});
it("renders three tabs", () => {
const wrapper = mount(ApiPermissions, {
global: {
stubs: {
ProjectPermissionsModule: true,
ApiEndpointPermissions: true,
PermissionMonitoring: true,
},
},
});
const tabs = wrapper.findAll(".el-tabs__nav-item");
expect(tabs.length).toBeGreaterThanOrEqual(3);
});
it("has save button disabled when not dirty", () => {
const wrapper = mount(ApiPermissions, {
global: {
stubs: {
ProjectPermissionsModule: true,
ApiEndpointPermissions: true,
PermissionMonitoring: true,
},
},
});
const saveButton = wrapper.findAll("button").find((btn) => btn.text().includes("保存"));
expect(saveButton?.attributes("disabled")).toBeDefined();
});
});