206 lines
9.6 KiB
TypeScript
206 lines
9.6 KiB
TypeScript
import { describe, it, expect, vi } from "vitest";
|
|
import { readFileSync } from "node:fs";
|
|
import { resolve } from "node:path";
|
|
import { mount } from "@vue/test-utils";
|
|
import PermissionMonitoring from "@/components/PermissionMonitoring.vue";
|
|
|
|
vi.mock("@/api/projectPermissions", () => ({
|
|
fetchPermissionMetrics: vi.fn().mockResolvedValue({
|
|
data: {
|
|
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,
|
|
window_seconds: 3600, scope: "sliding_window",
|
|
by_cache: {
|
|
project_permissions: {
|
|
total_accesses: 800, cache_hits: 700, cache_misses: 100,
|
|
cache_invalidations: 5, hit_rate: 87.5, miss_rate: 12.5,
|
|
},
|
|
member_roles: {
|
|
total_accesses: 200, cache_hits: 150, cache_misses: 50,
|
|
cache_invalidations: 5, hit_rate: 75, miss_rate: 25,
|
|
},
|
|
},
|
|
},
|
|
uptime_seconds: 3600,
|
|
},
|
|
}),
|
|
fetchPermissionHealth: vi.fn().mockResolvedValue({
|
|
data: {
|
|
status: "healthy", health_score: 95, issues: [],
|
|
sample_sufficient: true,
|
|
score_breakdown: {
|
|
performance: { weight: 25, deduction: 0 },
|
|
deny_rate: { weight: 20, deduction: 0 },
|
|
cache: {
|
|
weight: 25, deduction: 5, sample_sufficient: true,
|
|
scope: "sliding_window", window_seconds: 3600,
|
|
},
|
|
alerts: { weight: 10, deduction: 0 },
|
|
runtime: { weight: 20, deduction: 0 },
|
|
},
|
|
last_hour: {
|
|
total_checks: 30, denied_checks: 2, deny_rate: 6.67,
|
|
avg_elapsed_ms: 3.5, error_alerts: 0, warning_alerts: 0,
|
|
},
|
|
cache_stats: { cache_items: { total_count: 60 }, cache_metrics: {} },
|
|
components: {
|
|
database: { status: "healthy", latency_ms: 2.1 },
|
|
permission_log_writer: { status: "healthy", running: true, queue_size: 0, queue_capacity: 10000, dropped_entries: 0 },
|
|
security_log_writer: { status: "healthy", running: true, queue_size: 0, queue_capacity: 20000, dropped_entries: 0 },
|
|
retention: { status: "healthy", running: true, access_log_retention_days: 90, metric_retention_days: 400 },
|
|
},
|
|
},
|
|
}),
|
|
fetchPermissionAlerts: vi.fn().mockResolvedValue({ data: { total: 0, alerts: [] } }),
|
|
fetchTopDenied: vi.fn().mockResolvedValue({ data: { items: [] } }),
|
|
fetchStatsSummary: vi.fn().mockResolvedValue({
|
|
data: {
|
|
total_logs: 5000,
|
|
today: { total_checks: 200, allowed_checks: 190, denied_checks: 10, avg_elapsed_ms: 3.5, max_elapsed_ms: 45, allow_rate: 95, deny_rate: 5 },
|
|
last_hour: { total_checks: 30, allowed_checks: 28, denied_checks: 2, requests_per_minute: 0.5 },
|
|
},
|
|
}),
|
|
fetchPermissionTrends: vi.fn().mockResolvedValue({ data: { period: "24h", data_points: [] } }),
|
|
fetchAccessLogs: vi.fn().mockResolvedValue({ data: { total: 0, page: 1, page_size: 50, items: [] } }),
|
|
}));
|
|
|
|
describe("PermissionMonitoring.vue", () => {
|
|
it("renders monitoring dashboard with tabs", () => {
|
|
const wrapper = mount(PermissionMonitoring, {
|
|
global: {
|
|
stubs: {
|
|
ElTabs: false,
|
|
ElTabPane: false,
|
|
PermissionTrendCharts: true,
|
|
PermissionAccessLogs: true,
|
|
PermissionIpLocations: true,
|
|
SecurityCenter: true,
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(wrapper.find(".permission-monitoring").exists()).toBe(true);
|
|
});
|
|
|
|
it("exposes refresh method", () => {
|
|
const wrapper = mount(PermissionMonitoring, {
|
|
global: {
|
|
stubs: {
|
|
ElTabs: true,
|
|
ElTabPane: true,
|
|
PermissionTrendCharts: true,
|
|
PermissionAccessLogs: true,
|
|
PermissionIpLocations: true,
|
|
SecurityCenter: true,
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(typeof wrapper.vm.refresh).toBe("function");
|
|
});
|
|
|
|
it("keeps security events in security center instead of access logs", () => {
|
|
const source = readFileSync(resolve(__dirname, "./PermissionMonitoring.vue"), "utf8");
|
|
|
|
expect(source).toContain("isAdmin?: boolean");
|
|
expect(source).toContain('<SecurityCenter v-if="props.isAdmin"');
|
|
expect(source).toContain('<PermissionAccessLogs ref="accessLogsRef" />');
|
|
expect(source).not.toContain(':show-security-log="props.isAdmin"');
|
|
});
|
|
|
|
it("uses a full-height source-analysis tab without forcing scroll on other tabs", () => {
|
|
const source = readFileSync(resolve(__dirname, "./PermissionMonitoring.vue"), "utf8");
|
|
|
|
expect(source).toContain(":class=\"{ 'is-source-tab': activeTab === 'ip-locations' }\"");
|
|
expect(source).toContain('class="ip-locations-pane"');
|
|
expect(source).toContain(".permission-monitoring.is-source-tab .soybean-monitoring-tabs :deep(.el-tabs__content)");
|
|
expect(source).toContain("overflow: hidden");
|
|
expect(source).toContain(".soybean-monitoring-tabs :deep(.el-tabs__content)");
|
|
expect(source).toContain("flex: 1 1 0;");
|
|
expect(source).toContain("height: auto;");
|
|
expect(source).toContain("overflow-y: auto;");
|
|
expect(source).toContain('height: auto;\n min-height: 100%;');
|
|
expect(source).toContain('.permission-monitoring.is-source-tab .soybean-monitoring-tabs :deep(.el-tab-pane[aria-hidden="false"])');
|
|
expect(source).toContain("flex: 0 0 52px;");
|
|
expect(source).toContain("overscroll-behavior: contain;");
|
|
});
|
|
|
|
it("resizes the source-analysis chart when its tab becomes active", () => {
|
|
const source = readFileSync(resolve(__dirname, "./PermissionMonitoring.vue"), "utf8");
|
|
|
|
expect(source).toContain("onUnmounted");
|
|
expect(source).toContain("type IpLocationsExpose");
|
|
expect(source).toContain("resize: () => void | Promise<void>");
|
|
expect(source).toContain('if (tab === "ip-locations")');
|
|
expect(source).toContain("await nextTick()");
|
|
expect(source).toContain("await ipLocationsRef.value?.resize()");
|
|
});
|
|
|
|
it("uses system monitoring tab names and operation-oriented overview copy", () => {
|
|
const source = readFileSync(resolve(__dirname, "./PermissionMonitoring.vue"), "utf8");
|
|
|
|
expect(source).toContain('label="运行概览"');
|
|
expect(source).toContain('label="安全中心"');
|
|
expect(source).toContain('label="运行趋势"');
|
|
expect(source).toContain('label="访问日志"');
|
|
expect(source).toContain('label="来源分析"');
|
|
expect(source).toContain('label="运行趋势" name="trends" lazy');
|
|
expect(source).toContain('@drilldown="openTrendDrilldown"');
|
|
expect(source).toContain('accessLogsRef.value?.applyTrendFilter(payload)');
|
|
expect(source).toContain('label="访问日志" name="logs" lazy');
|
|
expect(source).toContain('label="来源分析" name="ip-locations" lazy');
|
|
expect(source).toContain('<SecurityCenter v-if="props.isAdmin"');
|
|
expect(source).toContain('name="security"');
|
|
expect(source).toContain('label: "今日监测事件"');
|
|
expect(source).toContain('label: "每分钟事件"');
|
|
expect(source).toContain('label: "今日通过率"');
|
|
expect(source).toContain('class="metric-strip"');
|
|
expect(source).toContain('class="metric-strip-detail"');
|
|
expect(source).toContain('detail: `历史 ${statsSummary.value.total_logs.toLocaleString()}`');
|
|
expect(source).toContain("运行指标");
|
|
expect(source).toContain('const formatResponseTime');
|
|
expect(source).toContain('return "<0.1ms"');
|
|
expect(source).toContain("业务样本不足");
|
|
expect(source).not.toContain("组件在线不代表指标已完成评估");
|
|
expect(source).not.toContain("评分由性能、拒绝率、缓存、告警和组件状态共同计算");
|
|
expect(source).not.toContain("最近一次检查未发现需要处理的问题");
|
|
expect(source).toContain('class="health-summary-label">综合状态');
|
|
expect(source).toContain('class="health-signal-item"');
|
|
expect(source).toContain('item.deduction ? `-${item.deduction}` : "正常"');
|
|
expect(source).toContain('class="overview-pane"');
|
|
expect(source).toContain('class="overview-hero-card"');
|
|
expect(source).toContain('class="operations-card"');
|
|
expect(source).toContain('class="operations-grid"');
|
|
expect(source).toContain('class="alerts-section"');
|
|
expect(source).toContain("grid-template-columns: minmax(210px, 0.85fr)");
|
|
expect(source).toContain('class="status-section runtime-section"');
|
|
expect(source).toContain('class="empty-alert-state"');
|
|
expect(source).toContain('const hasCacheSamples = computed');
|
|
expect(source).toContain("异常拒绝排行");
|
|
expect(source).toContain("告警数据加载失败,不能据此判断系统正常");
|
|
expect(source).toContain("const formatUpdatedTime");
|
|
expect(source).toContain("更新 {{ formatUpdatedTime(lastUpdatedAt) }}");
|
|
expect(source).toContain("runtimeComponentItems");
|
|
|
|
expect(source).not.toContain('label="实时概览"');
|
|
expect(source).not.toContain('label="趋势分析"');
|
|
expect(source).not.toContain('label="访问审计"');
|
|
expect(source).not.toContain('label="IP属地"');
|
|
expect(source).not.toContain('label: "今日总检查"');
|
|
expect(source).not.toContain('label: "每分钟请求"');
|
|
expect(source).not.toContain('label: "今日允许率"');
|
|
expect(source).not.toContain('label: "历史总记录"');
|
|
expect(source).not.toContain('label: "历史事件总数"');
|
|
expect(source).not.toContain('class="summary-card"');
|
|
expect(source).not.toContain('class="overview-toolbar"');
|
|
expect(source).not.toContain("被拒绝最多的权限");
|
|
expect(source).not.toContain("暂无告警,系统运行正常");
|
|
});
|
|
});
|