- 新增顶层 variables 段支持 string/number/boolean 字面量
- target 字符串字段支持 、、{...} 转义语法
- 变量解析优先级: variables -> process.env -> 默认值 -> 报错
- 完整引用保留原始类型,部分引用拼接为字符串
- 变量替换在 YAML 解析后、AJV 校验前执行
- 替换仅作用于 targets,跳过 id/type 字段
- target 新增必填 id 字段作为唯一标识,name 改为可选展示名称
- 数据库存储/API/前端全面迁移到 id 标识
- 统一 checker 运行时类型检查为 es-toolkit predicates
- 同步 delta specs 到主 specs,归档 config-variables 变更
172 lines
5.8 KiB
TypeScript
172 lines
5.8 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
||
|
||
import {
|
||
validateDashboardWindow,
|
||
validateMetricsBucket,
|
||
validatePagination,
|
||
validateRecentLimit,
|
||
validateTargetId,
|
||
validateTimeRange,
|
||
} from "../../src/server/middleware";
|
||
|
||
describe("validateTargetId", () => {
|
||
test("有效的 target ID 返回字符串", () => {
|
||
const result = validateTargetId("api-health_01", "production");
|
||
expect(result).not.toHaveProperty("status");
|
||
expect((result as { id: string }).id).toBe("api-health_01");
|
||
});
|
||
|
||
test("无效的 target ID 返回 400", () => {
|
||
const invalid = ["-1", "_abc", "has space", "1.5", ""];
|
||
|
||
for (const id of invalid) {
|
||
const result = validateTargetId(id, "production");
|
||
expect(result).toHaveProperty("status", 400);
|
||
}
|
||
});
|
||
});
|
||
|
||
describe("validateTimeRange", () => {
|
||
test("有效的 from/to 返回 ISO 字符串", () => {
|
||
const result = validateTimeRange("2024-01-01T00:00:00.000Z", "2024-01-02T00:00:00.000Z", "production");
|
||
expect(result).not.toHaveProperty("status");
|
||
expect((result as { from: string; to: string }).from).toBe("2024-01-01T00:00:00.000Z");
|
||
expect((result as { from: string; to: string }).to).toBe("2024-01-02T00:00:00.000Z");
|
||
});
|
||
|
||
test("缺失 from 或 to 返回 400", () => {
|
||
const missingFrom = validateTimeRange(null, "2024-01-02T00:00:00.000Z", "production");
|
||
const missingTo = validateTimeRange("2024-01-01T00:00:00.000Z", null, "production");
|
||
const missingBoth = validateTimeRange(null, null, "production");
|
||
|
||
expect(missingFrom).toHaveProperty("status", 400);
|
||
expect(missingTo).toHaveProperty("status", 400);
|
||
expect(missingBoth).toHaveProperty("status", 400);
|
||
});
|
||
|
||
test("空字符串 from 或 to 返回 400", () => {
|
||
const emptyFrom = validateTimeRange("", "2024-01-02T00:00:00.000Z", "production");
|
||
const emptyTo = validateTimeRange("2024-01-01T00:00:00.000Z", "", "production");
|
||
|
||
expect(emptyFrom).toHaveProperty("status", 400);
|
||
expect(emptyTo).toHaveProperty("status", 400);
|
||
});
|
||
|
||
test("无效的日期格式返回 400", () => {
|
||
const result = validateTimeRange("invalid-date", "2024-01-02T00:00:00.000Z", "production");
|
||
expect(result).toHaveProperty("status", 400);
|
||
});
|
||
|
||
test("from 晚于 to 返回 400", () => {
|
||
const result = validateTimeRange("2024-01-02T00:00:00.000Z", "2024-01-01T00:00:00.000Z", "production");
|
||
expect(result).toHaveProperty("status", 400);
|
||
});
|
||
});
|
||
|
||
describe("validatePagination", () => {
|
||
test("默认值:page=1, pageSize=20", () => {
|
||
const result = validatePagination(null, null, "production");
|
||
expect(result).toEqual({ page: 1, pageSize: 20 });
|
||
});
|
||
|
||
test("有效的 page 和 pageSize 参数", () => {
|
||
const result = validatePagination("2", "50", "production");
|
||
expect(result).toEqual({ page: 2, pageSize: 50 });
|
||
});
|
||
|
||
test("无效的 page 参数返回 400", () => {
|
||
const invalidPage = ["0", "-1", "abc", "1.5"];
|
||
|
||
for (const page of invalidPage) {
|
||
const result = validatePagination(page, "20", "production");
|
||
expect(result).toHaveProperty("status", 400);
|
||
}
|
||
});
|
||
|
||
test("无效的 pageSize 参数返回 400", () => {
|
||
const invalidPageSize = ["0", "-1", "abc", "1.5"];
|
||
|
||
for (const pageSize of invalidPageSize) {
|
||
const result = validatePagination("1", pageSize, "production");
|
||
expect(result).toHaveProperty("status", 400);
|
||
}
|
||
});
|
||
|
||
test("pageSize 超过上限返回 400", () => {
|
||
const result = validatePagination("1", "201", "production");
|
||
expect(result).toHaveProperty("status", 400);
|
||
});
|
||
|
||
test("pageSize 等于上限 200 返回成功", () => {
|
||
const result = validatePagination("1", "200", "production");
|
||
expect(result).toEqual({ page: 1, pageSize: 200 });
|
||
});
|
||
});
|
||
|
||
describe("validateRecentLimit", () => {
|
||
test("默认值:recentLimit=30", () => {
|
||
const result = validateRecentLimit(null, "production");
|
||
expect(result).toEqual({ recentLimit: 30 });
|
||
});
|
||
|
||
test("有效的 recentLimit 参数", () => {
|
||
const result = validateRecentLimit("50", "production");
|
||
expect(result).toEqual({ recentLimit: 50 });
|
||
});
|
||
|
||
test("无效的 recentLimit 参数返回 400", () => {
|
||
const invalid = ["0", "-1", "abc", "1.5"];
|
||
|
||
for (const limit of invalid) {
|
||
const result = validateRecentLimit(limit, "production");
|
||
expect(result).toHaveProperty("status", 400);
|
||
}
|
||
});
|
||
|
||
test("recentLimit 超过上限返回 400", () => {
|
||
const result = validateRecentLimit("201", "production");
|
||
expect(result).toHaveProperty("status", 400);
|
||
});
|
||
|
||
test("recentLimit 等于上限 200 返回成功", () => {
|
||
const result = validateRecentLimit("200", "production");
|
||
expect(result).toEqual({ recentLimit: 200 });
|
||
});
|
||
});
|
||
|
||
describe("validateDashboardWindow", () => {
|
||
test("默认值:window=24h", () => {
|
||
const result = validateDashboardWindow(null, "production");
|
||
expect(result).not.toHaveProperty("status");
|
||
expect((result as { label: string }).label).toBe("24h");
|
||
});
|
||
|
||
test("window=24h 返回成功", () => {
|
||
const result = validateDashboardWindow("24h", "production");
|
||
expect(result).not.toHaveProperty("status");
|
||
expect((result as { label: string }).label).toBe("24h");
|
||
});
|
||
|
||
test("不支持的 window 参数返回 400", () => {
|
||
const result = validateDashboardWindow("7d", "production");
|
||
expect(result).toHaveProperty("status", 400);
|
||
});
|
||
});
|
||
|
||
describe("validateMetricsBucket", () => {
|
||
test("默认值:bucket=1h", () => {
|
||
const result = validateMetricsBucket(null, "production");
|
||
expect(result).toEqual({ bucket: "1h" });
|
||
});
|
||
|
||
test("bucket=1h 返回成功", () => {
|
||
const result = validateMetricsBucket("1h", "production");
|
||
expect(result).toEqual({ bucket: "1h" });
|
||
});
|
||
|
||
test("不支持的 bucket 参数返回 400", () => {
|
||
const result = validateMetricsBucket("5m", "production");
|
||
expect(result).toHaveProperty("status", 400);
|
||
});
|
||
});
|