chore: 强化代码质量与风格检查体系
ESLint 升级到 recommended-type-checked + stylistic-type-checked, 引入 perfectionist 导入排序和 import 插件导入验证。 Prettier 显式声明全部格式化参数,消除跨环境差异。 TypeScript 启用 noUnusedLocals 和 noPropertyAccessFromIndexSignature。 完善 ignore 列表,排除 .agents/、bun.lock、data/ 等。 引入 husky + lint-staged(pre-commit)+ commitlint(commit-msg)。 更新 DEVELOPMENT.md 代码质量章节。 修复所有新增规则检测到的类型和风格违规。
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||
import { ProbeStore } from "../../../src/server/checker/store";
|
||||
import type { CheckFailure, ResolvedTarget } from "../../../src/server/checker/types";
|
||||
import { mkdir, rm } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
import type { CheckFailure, ResolvedTarget } from "../../../src/server/checker/types";
|
||||
|
||||
import { checkerRegistry } from "../../../src/server/checker/runner";
|
||||
import { HttpChecker } from "../../../src/server/checker/runner/http/runner";
|
||||
import { CommandChecker } from "../../../src/server/checker/runner/command/runner";
|
||||
import { HttpChecker } from "../../../src/server/checker/runner/http/runner";
|
||||
import { ProbeStore } from "../../../src/server/checker/store";
|
||||
|
||||
function ensureRegistered() {
|
||||
if (!checkerRegistry.supportedTypes.includes("http")) {
|
||||
@@ -20,33 +22,33 @@ beforeAll(() => {
|
||||
});
|
||||
|
||||
const httpTarget: ResolvedTarget = {
|
||||
type: "http",
|
||||
name: "test-http",
|
||||
expect: { maxDurationMs: 3000, status: [200] },
|
||||
group: "default",
|
||||
http: {
|
||||
url: "https://example.com/health",
|
||||
method: "GET",
|
||||
headers: { Accept: "application/json" },
|
||||
maxBodyBytes: 104857600,
|
||||
method: "GET",
|
||||
url: "https://example.com/health",
|
||||
},
|
||||
intervalMs: 30000,
|
||||
name: "test-http",
|
||||
timeoutMs: 10000,
|
||||
expect: { status: [200], maxDurationMs: 3000 },
|
||||
type: "http",
|
||||
};
|
||||
|
||||
const commandTarget: ResolvedTarget = {
|
||||
type: "command",
|
||||
name: "test-cmd",
|
||||
group: "default",
|
||||
command: {
|
||||
exec: "ping",
|
||||
args: ["-c", "1", "localhost"],
|
||||
cwd: "/tmp",
|
||||
env: {},
|
||||
exec: "ping",
|
||||
maxOutputBytes: 104857600,
|
||||
},
|
||||
group: "default",
|
||||
intervalMs: 60000,
|
||||
name: "test-cmd",
|
||||
timeoutMs: 5000,
|
||||
type: "command",
|
||||
};
|
||||
|
||||
describe("ProbeStore", () => {
|
||||
@@ -61,7 +63,7 @@ describe("ProbeStore", () => {
|
||||
|
||||
afterAll(async () => {
|
||||
store.close();
|
||||
await rm(tempDir, { recursive: true, force: true });
|
||||
await rm(tempDir, { force: true, recursive: true });
|
||||
});
|
||||
|
||||
test("初始化后无 targets", () => {
|
||||
@@ -80,21 +82,26 @@ describe("ProbeStore", () => {
|
||||
const t = store.getTargets().find((t) => t.name === "test-http")!;
|
||||
expect(t.type).toBe("http");
|
||||
expect(t.target).toBe("https://example.com/health");
|
||||
const config = JSON.parse(t.config);
|
||||
const config = JSON.parse(t.config) as {
|
||||
headers: Record<string, string>;
|
||||
maxBodyBytes: number;
|
||||
method: string;
|
||||
url: string;
|
||||
};
|
||||
expect(config.url).toBe("https://example.com/health");
|
||||
expect(config.method).toBe("GET");
|
||||
expect(config.headers).toEqual({ Accept: "application/json" });
|
||||
expect(config.maxBodyBytes).toBe(104857600);
|
||||
expect(t.interval_ms).toBe(30000);
|
||||
expect(t.timeout_ms).toBe(10000);
|
||||
expect(JSON.parse(t.expect!)).toEqual({ status: [200], maxDurationMs: 3000 });
|
||||
expect(JSON.parse(t.expect!)).toEqual({ maxDurationMs: 3000, status: [200] });
|
||||
});
|
||||
|
||||
test("command target 字段正确", () => {
|
||||
const t = store.getTargets().find((t) => t.name === "test-cmd")!;
|
||||
expect(t.type).toBe("command");
|
||||
expect(t.target).toBe("exec ping -c 1 localhost");
|
||||
const config = JSON.parse(t.config);
|
||||
const config = JSON.parse(t.config) as { args: string[]; cwd: string; exec: string; maxOutputBytes: number };
|
||||
expect(config.exec).toBe("ping");
|
||||
expect(config.args).toEqual(["-c", "1", "localhost"]);
|
||||
expect(config.cwd).toBe("/tmp");
|
||||
@@ -144,39 +151,39 @@ describe("ProbeStore", () => {
|
||||
const t1Id = targets[0]!.id;
|
||||
|
||||
store.insertCheckResult({
|
||||
durationMs: 150.5,
|
||||
failure: null,
|
||||
matched: true,
|
||||
statusDetail: "200 OK",
|
||||
targetId: t1Id,
|
||||
timestamp: "2025-01-01T00:00:00.000Z",
|
||||
matched: true,
|
||||
durationMs: 150.5,
|
||||
statusDetail: "200 OK",
|
||||
failure: null,
|
||||
});
|
||||
|
||||
store.insertCheckResult({
|
||||
durationMs: 300,
|
||||
failure: null,
|
||||
matched: true,
|
||||
statusDetail: "200 OK",
|
||||
targetId: t1Id,
|
||||
timestamp: "2025-01-01T00:00:30.000Z",
|
||||
matched: true,
|
||||
durationMs: 300,
|
||||
statusDetail: "200 OK",
|
||||
failure: null,
|
||||
});
|
||||
|
||||
const failure: CheckFailure = {
|
||||
kind: "error",
|
||||
phase: "duration",
|
||||
path: "$.maxDurationMs",
|
||||
expected: 3000,
|
||||
actual: 5000,
|
||||
expected: 3000,
|
||||
kind: "error",
|
||||
message: "请求耗时 5000ms 超过限制 3000ms",
|
||||
path: "$.maxDurationMs",
|
||||
phase: "duration",
|
||||
};
|
||||
|
||||
store.insertCheckResult({
|
||||
durationMs: null,
|
||||
failure,
|
||||
matched: false,
|
||||
statusDetail: null,
|
||||
targetId: t1Id,
|
||||
timestamp: "2025-01-01T00:01:00.000Z",
|
||||
matched: false,
|
||||
durationMs: null,
|
||||
statusDetail: null,
|
||||
failure,
|
||||
});
|
||||
|
||||
const history = store.getHistory(t1Id, "2025-01-01T00:00:00.000Z", "2025-12-31T23:59:59.999Z", 1, 10);
|
||||
@@ -198,12 +205,12 @@ describe("ProbeStore", () => {
|
||||
|
||||
for (let i = 0; i < 25; i++) {
|
||||
store.insertCheckResult({
|
||||
durationMs: 100 + i,
|
||||
failure: null,
|
||||
matched: true,
|
||||
statusDetail: "200 OK",
|
||||
targetId: t1Id,
|
||||
timestamp: `2025-01-01T01:${String(i).padStart(2, "0")}:00.000Z`,
|
||||
matched: true,
|
||||
durationMs: 100 + i,
|
||||
statusDetail: "200 OK",
|
||||
failure: null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -274,32 +281,32 @@ describe("ProbeStore", () => {
|
||||
test("删除 target 级联删除 check_results", () => {
|
||||
const cascadeStore = new ProbeStore(join(tempDir, "cascade.db"));
|
||||
const cascadeTarget: ResolvedTarget = {
|
||||
type: "http",
|
||||
name: "cascade-test",
|
||||
group: "default",
|
||||
http: { url: "http://cascade.test", method: "GET", headers: {}, maxBodyBytes: 104857600 },
|
||||
http: { headers: {}, maxBodyBytes: 104857600, method: "GET", url: "http://cascade.test" },
|
||||
intervalMs: 30000,
|
||||
name: "cascade-test",
|
||||
timeoutMs: 10000,
|
||||
type: "http",
|
||||
};
|
||||
|
||||
cascadeStore.syncTargets([cascadeTarget]);
|
||||
const t = cascadeStore.getTargets()[0]!;
|
||||
|
||||
cascadeStore.insertCheckResult({
|
||||
durationMs: 100,
|
||||
failure: null,
|
||||
matched: true,
|
||||
statusDetail: "200 OK",
|
||||
targetId: t.id,
|
||||
timestamp: "2025-01-01T00:00:00.000Z",
|
||||
matched: true,
|
||||
durationMs: 100,
|
||||
statusDetail: "200 OK",
|
||||
failure: null,
|
||||
});
|
||||
cascadeStore.insertCheckResult({
|
||||
durationMs: null,
|
||||
failure: { kind: "error", message: "fail", path: "$", phase: "status" },
|
||||
matched: false,
|
||||
statusDetail: null,
|
||||
targetId: t.id,
|
||||
timestamp: "2025-01-01T00:01:00.000Z",
|
||||
matched: false,
|
||||
durationMs: null,
|
||||
statusDetail: null,
|
||||
failure: { kind: "error", phase: "status", path: "$", message: "fail" },
|
||||
});
|
||||
|
||||
expect(cascadeStore.getLatestCheck(t.id)).not.toBeNull();
|
||||
@@ -329,12 +336,12 @@ describe("ProbeStore", () => {
|
||||
const freshStore = new ProbeStore(join(tempDir, "fresh-map.db"));
|
||||
freshStore.syncTargets([
|
||||
{
|
||||
type: "http",
|
||||
name: "no-records",
|
||||
group: "default",
|
||||
http: { url: "http://no.records", method: "GET", headers: {}, maxBodyBytes: 104857600 },
|
||||
http: { headers: {}, maxBodyBytes: 104857600, method: "GET", url: "http://no.records" },
|
||||
intervalMs: 30000,
|
||||
name: "no-records",
|
||||
timeoutMs: 10000,
|
||||
type: "http",
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -359,8 +366,8 @@ describe("ProbeStore", () => {
|
||||
|
||||
const stats2 = stats.get(t2Id);
|
||||
if (stats2) {
|
||||
expect(stats2!.totalChecks).toBe(0);
|
||||
expect(stats2!.availability).toBe(0);
|
||||
expect(stats2.totalChecks).toBe(0);
|
||||
expect(stats2.availability).toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -368,12 +375,12 @@ describe("ProbeStore", () => {
|
||||
const freshStore = new ProbeStore(join(tempDir, "fresh-stats.db"));
|
||||
freshStore.syncTargets([
|
||||
{
|
||||
type: "http",
|
||||
name: "no-stats",
|
||||
group: "default",
|
||||
http: { url: "http://no.stats", method: "GET", headers: {}, maxBodyBytes: 104857600 },
|
||||
http: { headers: {}, maxBodyBytes: 104857600, method: "GET", url: "http://no.stats" },
|
||||
intervalMs: 30000,
|
||||
name: "no-stats",
|
||||
timeoutMs: 10000,
|
||||
type: "http",
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user