import Ajv from "ajv"; import { describe, expect, test } from "bun:test"; import { createDefaultCheckerRegistry } from "../../../../src/server/checker/runner"; import { createProbeConfigJsonSchema } from "../../../../src/server/checker/schema/export"; import { formatConfigIssues, issue } from "../../../../src/server/checker/schema/issues"; import { validateProbeConfigContract } from "../../../../src/server/checker/schema/validate"; describe("config contract", () => { test("导出的 probe-config.schema.json 与 fragments 生成结果一致", async () => { const expected = `${JSON.stringify(createProbeConfigJsonSchema(createDefaultCheckerRegistry()), null, 2)}\n`; const actual = await Bun.file("probe-config.schema.json").text(); expect(actual).toBe(expected); }); test("导出 schema 拒绝未知字段和小写 HTTP method", () => { const ajv = new Ajv({ allErrors: true, coerceTypes: false, removeAdditional: false, strict: true, useDefaults: false, }); const validate = ajv.compile(createProbeConfigJsonSchema(createDefaultCheckerRegistry())); expect( validate({ targets: [ { http: { method: "get", unknownHttpField: true, url: "https://example.com" }, name: "api", type: "http", }, ], }), ).toBe(false); }); test("Ajv 错误转换为中文结构化 issue", () => { const result = validateProbeConfigContract( { targets: [ { group: 123, http: { extra: true }, name: "api", type: "http", }, ], unknownRoot: true, }, createDefaultCheckerRegistry(), ); expect(result.config).toBeNull(); const message = formatConfigIssues(result.issues); expect(message).toContain("unknownRoot 是未知字段"); expect(message).toContain('target "api" 的 group 类型不合法'); expect(message).toContain('target "api" 的 http.url 缺少必填字段'); expect(message).toContain('target "api" 的 http.extra 是未知字段'); }); test("ConfigValidationIssue 聚合渲染保留契约和语义错误", () => { const message = formatConfigIssues([ issue("unknown-field", "targets[0].http.extra", "是未知字段", "api"), issue("invalid-regex", "targets[0].expect.body[0].regex", "正则不合法", "api"), ]); expect(message).toBe('target "api" 的 http.extra 是未知字段\ntarget "api" 的 expect.body[0].regex 正则不合法'); }); });