diff --git a/frontend/src/components/ApiEndpointPermissions.test.ts b/frontend/src/components/ApiEndpointPermissions.test.ts new file mode 100644 index 00000000..2c61b061 --- /dev/null +++ b/frontend/src/components/ApiEndpointPermissions.test.ts @@ -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"); + } + }); +}); diff --git a/frontend/src/components/PermissionMonitoring.test.ts b/frontend/src/components/PermissionMonitoring.test.ts new file mode 100644 index 00000000..ab0070b5 --- /dev/null +++ b/frontend/src/components/PermissionMonitoring.test.ts @@ -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(); + } + }); +}); diff --git a/frontend/src/components/PermissionMonitoring.vue b/frontend/src/components/PermissionMonitoring.vue index 117534cd..6d13d984 100644 --- a/frontend/src/components/PermissionMonitoring.vue +++ b/frontend/src/components/PermissionMonitoring.vue @@ -135,11 +135,6 @@ interface Emits { defineProps(); defineEmits(); -const cacheStats = computed(() => { - // 从 health 中提取 cache_stats,如果没有则返回 null - return null; -}); - const formatTime = (timestamp: number): string => { return new Date(timestamp * 1000).toLocaleString("zh-CN"); }; diff --git a/frontend/src/views/admin/ApiPermissions.test.ts b/frontend/src/views/admin/ApiPermissions.test.ts new file mode 100644 index 00000000..4979f804 --- /dev/null +++ b/frontend/src/views/admin/ApiPermissions.test.ts @@ -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(); + }); +});