fix(api): сохранить неизвестные значения истории скорости
This commit is contained in:
1 parent
e5ff69e243
commit
a558412a27
13 files changed
+119
-70
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import { mapSpeedHistoryRun } from "../services/speed-history-dto.js"
|
||||
import type { FastifyPluginAsyncZod } from "@fastify/type-provider-zod"
|
||||
import { asc, desc, eq } from "drizzle-orm"
|
||||
import { db } from "../db/index.js"
|
||||
@@ -206,33 +207,11 @@ const uptimeRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
|
||||
app.get("/uptime/speed-test/runs", async (_req, reply) => {
|
||||
const rows = await db.select().from(uptimeSpeedTestRuns).orderBy(desc(uptimeSpeedTestRuns.createdAt)).limit(200)
|
||||
return reply.send({
|
||||
runs: rows.map((r) => ({
|
||||
id: r.id,
|
||||
probeId: r.probeId ?? null,
|
||||
srcServerId: String(r.srcServerId),
|
||||
dstServerId: String(r.dstServerId),
|
||||
srcInterface: r.srcInterface || "",
|
||||
dstInterface: r.dstInterface || "",
|
||||
srcAddress: r.srcAddress ?? null,
|
||||
dstAddress: r.dstAddress ?? null,
|
||||
srcInterfaceAddress: r.srcInterfaceAddress ?? null,
|
||||
dstInterfaceAddress: r.dstInterfaceAddress ?? null,
|
||||
protocol: r.protocol === "udp" ? "udp" : "tcp",
|
||||
direction: r.direction === "transmit" || r.direction === "receive" ? r.direction : "both",
|
||||
durationSec: Math.max(3, r.durationSec || 10),
|
||||
txAvgMbps: r.txAvgMbps ?? 0,
|
||||
rxAvgMbps: r.rxAvgMbps ?? 0,
|
||||
afterBtPing: {
|
||||
rttMs: r.pingRttMs ?? null,
|
||||
lossPct: r.pingLossPct ?? null,
|
||||
error: r.pingError ?? null,
|
||||
},
|
||||
status: r.status === "error" ? "error" : "done",
|
||||
error: r.error ?? null,
|
||||
createdAt: r.createdAt,
|
||||
})),
|
||||
})
|
||||
try {
|
||||
return reply.send({ runs: rows.map(mapSpeedHistoryRun) })
|
||||
} catch {
|
||||
return reply.code(502).send({ error: "История скорости содержит некорректную запись. Результаты не подтверждены." })
|
||||
}
|
||||
})
|
||||
|
||||
app.put("/uptime/speed-probes", async (req, reply) => {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { test } from "node:test"
|
||||
import assert from "node:assert/strict"
|
||||
import { mapSpeedHistoryRun, type StoredSpeedRun } from "./speed-history-dto.js"
|
||||
import { speedHistoryResponseSchema } from "@mmapp/contracts"
|
||||
const row:StoredSpeedRun={id:"run-1",probeId:null,srcServerId:1,dstServerId:2,srcInterface:null,dstInterface:null,srcAddress:null,dstAddress:null,srcInterfaceAddress:null,dstInterfaceAddress:null,protocol:"tcp",direction:"both",durationSec:10,txAvgMbps:null,rxAvgMbps:0,pingRttMs:null,pingLossPct:0,pingError:null,status:"done",error:null,createdAt:"2026-10-04T00:00:00Z"}
|
||||
test("История: неизвестный TX остаётся null, подтверждённые нули RX/loss остаются нулями",()=>{const dto=mapSpeedHistoryRun(row);assert.equal(dto.txAvgMbps,null);assert.equal(dto.rxAvgMbps,0);assert.equal(dto.afterBtPing?.rttMs,null);assert.equal(dto.afterBtPing?.lossPct,0)})
|
||||
test("История: нельзя подменить invalid status/protocol/direction/duration/date/rate/server",()=>{for(const invalid of [{status:"running"},{status:"unknown"},{protocol:"unknown"},{direction:"unknown"},{durationSec:0},{createdAt:"bad"},{txAvgMbps:-1},{rxAvgMbps:NaN},{srcServerId:0}])assert.throws(()=>mapSpeedHistoryRun({...row,...invalid}))})
|
||||
test("История: полный DTO, пустой список и уникальные ID",()=>{const dto=mapSpeedHistoryRun(row);assert.deepEqual(speedHistoryResponseSchema.parse({runs:[]}),{runs:[]});assert.equal(speedHistoryResponseSchema.safeParse({runs:[dto,dto]}).success,false);assert.equal(speedHistoryResponseSchema.safeParse({runs:[{...dto,txAvgMbps:undefined}]}).success,false)})
|
||||
|
||||
test("История: timestamp PostgreSQL нормализуется без изменения времени",()=>{assert.equal(mapSpeedHistoryRun({...row,createdAt:"2026-10-04 07:00:00+07"}).createdAt,"2026-10-04T00:00:00.000Z")})
|
||||
@@ -0,0 +1,17 @@
|
||||
import { speedHistoryRunSchema } from "@mmapp/contracts"
|
||||
export type StoredSpeedRun = {
|
||||
id:string;probeId:string|null;srcServerId:number;dstServerId:number;srcInterface:string|null;dstInterface:string|null;
|
||||
srcAddress:string|null;dstAddress:string|null;srcInterfaceAddress:string|null;dstInterfaceAddress:string|null;
|
||||
protocol:string;direction:string;durationSec:number;txAvgMbps:number|null;rxAvgMbps:number|null;
|
||||
pingRttMs:number|null;pingLossPct:number|null;pingError:string|null;status:string;error:string|null;createdAt:string;
|
||||
}
|
||||
export function mapSpeedHistoryRun(row:StoredSpeedRun) {
|
||||
return speedHistoryRunSchema.parse({
|
||||
id:row.id,probeId:row.probeId,srcServerId:String(row.srcServerId),dstServerId:String(row.dstServerId),
|
||||
srcInterface:row.srcInterface ?? "",dstInterface:row.dstInterface ?? "",
|
||||
srcAddress:row.srcAddress,dstAddress:row.dstAddress,srcInterfaceAddress:row.srcInterfaceAddress,dstInterfaceAddress:row.dstInterfaceAddress,
|
||||
protocol:row.protocol,direction:row.direction,durationSec:row.durationSec,
|
||||
txAvgMbps:row.txAvgMbps,rxAvgMbps:row.rxAvgMbps,status:row.status,error:row.error,createdAt:new Date(row.createdAt).toISOString(),
|
||||
afterBtPing:{rttMs:row.pingRttMs,lossPct:row.pingLossPct,error:row.pingError},
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user