1
0
Files
DiAL/tests/web/constants/target-table-columns.test.ts
lanyuanxiaoyao 375dd3492b feat: 结构化 observation 替代 statusDetail,API 层动态构造 detail
- CheckResult: statusDetail -> observation (持久化) + detail (API 动态派生)
- 存储: status_detail 列 -> observation TEXT (JSON)
- CheckerDefinition: 新增 buildDetail(observation) 方法
- 各 checker 返回结构化 observation,API 层通过 registry 调用 buildDetail
- HTTP: bodyPreview 在 status/header 失败时也提前采集
- UDP: observation 包含 durationMs,未响应归为 error failure
- CMD: 超时/输出超限时保留已收集 observation
- TCP: connectTimeMs 仅含连接建立耗时,不含 banner 等待
- 新增 buildDetail 单测和 mapCheckResult 覆盖测试
- 同步 openspec 主规范,归档 checker-observation 变更
2026-05-19 22:49:00 +08:00

186 lines
5.9 KiB
TypeScript

import type { PrimaryTableCellParams, PrimaryTableCol } from "tdesign-react";
import { describe, expect, test } from "bun:test";
import type { TargetStatus } from "../../../src/shared/api";
import { createTargetTableColumns } from "../../../src/web/constants/target-table-columns";
interface TableFilter {
list?: Array<{ label: string; value: string }>;
type?: string;
}
function getColumn(columns: Array<PrimaryTableCol<TargetStatus>>, colKey: string): PrimaryTableCol<TargetStatus> {
const column = columns.find((item) => item.colKey === colKey);
expect(column).toBeDefined();
return column!;
}
function makeTarget(overrides: Partial<TargetStatus> = {}): TargetStatus {
return {
currentStreak: null,
description: null,
group: "default",
id: "1",
interval: "5s",
latestCheck: null,
name: "test",
recentSamples: [],
stats: { availability: 100, downChecks: 0, totalChecks: 0, upChecks: 0 },
target: "https://example.com",
type: "http",
...overrides,
};
}
describe("createTargetTableColumns", () => {
test("生成 7 个目标表格列", () => {
const columns = createTargetTableColumns(["http", "cmd"]);
expect(columns.map((column) => column.colKey)).toEqual([
"latestCheck.matched",
"name",
"type",
"stats.availability",
"recentSamples",
"currentStreak",
"latestCheck.durationMs",
]);
});
test("根据 checkerTypes 生成类型筛选器", () => {
const typeColumn = getColumn(createTargetTableColumns(["http", "cmd", "tcp"]), "type");
const filter = typeColumn.filter as TableFilter;
expect(filter.type).toBe("single");
expect(filter.list).toEqual([
{ label: "全部", value: "" },
{ label: "http", value: "http" },
{ label: "cmd", value: "cmd" },
{ label: "tcp", value: "tcp" },
]);
});
test("checkerTypes 为空时只保留全部选项", () => {
const typeColumn = getColumn(createTargetTableColumns([]), "type");
const filter = typeColumn.filter as TableFilter;
expect(filter.list).toEqual([{ label: "全部", value: "" }]);
});
test("类型列直接渲染原始 type 文本", () => {
const typeColumn = getColumn(createTargetTableColumns(["tcp"]), "type");
const renderCell = typeColumn.cell as (params: PrimaryTableCellParams<TargetStatus>) => {
props: { children: unknown };
};
const element = renderCell({
col: typeColumn,
colIndex: 2,
row: makeTarget({ type: "tcp" }),
rowIndex: 0,
});
expect(element.props.children).toBe("tcp");
});
test("连续状态列渲染 capped 标记", () => {
const streakColumn = getColumn(createTargetTableColumns(["http"]), "currentStreak");
const renderCell = streakColumn.cell as (params: PrimaryTableCellParams<TargetStatus>) => {
props: { children: unknown[] };
};
const element = renderCell({
col: streakColumn,
colIndex: 5,
row: makeTarget({ currentStreak: { capped: true, count: 30, up: false } }),
rowIndex: 0,
});
expect(streakColumn.title).toBe("连续(次)");
expect(element.props.children.join("")).toBe("▼ 30+");
});
test("延迟列超过 9999ms 时显示上限文案", () => {
const latencyColumn = getColumn(createTargetTableColumns(["http"]), "latestCheck.durationMs");
const renderCell = latencyColumn.cell as (params: PrimaryTableCellParams<TargetStatus>) => {
props: { children: string; className: string };
};
const element = renderCell({
col: latencyColumn,
colIndex: 6,
row: makeTarget({
latestCheck: {
detail: "200",
durationMs: 12000,
failure: null,
matched: true,
observation: null,
timestamp: "2026-01-01T00:00:00.000Z",
},
}),
rowIndex: 0,
});
expect(element.props.children).toBe("9999+");
expect(element.props.className).toContain("latency-value");
});
test("延迟列标题为 延迟(ms)", () => {
const latencyColumn = getColumn(createTargetTableColumns(["http"]), "latestCheck.durationMs");
expect(latencyColumn.title).toBe("延迟(ms)");
});
test("延迟列正常值不包含 ms 单位", () => {
const latencyColumn = getColumn(createTargetTableColumns(["http"]), "latestCheck.durationMs");
const renderCell = latencyColumn.cell as (params: PrimaryTableCellParams<TargetStatus>) => {
props: { children: string; className: string };
};
const element = renderCell({
col: latencyColumn,
colIndex: 6,
row: makeTarget({
latestCheck: {
detail: "200",
durationMs: 123,
failure: null,
matched: true,
observation: null,
timestamp: "2026-01-01T00:00:00.000Z",
},
}),
rowIndex: 0,
});
expect(element.props.children).toBe("123");
});
test("名称列无排序配置", () => {
const nameColumn = getColumn(createTargetTableColumns(["http"]), "name");
expect(nameColumn.sorter).toBeUndefined();
expect(nameColumn.sortType).toBeUndefined();
});
test("名称列 name 为 null 时显示 id", () => {
const nameColumn = getColumn(createTargetTableColumns(["http"]), "name");
const renderCell = nameColumn.cell as (params: PrimaryTableCellParams<TargetStatus>) => string;
const result = renderCell({
col: nameColumn,
colIndex: 1,
row: makeTarget({ id: "my-api", name: null }),
rowIndex: 0,
});
expect(result).toBe("my-api");
});
test("名称列 name 有值时显示 name", () => {
const nameColumn = getColumn(createTargetTableColumns(["http"]), "name");
const renderCell = nameColumn.cell as (params: PrimaryTableCellParams<TargetStatus>) => string;
const result = renderCell({
col: nameColumn,
colIndex: 1,
row: makeTarget({ id: "my-api", name: "我的 API" }),
rowIndex: 0,
});
expect(result).toBe("我的 API");
});
});