fix(ui): проверять источник IPv4 для диагностики
This commit is contained in:
1 parent
d0c715e93c
commit
b60b7ac8e8
8 files changed
+81
-26
No files matched your search
+28
-22
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { Suspense, useEffect, useRef, useState, useMemo, useCallback } from "react"
|
import { Suspense, useEffect, useRef, useState, useMemo, useCallback } from "react"
|
||||||
import { useSearchParams } from "next/navigation"
|
import { useSearchParams } from "next/navigation"
|
||||||
|
import { readProbeSource } from "@/lib/probe-source"
|
||||||
import { readProbeUrl, patchProbeUrl } from "@/lib/probe-url"
|
import { readProbeUrl, patchProbeUrl } from "@/lib/probe-url"
|
||||||
import { PageHeader } from "@/components/page-header"
|
import { PageHeader } from "@/components/page-header"
|
||||||
import { FormToggle } from "@/components/form-kit"
|
import { FormToggle } from "@/components/form-kit"
|
||||||
@@ -616,7 +617,8 @@ function ProbesContent() {
|
|||||||
const [bwDuration, setBwDuration] = useState(10)
|
const [bwDuration, setBwDuration] = useState(10)
|
||||||
const [runBusy, setRunBusy] = useState(false)
|
const [runBusy, setRunBusy] = useState(false)
|
||||||
/** В live: IPv4 для src-address (DNS A-запись к host API); undefined = грузим, null = не удалось */
|
/** В live: IPv4 для src-address (DNS A-запись к host API); undefined = грузим, null = не удалось */
|
||||||
const [rosSrcV4, setRosSrcV4] = useState<string | null | undefined>(undefined)
|
const [source, setSource] = useState<{ key: string; ipv4?: string | null; error?: string; loading: boolean } | null>(null)
|
||||||
|
const [sourceRevision, setSourceRevision] = useState(0)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isLive || !canRead) return
|
if (!isLive || !canRead) return
|
||||||
@@ -664,27 +666,6 @@ function ProbesContent() {
|
|||||||
}))
|
}))
|
||||||
), [allServers])
|
), [allServers])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isLive || !canRead || liveLoad !== "idle" || !allServers.some(s => s.id === srcId && s.enabled)) {
|
|
||||||
queueMicrotask(() => setRosSrcV4(undefined))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const n = Number.parseInt(srcId, 10)
|
|
||||||
if (!Number.isFinite(n)) {
|
|
||||||
setRosSrcV4(undefined)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let cancelled = false
|
|
||||||
setRosSrcV4(undefined)
|
|
||||||
void apiFetch<{ ipv4: string | null }>(`/api/servers/${n}/ros-src-address`)
|
|
||||||
.then((r) => {
|
|
||||||
if (!cancelled) setRosSrcV4(r.ipv4)
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
if (!cancelled) setRosSrcV4(null)
|
|
||||||
})
|
|
||||||
return () => { cancelled = true }
|
|
||||||
}, [isLive, canRead, liveLoad, allServers, srcId, apiFetch])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isLive || !canReadGre || liveLoad !== "idle" || !allServers.some(s => s.id === srcId && s.enabled)) return
|
if (!isLive || !canReadGre || liveLoad !== "idle" || !allServers.some(s => s.id === srcId && s.enabled)) return
|
||||||
@@ -735,6 +716,22 @@ function ProbesContent() {
|
|||||||
|
|
||||||
const bwTunnels = tunnelsForSrc
|
const bwTunnels = tunnelsForSrc
|
||||||
const srcServer = useMemo(() => allServers.find(s => s.id === srcId), [allServers, srcId])
|
const srcServer = useMemo(() => allServers.find(s => s.id === srcId), [allServers, srcId])
|
||||||
|
const sourceKey = JSON.stringify([srcId, srcServer?.host])
|
||||||
|
const sourceRequired = tool === "ping" || tool === "traceroute" || tool === "mtu"
|
||||||
|
const sourceCurrent = source?.key === sourceKey ? source : null
|
||||||
|
const rosSrcV4 = sourceCurrent?.ipv4
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isLive || !canRead || liveLoad !== "idle" || !srcServer?.enabled || !sourceRequired) return
|
||||||
|
const host = srcServer.host, id = srcServer.id
|
||||||
|
if (!/^[1-9][0-9]*$/.test(id) || !Number.isSafeInteger(Number(id))) return
|
||||||
|
let cancelled = false
|
||||||
|
const ctrl = new AbortController()
|
||||||
|
queueMicrotask(() => { if (!cancelled) setSource({ key: sourceKey, loading: true }) })
|
||||||
|
void apiFetch<unknown>("/api/servers/" + id + "/ros-src-address", { signal: ctrl.signal })
|
||||||
|
.then(raw => { const ipv4 = readProbeSource(raw, host); if (!cancelled) setSource({ key: sourceKey, loading: false, ipv4 }) })
|
||||||
|
.catch(error => { if (!cancelled) setSource({ key: sourceKey, loading: false, error: error instanceof Error ? error.message : "Источник preview неизвестен." }) })
|
||||||
|
return () => { cancelled = true; ctrl.abort() }
|
||||||
|
}, [isLive, canRead, liveLoad, srcServer, sourceRequired, sourceKey, apiFetch, sourceRevision])
|
||||||
const greReady = !isLive || (greState?.id === srcId && greState.state === "ready")
|
const greReady = !isLive || (greState?.id === srcId && greState.state === "ready")
|
||||||
const bwTun = useMemo(() => bwTunnels.find(t => t.id === bwTunId), [bwTunnels, bwTunId])
|
const bwTun = useMemo(() => bwTunnels.find(t => t.id === bwTunId), [bwTunnels, bwTunId])
|
||||||
|
|
||||||
@@ -1000,6 +997,15 @@ function ProbesContent() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{isLive && srcServer?.enabled && sourceRequired && (
|
||||||
|
<div className="text-xs text-muted-foreground">
|
||||||
|
{sourceCurrent?.error ? <div role="alert" aria-label="Источник preview"><p>{sourceCurrent.error}</p><Button type="button" size="sm" variant="outline" onClick={() => setSourceRevision(n => n + 1)}>Повторить определение IPv4</Button></div>
|
||||||
|
: !sourceCurrent || sourceCurrent.loading ? <p role="status">Определяем IPv4 для preview…</p>
|
||||||
|
: <p role="status">{sourceCurrent.ipv4 === null ? "IPv4 для preview не определён." : "IPv4 для preview: " + sourceCurrent.ipv4}</p>}
|
||||||
|
<p>Preview справочный. Backend повторно определяет источник при запуске; пустой результат не подтверждает исходный адрес команды.</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* ── tool selector + config ── */}
|
{/* ── tool selector + config ── */}
|
||||||
<OpsPanel contentClassName="pt-4 pb-4 px-4 flex flex-col gap-4">
|
<OpsPanel contentClassName="pt-4 pb-4 px-4 flex flex-col gap-4">
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
| 7 | Серверы и мониторинг: детали, формы, URL-контекст, права | Завершён в границах локальной приёмки 2026-09-23; внешние ограничения ниже |
|
| 7 | Серверы и мониторинг: детали, формы, URL-контекст, права | Завершён в границах локальной приёмки 2026-09-23; внешние ограничения ниже |
|
||||||
| 8 | Трафик и статистика | Завершён 2026-09-28 в границах локальной приёмки; ограничения в блоке 8.13 |
|
| 8 | Трафик и статистика | Завершён 2026-09-28 в границах локальной приёмки; ограничения в блоке 8.13 |
|
||||||
| 9 | VPN, Firewall и Filters | Завершён в границах локальной приёмки 2026-09-30; блоки 9.1–9.64, ограничения в 9.64 |
|
| 9 | VPN, Firewall и Filters | Завершён в границах локальной приёмки 2026-09-30; блоки 9.1–9.64, ограничения в 9.64 |
|
||||||
| 10 | Маршрутизация, диагностика, карта и терминал | В работе: 10.1–10.63; оптимизатор, карта и терминал приняты локально; диагностика /probes в работе |
|
| 10 | Маршрутизация, диагностика, карта и терминал | В работе: 10.1–10.64; оптимизатор, карта и терминал приняты локально; диагностика /probes в работе |
|
||||||
| 11А | Пользователи, контейнеры, сертификаты и резервные копии | Не начат |
|
| 11А | Пользователи, контейнеры, сертификаты и резервные копии | Не начат |
|
||||||
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Не начат |
|
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Не начат |
|
||||||
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат |
|
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат |
|
||||||
@@ -36,6 +36,13 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Блок 10.64 — IPv4 источника preview (2026-10-01)
|
||||||
|
|
||||||
|
- ros-src-address проверяет host текущего устройства и канонический IPv4/null. Preview привязан к id+host; поздний ответ/ошибка отменяются при смене устройства/источника. Запрос идёт только для enabled устройства и инструментов ping/traceroute/mtu; DNS приложения не запрашивает этот источник.
|
||||||
|
- Error/loading/null отличаются; ошибка имеет ручной повтор. В UI явно указано: preview справочный, backend повторно определяет src-address при запуске. Ошибка preview не меняет сетевые правила и не запрещает разрешённую диагностику; неизвестный FQDN адрес показан ?, а не прежним IP.
|
||||||
|
- 36 основных E2E (29 данных + 7 IPv4), 4 source E2E, unit decoder, профильный lint, TypeScript и production frontend прошли. Реальный DNS/RouterOS не проверен.
|
||||||
|
- Далее 10.65: DI backend probes, честные HTTP ошибки вместо legacy error output, сохранение allowlist/методов; затем строгие аргументы и MTU/неизвестные измерения. Доступность, адаптивная приёмка /probes и прежние lint эффекты ещё открыты. Расписание остаётся 11Б.
|
||||||
|
|
||||||
## Блок 10.63 — устройство диагностики из URL (2026-10-01)
|
## Блок 10.63 — устройство диагностики из URL (2026-10-01)
|
||||||
|
|
||||||
- /probes читает server через useSearchParams/Suspense. Выбор явно сохраняется pushState; остальные query/hash, back и reload сохранены. Нет автоматического выбора первого устройства в live/демо; ServerRail preserveSelection удерживает исходный контекст.
|
- /probes читает server через useSearchParams/Suspense. Выбор явно сохраняется pushState; остальные query/hash, back и reload сохранены. Нет автоматического выбора первого устройства в live/демо; ServerRail preserveSelection удерживает исходный контекст.
|
||||||
|
|||||||
@@ -389,3 +389,11 @@ Shared контракт и backend observed исходные значения/д
|
|||||||
- 29 основных + 4 source E2E, 2 URL unit, профильный lint, TypeScript и production frontend прошли. Старые тесты выбирают server=1 явно; при переходе в demo выбирают реальное demo устройство вместо прежнего fallback.
|
- 29 основных + 4 source E2E, 2 URL unit, профильный lint, TypeScript и production frontend прошли. Старые тесты выбирают server=1 явно; при переходе в demo выбирают реальное demo устройство вместо прежнего fallback.
|
||||||
- Далее 10.64: строгий ros-src-address контракт и привязка preview к id/host с отменой/повтором; затем backend probes DI/аргументы/ошибки/метрики, доступность/адаптивная приёмка. Этап 10 остаётся в работе. Моки не доказывают реальные RouterOS/DNS/БД.
|
- Далее 10.64: строгий ros-src-address контракт и привязка preview к id/host с отменой/повтором; затем backend probes DI/аргументы/ошибки/метрики, доступность/адаптивная приёмка. Этап 10 остаётся в работе. Моки не доказывают реальные RouterOS/DNS/БД.
|
||||||
|
|
||||||
|
|
||||||
|
## Блок 10.64 — IPv4 источника preview (2026-10-01)
|
||||||
|
|
||||||
|
- ros-src-address проверяет host текущего устройства и канонический IPv4/null. Preview привязан к id+host; поздний ответ/ошибка отменяются при смене устройства/источника. Запрос идёт только для enabled устройства и инструментов ping/traceroute/mtu; DNS приложения не запрашивает этот источник.
|
||||||
|
- Error/loading/null отличаются; ошибка имеет ручной повтор. В UI явно указано: preview справочный, backend повторно определяет src-address при запуске. Ошибка preview не меняет сетевые правила и не запрещает разрешённую диагностику; неизвестный FQDN адрес показан ?, а не прежним IP.
|
||||||
|
- 36 основных E2E (29 данных + 7 IPv4), 4 source E2E, unit decoder, профильный lint, TypeScript и production frontend прошли. Реальный DNS/RouterOS не проверен.
|
||||||
|
- Далее 10.65: DI backend probes, честные HTTP ошибки вместо legacy error output, сохранение allowlist/методов; затем строгие аргументы и MTU/неизвестные измерения. Доступность, адаптивная приёмка /probes и прежние lint эффекты ещё открыты. Расписание остаётся 11Б.
|
||||||
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import {test} from "node:test"
|
||||||
|
import assert from "node:assert/strict"
|
||||||
|
import {readProbeSource} from "./probe-source"
|
||||||
|
test("Preview принимает только IPv4 текущего host, null остаётся неизвестностью",()=>{assert.equal(readProbeSource({host:"router.test",ipv4:"192.0.2.1"},"router.test"),"192.0.2.1");assert.equal(readProbeSource({host:"router.test",ipv4:null},"router.test"),null);for(const raw of [{ipv4:"192.0.2.1"},{host:"other.test",ipv4:"192.0.2.1"},{host:"router.test",ipv4:"999.0.0.1"},{host:"router.test",ipv4:"192.000.2.1"},{host:"router.test",ipv4:"::1"},{}])assert.throws(()=>readProbeSource(raw,"router.test"))})
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import {z} from "zod"
|
||||||
|
const ipv4=z.string().refine(value=>/^\d{1,3}(\.\d{1,3}){3}$/.test(value)&&value.split(".").every(part=>Number(part)<=255&&String(Number(part))===part),"Ожидался IPv4")
|
||||||
|
const response=z.object({host:z.string().min(1),ipv4:ipv4.nullable()})
|
||||||
|
export function readProbeSource(raw:unknown,host:string){
|
||||||
|
const result=response.safeParse(raw)
|
||||||
|
if(!result.success||result.data.host!==host)throw new Error("Некорректный ответ API: источник preview не подтверждён.")
|
||||||
|
return result.data.ipv4
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ const headers={"access-control-allow-origin":"*","access-control-allow-methods":
|
|||||||
for(const change of ["backend","mode"])for(const fails of [false,true])test("Диагностика: поздний запрос/"+change+"/"+fails,async({page})=>{
|
for(const change of ["backend","mode"])for(const fails of [false,true])test("Диагностика: поздний запрос/"+change+"/"+fails,async({page})=>{
|
||||||
let started=false,completed=false,release!:()=>void;const gate=new Promise<void>(r=>release=r)
|
let started=false,completed=false,release!:()=>void;const gate=new Promise<void>(r=>release=r)
|
||||||
await page.route("**/health",r=>r.fulfill({headers,json:{ok:true}}))
|
await page.route("**/health",r=>r.fulfill({headers,json:{ok:true}}))
|
||||||
await page.route("**/api/**",r=>{const u=new URL(r.request().url()),p=u.pathname;return r.fulfill({headers,json:p==="/api/auth/config"?{required:false}:p==="/api/servers"?[{id:1,name:u.port==="8101"?"Новый источник":"Прежний источник",host:"192.0.2.1",type:"exit-node",country:"RU",enabled:true,status:"online"}]:p.endsWith("/ros-src-address")?{ipv4:"192.0.2.1"}:p.endsWith("/probes/run")?{output:"НОВЫЙ РЕЗУЛЬТАТ"}:{tunnels:[]}})})
|
await page.route("**/api/**",r=>{const u=new URL(r.request().url()),p=u.pathname;return r.fulfill({headers,json:p==="/api/auth/config"?{required:false}:p==="/api/servers"?[{id:1,name:u.port==="8101"?"Новый источник":"Прежний источник",host:"192.0.2.1",type:"exit-node",country:"RU",enabled:true,status:"online"}]:p.endsWith("/ros-src-address")?{host:"192.0.2.1",ipv4:"192.0.2.1"}:p.endsWith("/probes/run")?{output:"НОВЫЙ РЕЗУЛЬТАТ"}:{tunnels:[]}})})
|
||||||
await page.goto("/probes?server=1");await expect(page.getByRole("button",{name:"Запустить",exact:true})).toBeEnabled()
|
await page.goto("/probes?server=1");await expect(page.getByRole("button",{name:"Запустить",exact:true})).toBeEnabled()
|
||||||
await page.route("**/api/servers/1/probes/run",async r=>{if(new URL(r.request().url()).port!=="8000")return r.fallback();started=true;await gate;try{await r.fulfill(fails?{headers,status:503,json:{error:"СТАРАЯ ОШИБКА"}}:{headers,json:{output:"СТАРЫЙ РЕЗУЛЬТАТ"}})}catch{}finally{completed=true}})
|
await page.route("**/api/servers/1/probes/run",async r=>{if(new URL(r.request().url()).port!=="8000")return r.fallback();started=true;await gate;try{await r.fulfill(fails?{headers,status:503,json:{error:"СТАРАЯ ОШИБКА"}}:{headers,json:{output:"СТАРЫЙ РЕЗУЛЬТАТ"}})}catch{}finally{completed=true}})
|
||||||
await page.getByRole("button",{name:"Запустить",exact:true}).click();await expect.poll(()=>started).toBe(true);await page.getByRole("link",{name:/^Настройки/}).click()
|
await page.getByRole("button",{name:"Запустить",exact:true}).click();await expect.poll(()=>started).toBe(true);await page.getByRole("link",{name:/^Настройки/}).click()
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { test, expect, type Page } from "@playwright/test"
|
|||||||
const devices=[{id:1,name:"Первый",host:"192.0.2.1",site:"Тест",country:"RU",asn:"AS64500",type:"exit-node",enabled:true,status:"online",latency:null}]
|
const devices=[{id:1,name:"Первый",host:"192.0.2.1",site:"Тест",country:"RU",asn:"AS64500",type:"exit-node",enabled:true,status:"online",latency:null}]
|
||||||
async function routes(page:Page){
|
async function routes(page:Page){
|
||||||
await page.route("**/health",r=>r.fulfill({json:{ok:true}}))
|
await page.route("**/health",r=>r.fulfill({json:{ok:true}}))
|
||||||
await page.route("**/api/**",r=>{const p=new URL(r.request().url()).pathname;return r.fulfill({json:p==="/api/servers"?devices:p==="/api/auth/config"?{required:false}:p.endsWith("/ros-src-address")?{ipv4:"192.0.2.1"}:{tunnels:[]}})})
|
await page.route("**/api/**",r=>{const p=new URL(r.request().url()).pathname;return r.fulfill({json:p==="/api/servers"?devices:p==="/api/auth/config"?{required:false}:p.endsWith("/ros-src-address")?{host:"192.0.2.1",ipv4:"192.0.2.1"}:{tunnels:[]}})})
|
||||||
}
|
}
|
||||||
test.beforeEach(async({page})=>routes(page))
|
test.beforeEach(async({page})=>routes(page))
|
||||||
const run=(page:Page)=>page.getByRole("button",{name:"Запустить",exact:true})
|
const run=(page:Page)=>page.getByRole("button",{name:"Запустить",exact:true})
|
||||||
@@ -94,7 +94,7 @@ test("Диагностика: DNS приложения, inline ошибка и
|
|||||||
})
|
})
|
||||||
|
|
||||||
test("Диагностика: без выбора не запрашивает устройство",async({page})=>{
|
test("Диагностика: без выбора не запрашивает устройство",async({page})=>{
|
||||||
let requests=0;await page.route("**/api/servers/*/**",r=>{requests++;return r.fulfill({json:{ipv4:"192.0.2.1"}})})
|
let requests=0;await page.route("**/api/servers/*/**",r=>{requests++;return r.fulfill({json:{host:"192.0.2.1",ipv4:"192.0.2.1"}})})
|
||||||
await page.goto("/probes");await expect(page.getByRole("status").filter({hasText:"Выберите устройство для диагностики"})).toBeVisible();await expect(run(page)).toBeDisabled();expect(requests).toBe(0)
|
await page.goto("/probes");await expect(page.getByRole("status").filter({hasText:"Выберите устройство для диагностики"})).toBeVisible();await expect(run(page)).toBeDisabled();expect(requests).toBe(0)
|
||||||
})
|
})
|
||||||
for(const query of ["server=1&server=2","server=bad%20id","server="])test("Диагностика: неверная ссылка не читает API "+query,async({page})=>{
|
for(const query of ["server=1&server=2","server=bad%20id","server="])test("Диагностика: неверная ссылка не читает API "+query,async({page})=>{
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import {test,expect} from "@playwright/test"
|
||||||
|
const devices=[{id:1,name:"Первый",host:"first.test",enabled:true,status:null,type:"exit-node"},{id:2,name:"Второй",host:"second.test",enabled:true,status:"online",type:"exit-node"}]
|
||||||
|
test.beforeEach(async({page})=>{await page.route("**/health",r=>r.fulfill({json:{ok:true}}));await page.route("**/api/**",r=>r.fulfill({json:new URL(r.request().url()).pathname==="/api/servers"?devices:new URL(r.request().url()).pathname==="/api/auth/config"?{required:false}:{tunnels:[]}}))})
|
||||||
|
for(const kind of ["API","bad-ip","wrong-host"])test("Источник preview: "+kind+" и повтор",async({page})=>{
|
||||||
|
let fail=true;await page.route("**/api/servers/1/ros-src-address",r=>r.fulfill(fail?(kind==="API"?{status:503,json:{error:"IPv4 отказ"}}:{json:{host:kind==="wrong-host"?"other.test":"first.test",ipv4:kind==="bad-ip"?"999.0.0.1":"192.0.2.1"}}):{json:{host:"first.test",ipv4:"192.0.2.1"}}))
|
||||||
|
await page.goto("/probes?server=1");await expect(page.getByRole("alert",{name:"Источник preview"})).toBeVisible();await expect(page.getByRole("button",{name:"Запустить",exact:true})).toBeEnabled()
|
||||||
|
fail=false;await page.getByRole("button",{name:"Повторить определение IPv4"}).click();await expect(page.getByRole("status").filter({hasText:"IPv4 для preview: 192.0.2.1"})).toBeVisible();await expect(page.getByRole("alert",{name:"Источник preview"})).toHaveCount(0)
|
||||||
|
})
|
||||||
|
test("Источник preview: null не является ошибкой или IP",async({page})=>{
|
||||||
|
await page.route("**/api/servers/1/ros-src-address",r=>r.fulfill({json:{host:"first.test",ipv4:null}}));await page.goto("/probes?server=1");await expect(page.getByRole("status").filter({hasText:"IPv4 для preview не определён"})).toBeVisible();await expect(page.getByRole("alert",{name:"Источник preview"})).toHaveCount(0);await expect(page.getByText(/src-address=\?/)).toBeVisible()
|
||||||
|
})
|
||||||
|
for(const fails of [false,true])test("Источник preview: поздний адрес прежнего устройства "+fails,async({page})=>{
|
||||||
|
let started=false,completed=false,release!:()=>void;const gate=new Promise<void>(r=>release=r)
|
||||||
|
await page.route("**/api/servers/1/ros-src-address",async r=>{started=true;await gate;try{await r.fulfill(fails?{status:503,json:{error:"СТАРАЯ ОШИБКА"}}:{json:{host:"first.test",ipv4:"192.0.2.1"}})}catch{}finally{completed=true}})
|
||||||
|
await page.route("**/api/servers/2/ros-src-address",r=>r.fulfill({json:{host:"second.test",ipv4:"192.0.2.2"}}))
|
||||||
|
await page.goto("/probes?server=1");await expect.poll(()=>started).toBe(true);await page.getByRole("option",{name:/Второй/}).click();await expect(page.getByRole("status").filter({hasText:"IPv4 для preview: 192.0.2.2"})).toBeVisible();release();await expect.poll(()=>completed).toBe(true)
|
||||||
|
await expect(page.getByText(/src-address=192.0.2.2/)).toBeVisible();await expect(page.getByText(/192.0.2.1|СТАРАЯ ОШИБКА/)).toHaveCount(0)
|
||||||
|
})
|
||||||
|
test("Источник preview: DNS не запрашивает RouterOS источник",async({page})=>{
|
||||||
|
let reads=0;await page.route("**/api/servers/*/ros-src-address",r=>{reads++;return r.fulfill({json:{host:"first.test",ipv4:null}})})
|
||||||
|
await page.goto("/probes");await page.getByRole("button",{name:"DNS",exact:true}).click();await page.getByRole("option",{name:/Первый/}).click();await expect(page.getByRole("textbox",{name:"Домен"})).toBeVisible();expect(reads).toBe(0)
|
||||||
|
})
|
||||||
Reference in new issue
Block a user