From fb32f87d3356d99f0dcdd9f8737bf56da0a7318c Mon Sep 17 00:00:00 2001 From: Denozordec Date: Wed, 7 Oct 2026 14:17:36 +0700 Subject: [PATCH] =?UTF-8?q?fix(uptime):=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D1=8F=D1=82=D1=8C=20=D0=B7=D0=B0=D0=B2=D0=B5=D1=80=D1=88?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=82=D0=B5=D1=81=D1=82=D0=B0=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=20=D0=B7=D0=B0=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D1=8C=D1=8E=20=D1=83=D1=81=D0=BF=D0=B5=D1=85=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../services/bandwidth-test-status.test.ts | 24 ++++++++++++++++++ backend/src/services/bandwidth-test-status.ts | 25 +++++++++++++++++++ backend/src/services/uptime-speed-service.ts | 2 ++ docs/ux-ui-plan.md | 2 +- docs/ux-ui-progress.md | 2 +- docs/ux-ui-stage-12.md | 9 +++++++ 6 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 backend/src/services/bandwidth-test-status.test.ts create mode 100644 backend/src/services/bandwidth-test-status.ts diff --git a/backend/src/services/bandwidth-test-status.test.ts b/backend/src/services/bandwidth-test-status.test.ts new file mode 100644 index 00000000..60d71a13 --- /dev/null +++ b/backend/src/services/bandwidth-test-status.test.ts @@ -0,0 +1,24 @@ +import { test } from "node:test" +import assert from "node:assert/strict" +import { assertBandwidthTestCompleted } from "./bandwidth-test-status.js" +import { readBandwidthTestRates } from "./bandwidth-test-rates.js" + +test("confirmed completion preserves one-direction unknown and explicit zero", () => { + const rows = [{ status: "connecting" }, { status: "running", "rx-current": "20Mbps" }, { status: "done testing", "rx-total-average": "0bps" }] + assert.doesNotThrow(() => assertBandwidthTestCompleted(rows)) + const result = readBandwidthTestRates(rows) + assert.equal(result.txAvgMbps, null) + assert.equal(result.rxAvgMbps, 0) + assert.doesNotThrow(() => assertBandwidthTestCompleted([{ status: "done testing" }, { "tx-total-average": "10Mbps" }])) +}) +test("all native failures reject even with valid rates or a subsequent success marker", () => { + for (const status of ["authentication failed", "can not connect", "can not start test", "remote is busy", "test unsupported", "disconnected"]) { + assert.throws(() => assertBandwidthTestCompleted([{ status, "tx-total-average": "50Mbps" }])) + assert.throws(() => assertBandwidthTestCompleted([{ status }, { status: "done testing" }])) + } +}) +test("missing, incomplete and malformed native replies never count as completed", () => { + for (const raw of [null, {}, [], [null], [[]], [{ status: 8 }], [{ status: "" }], [{ "tx-total-average": "50Mbps" }], [{ status: "running" }], [{ status: "connecting" }], [{ status: "done testing" }, { status: "running" }], [{ status: "done testing" }, { status: "future status" }], [{ status: "done testing", error: "failure" }], [{ status: "done testing", failure: "failure" }], [{ status: "done testing", "!trap": true }]]) { + assert.throws(() => assertBandwidthTestCompleted(raw), /Результат операции неизвестен/) + } +}) diff --git a/backend/src/services/bandwidth-test-status.ts b/backend/src/services/bandwidth-test-status.ts new file mode 100644 index 00000000..0b53af6c --- /dev/null +++ b/backend/src/services/bandwidth-test-status.ts @@ -0,0 +1,25 @@ +const failures: Record = { + "authentication failed": "RouterOS отклонил авторизацию bandwidth-test", + "can not connect": "Bandwidth-test не смог подключиться к назначению", + "can not start test": "RouterOS не смог начать bandwidth-test", + "remote is busy": "Устройство назначения занято", + "test unsupported": "Устройство назначения не поддерживает bandwidth-test", + "disconnected": "Соединение bandwidth-test прервано до завершения", +} + +/** HTTP success is insufficient: the last reported native state must confirm completion. */ +export function assertBandwidthTestCompleted(raw: unknown): asserts raw is Array> { + const unknown = () => new Error("RouterOS не подтвердил завершение bandwidth-test. Результат операции неизвестен") + if (!Array.isArray(raw) || !raw.length) throw unknown() + let lastStatus: string | undefined + for (const row of raw) { + if (!row || typeof row !== "object" || Array.isArray(row)) throw unknown() + if ("error" in row || "failure" in row || "!trap" in row) throw unknown() + if (row.status === undefined) continue + if (typeof row.status !== "string" || !row.status.trim()) throw unknown() + const status = row.status.trim().toLowerCase() + if (Object.hasOwn(failures, status)) throw new Error(failures[status]) + lastStatus = status + } + if (lastStatus !== "done testing") throw unknown() +} diff --git a/backend/src/services/uptime-speed-service.ts b/backend/src/services/uptime-speed-service.ts index edc3eacb..39df7cba 100644 --- a/backend/src/services/uptime-speed-service.ts +++ b/backend/src/services/uptime-speed-service.ts @@ -7,6 +7,7 @@ import type { RosIpAddress } from "../types/server.js" import { withBtestNodeLocks } from "./btest-node-locks.js" import { MikrotikClient } from "./mikrotik.js" import { parsePing } from "./uptime-collector.js" +import { assertBandwidthTestCompleted } from "./bandwidth-test-status.js" import { readBandwidthTestRates } from "./bandwidth-test-rates.js" function firstUsableIpOnInterface(rows: RosIpAddress[], iface: string): string { @@ -106,6 +107,7 @@ export async function executeBandwidthSpeedTestInner(params: SpeedTestRunParams) direction, durationSec, }) + assertBandwidthTestCompleted(rows) const { txAvgMbps: txFinal, rxAvgMbps: rxFinal, txSeries, rxSeries } = readBandwidthTestRates(rows) await new Promise((resolve) => setTimeout(resolve, 3000)) diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index 77c9c5d0..ffd99eae 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -1,6 +1,6 @@ # План реализации UX/UI по пятичасовым окнам -Актуальное продолжение 2026-10-07: 12.3.73 завершён: nullable bandwidth producer/POST consumer, проверка runId, 16pure/71browser/8context и frontend/backend build. Следующий12.3.74: terminal confirmation RouterOS; затем75OSPF RX/TX mapping,76mobile pinned-column readable geometry. Команды/права не менять,12.5 отдельно. +Актуальное продолжение 2026-10-07: 12.3.74 завершён: backend проверяет native terminal status перед Ping/DB success write,16pure и backend build. Следующий12.3.75: OSPF RX/TX mapping; затем76mobile pinned-column readable geometry. Команды/права не менять,12.5 отдельно. Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-04. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат. diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index 4bfdea36..e658571d 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -1,6 +1,6 @@ # UX/UI: журнал реализации -Актуальное продолжение 2026-10-07: 12.3.73 завершён: nullable bandwidth producer/POST consumer, проверка runId, 16pure/71browser/8context и frontend/backend build. Следующий12.3.74: terminal confirmation RouterOS; затем75OSPF RX/TX mapping,76mobile pinned-column readable geometry. Команды/права не менять,12.5 отдельно. +Актуальное продолжение 2026-10-07: 12.3.74 завершён: backend проверяет native terminal status перед Ping/DB success write,16pure и backend build. Следующий12.3.75: OSPF RX/TX mapping; затем76mobile pinned-column readable geometry. Команды/права не менять,12.5 отдельно. Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index a62bc04e..f58c9805 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -817,3 +817,12 @@ CPU final:1000routes13.3ms;100000(10×100×100)49.7ms;100000(100×10×100)44.0ms 7 pure parser/DTO +9 isolated scheduler journal tests прошли.71 main browser (uptime-links, speed-data, large-speed-history),8 context прошли; non-incremental TypeScript, targeted lint (9 существующих предупреждений uptime,0 errors), backend и frontend production build успешны. Первые два новых браузерных прогона обнаружили неверный KPI role selector и неоднозначность одинаковой подписи в истории/строке; selectors уточнены, финальный связанный профиль полностью passed. Historical large-speed-history-320 PNG восстановлен. Следующий74: native terminal status ещё не проверяется перед done/DB write.75OSPF direction и76mobile metric geometry остаются. DTO доказывает целостность ответа, но не факт выполнения на устройстве. Реальная RouterOS/PG приёмка12.5 отдельно. + + +## 12.3.74 — подтверждённое завершение bandwidth-test + +2026-10-07. Pure guard проверяет структуру native rows, все известные failure statuses и последний сообщённый status. Только done testing допускает дальнейший расчёт, after-ping и DB success write. Connecting/running, отсутствие статуса, неизвестный status, malformed response или error/failure/trap marker отклоняются с неизвестным результатом; native failures имеют краткие русские сообщения. Числовой enum не принимается как строковое подтверждение. Подтверждённый one-direction result может иметь null на другой стороне и0 на измеренной. + +16 pure tests (3status,4rates,9scheduler) и backend build passed. Проверка placement/callers по source: HTTP и scheduled collection используют общий execute, их catch вызывает прежний failure recorder, который сохраняет старые probe averages и пишет error run с nullable rates. Полная DB/network orchestration не запускалась; это source review и pure guard tests, а не внешняя приёмка. Команда bandwidth-test и её параметры, locks, routing rules, permissions и DB schema не менялись. + +Источник native statuses: [RouterOS CLI bandwidth-test](https://manual.mikrotik.com/docs/cli-reference/tool/bandwidth-test/). Последний reported done допускает последующие rows без status, но любые failure markers отклоняются даже после completion. Реальный REST shape/версия RouterOS и PG writes требуют12.5. Следующие75direction OSPF и76mobile geometry.