fix(evobgp): требовать подтверждение проверки соединения

This commit is contained in:
Denozordec committed 2026-10-01 22:17:42 +07:00
1 parent 0d2affaf6b
commit dd387cf379
7 files changed
+64 -3

No files matched your search

+1 -1
View File
@@ -226,7 +226,7 @@ REST запрос → ожидание → подтверждённый output /
| Пользователи | [Профиль](ux-ui/screens/users-profile-desktop.png), [ошибка](ux-ui/screens/users-error-desktop.png) | [390](ux-ui/screens/users-profile-mobile.png), [320](ux-ui/screens/users-profile-narrow.png) |
| Контейнеры | [Обзор](ux-ui/screens/containers-overview-desktop.png), [снимок](ux-ui/screens/containers-export-desktop.png) | [390](ux-ui/screens/containers-export-mobile.png), [320](ux-ui/screens/containers-export-narrow.png) |
Каталог Evolution защищён от неполного HTTP-ответа в 11Б.3.3.4.1; testConnection receipt и состояния интеграции остаются в объёме 11Б. Актуальная точка продолжения: 11Б.3.3.5 — подключение strict settings readers, независимые known/error/time и dirty drafts. API основа проверена в 11Б.3.3.4; пока не подключена к странице. Независимые scheduler status/runs и достоверные KPI приняты в 11Б.3.3.3. Strict history/unknown/свежесть приняты в 11Б.3.3.2. Контекст/abort/revision и исходный lint исправлены в 11Б.3.3.1; далее независимые readers/dirty states и операции. GeoIP API принят в 11Б.3.2. Scheduler API основа проверена в 11Б.3.1, пока не подключена к странице. Совместимость JSONB/legacy snapshot и foreign guard проверены в 11Б.2; исходные lint/context ограничения страницы описаны в журнале. Общий runner исправлен в 11Б.1; карта оставшегося этапа — [11Б](ux-ui-stage-11b.md). Этап 11А принят локально в 11А.33: backups 71 основных + 28 source E2E, 27 frontend unit, backend профиль и production build. Backup scheduler cleanup проверен в 11А.32; общий dispatcher/journal status исправлен в 11Б.1, реальные интеграции остаются внешней приёмкой. S3/partial job/initial errors приняты в 11А.31.2.2. Диалоги ошибок приняты в 11А.31.2.1. Frame/первые 9 экранов приняты в 11А.31.1. URL/Filters контекст принят в 11А.30. Клиентский checkpoint restore принят в 11А.29.5. Backend outcome неизвестного импорта принят в 11А.29.4. Идентичность receipt принята в 11А.29.3. Backend cleanup удаления принят в 11А.29.2. Frontend delete/restore/download принят в 11А.29.1, затем URL/Frame/визуальная приёмка и scheduler cleanup. Frontend checkpoint/создание/serial polling приняты локально в 11А.28.2. API/основа наблюдения реализованы в 11А.28.1. Настройки расписания/S3 реализованы в 11А.27.1–11А.27.2. Каталоги/source/права реализованы в 11А.25, backend результат ручных операций исправлен в 11А.26. Сертификаты приняты локально в 11А.24, backups API стал строгим. State API, reader, snapshot merge и nullable представление подключены в 11А.14–11А.17.3; таблица требований — [этап 11А](ux-ui-stage-11a.md). Резервные копии и весь 11А приняты локально в 11А.33; внешние ограничения — журнал.
Каталог Evolution защищён от неполного HTTP-ответа в 11Б.3.3.4.1; Подтверждение testConnection исправлено в 11Б.3.3.4.2; состояния интеграции остаются в объёме 11Б. Актуальная точка продолжения: 11Б.3.3.5 — подключение strict settings readers, независимые known/error/time и dirty drafts. API основа проверена в 11Б.3.3.4; пока не подключена к странице. Независимые scheduler status/runs и достоверные KPI приняты в 11Б.3.3.3. Strict history/unknown/свежесть приняты в 11Б.3.3.2. Контекст/abort/revision и исходный lint исправлены в 11Б.3.3.1; далее независимые readers/dirty states и операции. GeoIP API принят в 11Б.3.2. Scheduler API основа проверена в 11Б.3.1, пока не подключена к странице. Совместимость JSONB/legacy snapshot и foreign guard проверены в 11Б.2; исходные lint/context ограничения страницы описаны в журнале. Общий runner исправлен в 11Б.1; карта оставшегося этапа — [11Б](ux-ui-stage-11b.md). Этап 11А принят локально в 11А.33: backups 71 основных + 28 source E2E, 27 frontend unit, backend профиль и production build. Backup scheduler cleanup проверен в 11А.32; общий dispatcher/journal status исправлен в 11Б.1, реальные интеграции остаются внешней приёмкой. S3/partial job/initial errors приняты в 11А.31.2.2. Диалоги ошибок приняты в 11А.31.2.1. Frame/первые 9 экранов приняты в 11А.31.1. URL/Filters контекст принят в 11А.30. Клиентский checkpoint restore принят в 11А.29.5. Backend outcome неизвестного импорта принят в 11А.29.4. Идентичность receipt принята в 11А.29.3. Backend cleanup удаления принят в 11А.29.2. Frontend delete/restore/download принят в 11А.29.1, затем URL/Frame/визуальная приёмка и scheduler cleanup. Frontend checkpoint/создание/serial polling приняты локально в 11А.28.2. API/основа наблюдения реализованы в 11А.28.1. Настройки расписания/S3 реализованы в 11А.27.1–11А.27.2. Каталоги/source/права реализованы в 11А.25, backend результат ручных операций исправлен в 11А.26. Сертификаты приняты локально в 11А.24, backups API стал строгим. State API, reader, snapshot merge и nullable представление подключены в 11А.14–11А.17.3; таблица требований — [этап 11А](ux-ui-stage-11a.md). Резервные копии и весь 11А приняты локально в 11А.33; внешние ограничения — журнал.
| Экран | ПК | Телефон |
| --- | --- | --- |
+8 -1
View File
@@ -28,7 +28,7 @@
| 9 | VPN, Firewall и Filters | Завершён в границах локальной приёмки 2026-09-30; блоки 9.1–9.64, ограничения в 9.64 |
| 10 | Маршрутизация, диагностика, карта и терминал | Завершён в границах локальной приёмки 2026-10-01; блоки 10.1–10.71, внешние ограничения в 10.71 |
| 11А | Пользователи, контейнеры, сертификаты и резервные копии | Завершён в границах локальной приёмки 2026-10-01; 11А.1–11А.33, внешние ограничения в 11А.33 |
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | В работе: 11Б.1–11Б.3.3.4.1; status/history/KPI, strict settings API основа и защита каталога Evolution проверены; далее readers/dirty формы |
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | В работе: 11Б.1–11Б.3.3.4.2; status/history/KPI, strict settings API основа и защита каталога Evolution проверены; далее readers/dirty формы |
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат |
@@ -36,6 +36,13 @@
## Блок 11Б.3.3.4.2 — подтверждение проверки EvoBGP (2026-10-01)
- testConnection проверяет явное ok=true: HTTP 200 с false, пустым или некорректным receipt не становится сообщением об установленном соединении. Ошибка возвращается существующей форме, без повторного POST.
- Прошли unit для true/false/missing/null/неверных типов, ESLint изменённых файлов и полный TypeScript. Два профильных браузерных теста прошли; после расширения новый тест повторно проверил false → ручная повторная проверка → true и ровно один запрос на каждое нажатие. HTTP-подмена не доказывает соединение с реальным Portal.
- Полная production сборка прошла в предыдущем блоке 11Б.3.3.4.1; текущая маленькая правка проверена TypeScript и браузером. Общие settings/RBAC/dirty/context и сохранение последнего каталога остаются впереди. Основная точка продолжения — 11Б.3.3.5.
- Блок сохранён отдельным локальным коммитом без push. Процент недельной квоты округлён до 100%, ordinaryUsageAllowed при начале блока оставался true.
## Блок 11Б.3.3.4.1 — проверка каталога Evolution (2026-10-01)
- Каталог Evolution проверяется до записи в состояние: обязательные коллекции, поля строк, дата снимка, числовые счётчики и уникальность идентификаторов. Неполный HTTP 200 больше не обрушает боковую навигацию; в настройках EvoBGP отображается понятная ошибка.
+7
View File
@@ -110,3 +110,10 @@ ReUI PRO/base-nova; существующие Frame, Data Grid v9, KpiStatGrid, S
- Прошли 3 unit, 1 браузерный тест через обычные API-пути с подменой HTTP, ESLint изменённых файлов и production frontend/contracts build со встроенным TypeScript. В браузере проверены доступность журнала, переход в настройки EvoBGP и отсутствие pageerror при malformed каталоге. Реальный Evolution Portal не проверен.
- Следующий основной блок остаётся 11Б.3.3.5: подключение strict settings readers, независимые known/error/time и dirty drafts. Также выявлено: Evolution testConnection пока принимает любой успешный HTTP вместо подтверждения ok=true; исправление и профильная проверка ещё впереди.
- На завершении пятичасовой лимит использован на 42%, недельный показывает 100%. Новый блок не начат; изменения сохранены локально без push.
## Блок 11Б.3.3.4.2 — подтверждение проверки EvoBGP (2026-10-01)
- testConnection проверяет явное ok=true: HTTP 200 с false, пустым или некорректным receipt не становится сообщением об установленном соединении. Ошибка возвращается существующей форме, без повторного POST.
- Прошли unit для true/false/missing/null/неверных типов, ESLint изменённых файлов и полный TypeScript. Два профильных браузерных теста прошли; после расширения новый тест повторно проверил false → ручная повторная проверка → true и ровно один запрос на каждое нажатие. HTTP-подмена не доказывает соединение с реальным Portal.
- Полная production сборка прошла в предыдущем блоке 11Б.3.3.4.1; текущая маленькая правка проверена TypeScript и браузером. Общие settings/RBAC/dirty/context и сохранение последнего каталога остаются впереди. Основная точка продолжения — 11Б.3.3.5.
- Блок сохранён отдельным локальным коммитом без push. Процент недельной квоты округлён до 100%, ordinaryUsageAllowed при начале блока оставался true.
+3 -1
View File
@@ -10,6 +10,7 @@ import {
} from "react"
import { useDataSource } from "@/lib/data-source"
import type { Domain, IpRange, Asn } from "@/lib/data"
import { confirmEvoBgpConnection } from "@/lib/evobgp-test-receipt"
import { parseEvoBgpCatalog } from "@/lib/evobgp-catalog"
import { ApiClientError, requestJson } from "@/shared/api/http-client"
@@ -172,10 +173,11 @@ export function EvoBGPProvider({ children }: { children: React.ReactNode }) {
const testConnection = useCallback(
async (draft?: EvoBgpTestDraft) => {
try {
await requestJson<{ ok?: boolean }>(backendUrl, "/api/evobgp/test", {
const receipt = await requestJson<unknown>(backendUrl, "/api/evobgp/test", {
method: "POST",
body: JSON.stringify(draft ?? {}),
})
confirmEvoBgpConnection(receipt)
return { ok: true, message: "Соединение с EvoBGP установлено" }
} catch (e) {
return { ok: false, message: errorMessage(e, "Ошибка") }
+10
View File
@@ -0,0 +1,10 @@
import assert from "node:assert/strict"
import { test } from "node:test"
import { confirmEvoBgpConnection } from "./evobgp-test-receipt"
test("only explicit true confirms the connection", () => {
assert.doesNotThrow(() => confirmEvoBgpConnection({ ok: true }))
for (const raw of [null, {}, { ok: false }, { ok: 1 }, { ok: "true" }, []]) {
assert.throws(() => confirmEvoBgpConnection(raw), /не подтверждено сервером/)
}
})
+9
View File
@@ -0,0 +1,9 @@
import { z } from "zod"
const receipt = z.object({ ok: z.literal(true) })
export function confirmEvoBgpConnection(raw: unknown): void {
if (!receipt.safeParse(raw).success) {
throw new Error("Соединение с EvoBGP не подтверждено сервером")
}
}
+26
View File
@@ -0,0 +1,26 @@
import { test, expect } from "@playwright/test"
test("Evolution: HTTP 200 без ok=true не подтверждает соединение", async ({ page }) => {
let confirmed = false; let tests = 0;
const errors: string[] = []; let catalogs = 0; page.on("pageerror", error => errors.push(error.message))
await page.route("**/health", r => r.fulfill({ json: { ok: true } }))
await page.route("**/api/**", r => {
const path = new URL(r.request().url()).pathname
if (path === "/api/auth/config") return r.fulfill({ json: { required: false } })
if (path === "/api/evobgp/settings") return r.fulfill({ json: { baseUrl: "https://evo.example.test", enabled: true, secretConfigured: true } })
if (path === "/api/evobgp/test") { tests++; return r.fulfill({ json: { ok: confirmed } }) }
if (path === "/api/evobgp/catalog") { catalogs++; return r.fulfill({ json: {} }) }
return r.fulfill({ status: 503, json: { error: "outside catalogue fixture" } })
})
await page.goto("/data-collection"); await expect.poll(() => catalogs).toBeGreaterThan(0)
await expect(page.getByText("Журнал прогонов", { exact: true })).toBeVisible()
await page.goto("/settings"); await page.getByRole("button", { name: "EvoBGP", exact: true }).click(); await expect(page.getByText("Каталог Evolution не подтверждён: получен некорректный ответ сервера", { exact: true })).toBeVisible()
await page.getByRole("button", { name: "Проверить ключ", exact: true }).click()
await expect(page.getByText("Соединение с EvoBGP не подтверждено сервером", { exact: true })).toBeVisible()
await expect(page.getByText("Соединение с EvoBGP установлено", { exact: true })).toHaveCount(0)
expect(tests).toBe(1)
confirmed = true
await page.getByRole("button", { name: "Проверить ключ", exact: true }).click()
await expect(page.getByText("Соединение с EvoBGP установлено", { exact: true })).toBeVisible()
expect(tests).toBe(2)
expect(errors).toEqual([])
})