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:
@@ -184,7 +184,7 @@ describe("HttpChecker", () => {
|
||||
test("成功请求 200", async () => {
|
||||
const result = await checker.execute(makeTarget({ url: `${baseUrl}/ok` }), makeCtx());
|
||||
expect(result.matched).toBe(true);
|
||||
expect(result.statusDetail).toBe("HTTP 200");
|
||||
expect(result.observation).toMatchObject({ statusCode: 200 });
|
||||
expect(result.durationMs).not.toBeNull();
|
||||
expect(result.failure).toBeNull();
|
||||
});
|
||||
@@ -192,7 +192,7 @@ describe("HttpChecker", () => {
|
||||
test("404 不匹配默认 status [200]", async () => {
|
||||
const result = await checker.execute(makeTarget({ url: `${baseUrl}/notfound` }), makeCtx());
|
||||
expect(result.matched).toBe(false);
|
||||
expect(result.statusDetail).toBe("HTTP 404");
|
||||
expect(result.observation).toMatchObject({ bodyPreview: "not found", statusCode: 404 });
|
||||
expect(result.failure!.phase).toBe("status");
|
||||
});
|
||||
|
||||
@@ -218,6 +218,7 @@ describe("HttpChecker", () => {
|
||||
makeCtx(),
|
||||
);
|
||||
expect(result.matched).toBe(false);
|
||||
expect(result.observation).toMatchObject({ bodyPreview: "hello world", statusCode: 200 });
|
||||
expect(result.failure!.phase).toBe("headers");
|
||||
});
|
||||
|
||||
@@ -328,13 +329,13 @@ describe("HttpChecker", () => {
|
||||
test("maxRedirects=0 不跟随重定向", async () => {
|
||||
const result = await checker.execute(makeTarget({ maxRedirects: 0, url: `${baseUrl}/redirect` }), makeCtx());
|
||||
expect(result.matched).toBe(false);
|
||||
expect(result.statusDetail).toBe("HTTP 301");
|
||||
expect(result.observation).toMatchObject({ statusCode: 301 });
|
||||
});
|
||||
|
||||
test("maxRedirects>0 跟随重定向", async () => {
|
||||
const result = await checker.execute(makeTarget({ maxRedirects: 5, url: `${baseUrl}/redirect` }), makeCtx());
|
||||
expect(result.matched).toBe(true);
|
||||
expect(result.statusDetail).toBe("HTTP 200");
|
||||
expect(result.observation).toMatchObject({ statusCode: 200 });
|
||||
});
|
||||
|
||||
test("maxRedirects 精确限制跟随次数", async () => {
|
||||
@@ -343,7 +344,7 @@ describe("HttpChecker", () => {
|
||||
makeCtx(),
|
||||
);
|
||||
expect(result.matched).toBe(false);
|
||||
expect(result.statusDetail).toBe("HTTP 302");
|
||||
expect(result.observation).toMatchObject({ statusCode: 302 });
|
||||
});
|
||||
|
||||
test("maxRedirects 允许足够次数时到达最终目标", async () => {
|
||||
@@ -352,7 +353,7 @@ describe("HttpChecker", () => {
|
||||
makeCtx(),
|
||||
);
|
||||
expect(result.matched).toBe(true);
|
||||
expect(result.statusDetail).toBe("HTTP 200");
|
||||
expect(result.observation).toMatchObject({ statusCode: 200 });
|
||||
});
|
||||
|
||||
test("ignoreSSL 跳过自签名证书校验", async () => {
|
||||
@@ -370,14 +371,14 @@ describe("HttpChecker", () => {
|
||||
makeCtx(),
|
||||
);
|
||||
expect(strictResult.matched).toBe(false);
|
||||
expect(strictResult.statusDetail).toBeNull();
|
||||
expect(strictResult.observation).toBeNull();
|
||||
|
||||
const ignoredResult = await checker.execute(
|
||||
makeTarget({ ignoreSSL: true, url: `https://localhost:${httpsServer.port}/` }),
|
||||
makeCtx(),
|
||||
);
|
||||
expect(ignoredResult.matched).toBe(true);
|
||||
expect(ignoredResult.statusDetail).toBe("HTTP 200");
|
||||
expect(ignoredResult.observation).toMatchObject({ statusCode: 200 });
|
||||
} finally {
|
||||
void httpsServer.stop();
|
||||
}
|
||||
@@ -594,7 +595,7 @@ describe("HttpChecker", () => {
|
||||
makeCtx(),
|
||||
);
|
||||
expect(result.matched).toBe(true);
|
||||
expect(result.statusDetail).toBe("HTTP 200");
|
||||
expect(result.observation).toMatchObject({ statusCode: 200 });
|
||||
});
|
||||
|
||||
test("混合 body rules 集成检查", async () => {
|
||||
|
||||
Reference in New Issue
Block a user