From 58dea92e5fd1621886358bb58118d2ece299f9ef Mon Sep 17 00:00:00 2001 From: imengying Date: Mon, 7 Sep 2026 22:04:25 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=B7=A8=E5=88=86=E9=92=9F?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E7=9A=84=E6=B5=8B=E8=AF=95=E8=AF=AF=E6=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/smoke.test.ts | 52 ++++++++++++++++++++++++++++++++++++++ scripts/smoke-test.sh | 30 ++++++++++++++++++++-- 2 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 frontend/src/smoke.test.ts diff --git a/frontend/src/smoke.test.ts b/frontend/src/smoke.test.ts new file mode 100644 index 0000000..9a4dcad --- /dev/null +++ b/frontend/src/smoke.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, test } from "bun:test"; +import { spawnSync } from "node:child_process"; +import { readFileSync } from "node:fs"; + +const script = readFileSync(new URL("../../scripts/smoke-test.sh", import.meta.url), "utf8"); +const assertion = script.match(/^assert_latency_history\(\) \{[\s\S]*?^\}/m)?.[0]; +if (!assertion) throw new Error("Missing latency history smoke assertion"); + +const taskId = "smoke-latency"; +const point = (timestamp: number, latency_ms: number, packet_loss: number, task_id = taskId) => ({ + task_id, timestamp, latency_ms, packet_loss, +}); + +function check(points: ReturnType[], tasks = [{ id: taskId }]) { + return spawnSync("sh", ["-c", `${assertion}\nassert_latency_history "$1"`, "smoke-test", taskId], { + input: JSON.stringify({ tasks, points }), encoding: "utf8", + }); +} + +describe("latency history smoke assertion", () => { + test("accepts a single averaged bucket with floating point rounding", () => { + const result = check([point(120, 38.400000000000006, 50)]); + expect(result.status).toBe(0); + expect(result.stderr).toBe(""); + }); + + test("accepts samples split across a minute boundary in either response order", () => { + const points = [point(120, 28.4, 25), point(180, 48.4, 75)]; + expect(check(points).status).toBe(0); + expect(check([...points].reverse()).status).toBe(0); + }); + + test("rejects missing, corrupt, duplicate and unrelated samples", () => { + for (const points of [ + [], [point(120, 28.4, 25)], [point(120, 38.4, 51)], + [point(120, 38.4, 50, "other-task")], + [point(120, 28.4, 25), point(240, 48.4, 75)], + [point(120, 28.4, 25), point(180, 47, 75)], + [point(120, 28.4, 25), point(180, 48.4, 76)], + [point(120, 38.4, 50), point(120, 38.4, 50)], + ]) { + const result = check(points); + expect(result.status).toBe(1); + expect(result.stderr).toContain("latency history assertion failed"); + expect(result.stderr).toContain(JSON.stringify({ tasks: [{ id: taskId }], points })); + } + }); + + test("requires the task to remain assigned", () => { + expect(check([point(120, 38.4, 50)], []).status).toBe(1); + }); +}); diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh index 46f4e47..8324d7f 100644 --- a/scripts/smoke-test.sh +++ b/scripts/smoke-test.sh @@ -32,6 +32,28 @@ step() { printf 'smoke: %s\n' "$1" >&2 } +assert_latency_history() { + latency_history_json=$(cat) + # Samples one second apart can land in adjacent minute buckets. + if printf '%s' "$latency_history_json" | jq -e --arg task_id "$1" ' + def near($expected): type == "number" and . > ($expected - 0.001) and . < ($expected + 0.001); + (.tasks | any(.id == $task_id)) and + (.points | map(select(.task_id == $task_id)) | sort_by(.timestamp) | + if length == 1 then + (.[0].latency_ms | near(38.4)) and (.[0].packet_loss | near(50)) + elif length == 2 then + (.[1].timestamp - .[0].timestamp == 60) and + (.[0].latency_ms | near(28.4)) and (.[0].packet_loss | near(25)) and + (.[1].latency_ms | near(48.4)) and (.[1].packet_loss | near(75)) + else false end) + ' >/dev/null; then + return 0 + fi + printf '%s\n' 'smoke: latency history assertion failed; expected one averaged bucket or two adjacent sample buckets' >&2 + printf '%s\n' "$latency_history_json" >&2 + return 1 +} + admin_token= server_id= latency_task_id= @@ -231,18 +253,22 @@ rotated_agent_token=$(MONITOR_BASE_URL="$MONITOR_BASE_URL" MONITOR_ADMIN_TOKEN=" [ "$rotated_agent_token" != "$agent_token" ] agent_token=$rotated_agent_token -step "persisted metrics" +step "persisted metrics: admin IP metadata" request -H "Authorization: Bearer $admin_token" "$MONITOR_BASE_URL/api/admin/servers" | \ jq -e --arg id "$server_id" '.servers | any(.id == $id and .last_ip == "8.8.8.8")' >/dev/null +step "persisted metrics: public snapshot" post_report_bootstrap=$(request -H "Authorization: Bearer $admin_token" "$MONITOR_BASE_URL/api/bootstrap") if ! printf '%s' "$post_report_bootstrap" | jq -e --arg id "$server_id" --arg task_id "$latency_task_id" '.servers | any(.id == $id and .cpu == 18.5 and .gpu_usage == 32.5 and .disk_await_ms == 1.4 and (.gpus | length) == 1 and (.disks | length) == 1 and .disk_used == 21474836480 and .traffic_limit == 107374182400 and .net_rx_total == 2684354560 and .net_tx_total == 1342177280 and .price == 9.9 and (has("last_ip") | not) and (.latency | any(.task_id == $task_id and .latency_ms == 48.4 and .packet_loss == 75)))' >/dev/null; then printf '%s' "$post_report_bootstrap" | jq --arg id "$server_id" '.servers[] | select(.id == $id)' >&2 exit 1 fi +step "persisted metrics: GPU history" request -H "Authorization: Bearer $admin_token" "$MONITOR_BASE_URL/api/history/$server_id?hours=1" | jq -e '.points | length >= 1 and any(.gpu_usage == 32.5)' >/dev/null -request -H "Authorization: Bearer $admin_token" "$MONITOR_BASE_URL/api/latency/$server_id?hours=1" | jq -e --arg task_id "$latency_task_id" '(.tasks | any(.id == $task_id)) and (.points | any(.task_id == $task_id and .latency_ms > 38.399 and .latency_ms < 38.401 and .packet_loss > 49.999 and .packet_loss < 50.001))' >/dev/null +step "persisted metrics: latency history" +request -H "Authorization: Bearer $admin_token" "$MONITOR_BASE_URL/api/latency/$server_id?hours=1" | assert_latency_history "$latency_task_id" +step "persisted metrics: latency task unassignment" request -H "Authorization: Bearer $admin_token" -H 'Content-Type: application/json' -X PATCH \ --data '{"name":"Smoke TCP","task_type":"tcp","target":"example.com","port":443,"interval_seconds":60,"default_enabled":true,"server_ids":[]}' \ "$MONITOR_BASE_URL/api/admin/latency-tasks/$latency_task_id" >/dev/null