/** * 单元测试:Dashboard 页面 + DbHealthCard 组件 * * _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5_ * * - 测试 4 个区块正确渲染 * - 测试跳转链接指向正确路由 * - 测试 DbHealthCard connected/disconnected/timeout 状态渲染 */ import { describe, it, expect, vi, beforeAll, beforeEach } from "vitest"; import { render, screen, fireEvent } from "@testing-library/react"; import "@testing-library/jest-dom/vitest"; import { MemoryRouter } from "react-router-dom"; import React from "react"; import DbHealthCard from "../components/DbHealthCard"; import type { DbHealthItem } from "../api/dbHealth"; /* ------------------------------------------------------------------ */ /* Ant Design jsdom 兼容:polyfill window.matchMedia */ /* ------------------------------------------------------------------ */ beforeAll(() => { Object.defineProperty(window, "matchMedia", { writable: true, value: vi.fn().mockImplementation((query: string) => ({ matches: false, media: query, onchange: null, addListener: vi.fn(), removeListener: vi.fn(), addEventListener: vi.fn(), removeEventListener: vi.fn(), dispatchEvent: vi.fn(), })), }); }); /* ------------------------------------------------------------------ */ /* Mock 所有 Dashboard 依赖的 API 模块 */ /* ------------------------------------------------------------------ */ vi.mock("../api/opsPanel", () => ({ fetchSystemInfo: vi.fn().mockResolvedValue({ cpu_percent: 25, memory_total_gb: 16, memory_used_gb: 8, memory_percent: 50, disk_total_gb: 500, disk_used_gb: 250, disk_percent: 50, boot_time: "2026-01-01T00:00:00", }), fetchServicesStatus: vi.fn().mockResolvedValue([ { env: "prod", label: "生产环境", running: true, pid: 1234, port: 8000, uptime_seconds: 3600, memory_mb: 256, cpu_percent: 10, }, ]), fetchGitInfo: vi.fn().mockResolvedValue([ { env: "prod", branch: "main", last_commit_hash: "abc1234", last_commit_message: "test commit", last_commit_time: "2026-01-01T00:00:00", has_local_changes: false, }, ]), startService: vi.fn(), stopService: vi.fn(), restartService: vi.fn(), gitPull: vi.fn(), syncDeps: vi.fn(), })); vi.mock("../api/dbHealth", () => ({ fetchDbHealth: vi.fn().mockResolvedValue([ { db_name: "etl_feiqiu", status: "connected", active_connections: 5, idle_connections: 10, db_size_mb: 128.5, slow_query_count: 2, }, { db_name: "test_etl_feiqiu", status: "disconnected", active_connections: null, idle_connections: null, db_size_mb: null, slow_query_count: null, }, ]), })); vi.mock("../api/adminAI", () => ({ getTriggerJobs: vi.fn().mockResolvedValue({ items: [], total: 0, page: 1, page_size: 50, today_skipped_duplicates: 0, }), })); // Mock AIDashboard 为简单占位,避免其内部 API 调用 vi.mock("../pages/AIDashboard", () => ({ default: () =>
AIDashboard
, })); /* ------------------------------------------------------------------ */ /* Dashboard 页面测试 */ /* ------------------------------------------------------------------ */ describe("Dashboard 页面 (Requirements 2.1, 2.2)", () => { let Dashboard: React.FC; beforeEach(async () => { vi.clearAllMocks(); const mod = await import("../pages/Dashboard"); Dashboard = mod.default; }); it("渲染 4 个区块:OpsPanel 子组件、DbHealthCard、AI 运行总览、AI 调度摘要", async () => { render( , ); // 区块 1:OpsPanel 子组件 — 页面标题"运行状态" expect(await screen.findByText("运行状态")).toBeInTheDocument(); // 区块 2:数据库健康监控 expect(screen.getByText("数据库健康监控")).toBeInTheDocument(); // 区块 3:AI 运行总览(mocked AIDashboard) expect(screen.getByText("AI 运行总览")).toBeInTheDocument(); expect(screen.getByTestId("ai-dashboard-mock")).toBeInTheDocument(); // 区块 4:AI 调度摘要(Divider + Card title 各出现一次) const summaryElements = screen.getAllByText("AI 调度摘要"); expect(summaryElements.length).toBeGreaterThanOrEqual(1); expect(screen.getByText("今日触发数")).toBeInTheDocument(); expect(screen.getByText("今日成功率")).toBeInTheDocument(); }); it("跳转链接:ETL 状态详情、触发器详情、AI 调度详情", async () => { render( , ); await screen.findByText("运行状态"); expect(screen.getByText(/ETL 状态详情/)).toBeInTheDocument(); expect(screen.getByText(/触发器详情/)).toBeInTheDocument(); // AI 调度详情出现两次(顶部按钮 + 底部链接),取第一个 const aiButtons = screen.getAllByText(/AI 调度详情/); expect(aiButtons.length).toBeGreaterThanOrEqual(1); }); }); /* ------------------------------------------------------------------ */ /* DbHealthCard 组件测试(纯展示组件,不需要 mock API) */ /* ------------------------------------------------------------------ */ describe("DbHealthCard — connected 状态 (Requirements 2.3, 2.4)", () => { const connectedItems: DbHealthItem[] = [ { db_name: "etl_feiqiu", status: "connected", active_connections: 5, idle_connections: 10, db_size_mb: 128.5, slow_query_count: 2, }, { db_name: "zqyy_app", status: "connected", active_connections: 3, idle_connections: 7, db_size_mb: 256.0, slow_query_count: 0, }, ]; it("为每个 connected 数据库渲染卡片,显示「已连接」标签", () => { render(); expect(screen.getByText("etl_feiqiu")).toBeInTheDocument(); expect(screen.getByText("zqyy_app")).toBeInTheDocument(); const connectedTags = screen.getAllByText("已连接"); expect(connectedTags).toHaveLength(2); }); it("展示连接池指标(活跃/空闲连接数)", () => { render(); expect(screen.getByText(/活跃 5/)).toBeInTheDocument(); expect(screen.getByText(/空闲 10/)).toBeInTheDocument(); }); it("展示数据库大小和慢查询数量", () => { render(); const sizeLabels = screen.getAllByText("数据库大小"); expect(sizeLabels.length).toBeGreaterThanOrEqual(1); const slowLabels = screen.getAllByText(/慢查询/); expect(slowLabels.length).toBeGreaterThanOrEqual(1); }); }); describe("DbHealthCard — disconnected 状态 (Requirement 2.5)", () => { const disconnectedItems: DbHealthItem[] = [ { db_name: "test_etl_feiqiu", status: "disconnected", active_connections: null, idle_connections: null, db_size_mb: null, slow_query_count: null, }, ]; it("disconnected 数据库显示「未连接」标签", () => { render(); expect(screen.getByText("test_etl_feiqiu")).toBeInTheDocument(); expect(screen.getByText("未连接")).toBeInTheDocument(); }); it("disconnected 数据库显示无法获取指标提示", () => { render(); expect(screen.getByText("数据库未连接,无法获取指标")).toBeInTheDocument(); }); }); describe("DbHealthCard — timeout 状态", () => { it("超时时显示「加载超时」标签", () => { render(); expect(screen.getByText("加载超时")).toBeInTheDocument(); }); it("超时时显示重试按钮,点击触发 onRetry", () => { const onRetry = vi.fn(); render(); const retryBtn = screen.getByText("重试"); expect(retryBtn).toBeInTheDocument(); fireEvent.click(retryBtn); expect(onRetry).toHaveBeenCalledOnce(); }); }); describe("DbHealthCard — mixed 状态(connected + disconnected)", () => { const mixedItems: DbHealthItem[] = [ { db_name: "etl_feiqiu", status: "connected", active_connections: 5, idle_connections: 10, db_size_mb: 128.5, slow_query_count: 2, }, { db_name: "test_etl_feiqiu", status: "disconnected", active_connections: null, idle_connections: null, db_size_mb: null, slow_query_count: null, }, ]; it("同时渲染 connected 和 disconnected 卡片", () => { render(); expect(screen.getByText("已连接")).toBeInTheDocument(); expect(screen.getByText("未连接")).toBeInTheDocument(); expect(screen.getByText("etl_feiqiu")).toBeInTheDocument(); expect(screen.getByText("test_etl_feiqiu")).toBeInTheDocument(); }); });