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 变更
This commit is contained in:
@@ -14,10 +14,11 @@ describe("OverviewTab", () => {
|
||||
id: "1",
|
||||
interval: "30s",
|
||||
latestCheck: {
|
||||
detail: "200 OK",
|
||||
durationMs: 100,
|
||||
failure: null,
|
||||
matched: true,
|
||||
statusDetail: "200 OK",
|
||||
observation: null,
|
||||
timestamp: "2025-01-15T10:00:00.000Z",
|
||||
},
|
||||
name: "test-target",
|
||||
|
||||
@@ -14,10 +14,11 @@ describe("TargetDetailDrawer", () => {
|
||||
id: "1",
|
||||
interval: "30s",
|
||||
latestCheck: {
|
||||
detail: "200 OK",
|
||||
durationMs: 100,
|
||||
failure: null,
|
||||
matched: true,
|
||||
statusDetail: "200 OK",
|
||||
observation: null,
|
||||
timestamp: "2025-01-15T10:00:00.000Z",
|
||||
},
|
||||
name: "test-target",
|
||||
|
||||
@@ -20,10 +20,11 @@ describe("TargetGroup", () => {
|
||||
id: "1",
|
||||
interval: "30s",
|
||||
latestCheck: {
|
||||
detail: "200 OK",
|
||||
durationMs: 100,
|
||||
failure: null,
|
||||
matched: true,
|
||||
statusDetail: "200 OK",
|
||||
observation: null,
|
||||
timestamp: "2025-01-15T10:00:00.000Z",
|
||||
},
|
||||
name: "target-1",
|
||||
@@ -39,10 +40,11 @@ describe("TargetGroup", () => {
|
||||
id: "2",
|
||||
interval: "30s",
|
||||
latestCheck: {
|
||||
detail: "500 Internal Server Error",
|
||||
durationMs: 100,
|
||||
failure: { kind: "error", message: "Failed", path: "$", phase: "status" },
|
||||
matched: false,
|
||||
statusDetail: "500 Internal Server Error",
|
||||
observation: null,
|
||||
timestamp: "2025-01-15T10:00:00.000Z",
|
||||
},
|
||||
name: "target-2",
|
||||
|
||||
@@ -110,10 +110,11 @@ describe("createTargetTableColumns", () => {
|
||||
colIndex: 6,
|
||||
row: makeTarget({
|
||||
latestCheck: {
|
||||
detail: "200",
|
||||
durationMs: 12000,
|
||||
failure: null,
|
||||
matched: true,
|
||||
statusDetail: "200",
|
||||
observation: null,
|
||||
timestamp: "2026-01-01T00:00:00.000Z",
|
||||
},
|
||||
}),
|
||||
@@ -139,10 +140,11 @@ describe("createTargetTableColumns", () => {
|
||||
colIndex: 6,
|
||||
row: makeTarget({
|
||||
latestCheck: {
|
||||
detail: "200",
|
||||
durationMs: 123,
|
||||
failure: null,
|
||||
matched: true,
|
||||
statusDetail: "200",
|
||||
observation: null,
|
||||
timestamp: "2026-01-01T00:00:00.000Z",
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -24,10 +24,10 @@ function makeTarget(overrides: Partial<TargetStatus> = {}): TargetStatus {
|
||||
describe("statusSorter", () => {
|
||||
test("DOWN 排在 UP 前面", () => {
|
||||
const up = makeTarget({
|
||||
latestCheck: { durationMs: 10, failure: null, matched: true, statusDetail: null, timestamp: "" },
|
||||
latestCheck: { detail: null, durationMs: 10, failure: null, matched: true, observation: null, timestamp: "" },
|
||||
});
|
||||
const down = makeTarget({
|
||||
latestCheck: { durationMs: 10, failure: null, matched: false, statusDetail: null, timestamp: "" },
|
||||
latestCheck: { detail: null, durationMs: 10, failure: null, matched: false, observation: null, timestamp: "" },
|
||||
});
|
||||
expect(statusSorter(down, up)).toBeLessThan(0);
|
||||
expect(statusSorter(up, down)).toBeGreaterThan(0);
|
||||
@@ -35,10 +35,10 @@ describe("statusSorter", () => {
|
||||
|
||||
test("相同状态返回 0", () => {
|
||||
const a = makeTarget({
|
||||
latestCheck: { durationMs: 10, failure: null, matched: true, statusDetail: null, timestamp: "" },
|
||||
latestCheck: { detail: null, durationMs: 10, failure: null, matched: true, observation: null, timestamp: "" },
|
||||
});
|
||||
const b = makeTarget({
|
||||
latestCheck: { durationMs: 20, failure: null, matched: true, statusDetail: null, timestamp: "" },
|
||||
latestCheck: { detail: null, durationMs: 20, failure: null, matched: true, observation: null, timestamp: "" },
|
||||
});
|
||||
expect(statusSorter(a, b)).toBe(0);
|
||||
});
|
||||
@@ -46,7 +46,7 @@ describe("statusSorter", () => {
|
||||
test("无 latestCheck 的目标排在最后", () => {
|
||||
const noCheck = makeTarget();
|
||||
const up = makeTarget({
|
||||
latestCheck: { durationMs: 10, failure: null, matched: true, statusDetail: null, timestamp: "" },
|
||||
latestCheck: { detail: null, durationMs: 10, failure: null, matched: true, observation: null, timestamp: "" },
|
||||
});
|
||||
expect(statusSorter(noCheck, up)).toBeGreaterThan(0);
|
||||
});
|
||||
@@ -75,20 +75,20 @@ describe("availabilitySorter", () => {
|
||||
describe("latencySorter", () => {
|
||||
test("低延迟排前面", () => {
|
||||
const fast = makeTarget({
|
||||
latestCheck: { durationMs: 50, failure: null, matched: true, statusDetail: null, timestamp: "" },
|
||||
latestCheck: { detail: null, durationMs: 50, failure: null, matched: true, observation: null, timestamp: "" },
|
||||
});
|
||||
const slow = makeTarget({
|
||||
latestCheck: { durationMs: 200, failure: null, matched: true, statusDetail: null, timestamp: "" },
|
||||
latestCheck: { detail: null, durationMs: 200, failure: null, matched: true, observation: null, timestamp: "" },
|
||||
});
|
||||
expect(latencySorter(fast, slow)).toBeLessThan(0);
|
||||
});
|
||||
|
||||
test("无延迟排最后", () => {
|
||||
const noLatency = makeTarget({
|
||||
latestCheck: { durationMs: null, failure: null, matched: true, statusDetail: null, timestamp: "" },
|
||||
latestCheck: { detail: null, durationMs: null, failure: null, matched: true, observation: null, timestamp: "" },
|
||||
});
|
||||
const hasLatency = makeTarget({
|
||||
latestCheck: { durationMs: 100, failure: null, matched: true, statusDetail: null, timestamp: "" },
|
||||
latestCheck: { detail: null, durationMs: 100, failure: null, matched: true, observation: null, timestamp: "" },
|
||||
});
|
||||
expect(latencySorter(noLatency, hasLatency)).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user