From 6cb27b2aab504f2e4b02289891280c5b5c8dda2d Mon Sep 17 00:00:00 2001 From: Denozordec Date: Thu, 1 Oct 2026 02:00:21 +0700 Subject: [PATCH] =?UTF-8?q?chore(test):=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D0=B8=D1=82=D1=8C=20=D1=81=D0=BC=D0=B5=D0=BD=D1=83=20?= =?UTF-8?q?=D0=B8=D1=81=D1=82=D0=BE=D1=87=D0=BD=D0=B8=D0=BA=D0=B0=20=D0=BA?= =?UTF-8?q?=D0=B0=D1=80=D1=82=D1=8B=20=D1=81=D0=B5=D1=82=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/ux-ui-progress.md | 8 ++++- docs/ux-ui-stage-10.md | 7 +++++ playwright.config.ts | 2 +- playwright.network-map-context.config.ts | 8 +++++ tests/e2e/network-map-context.spec.ts | 37 ++++++++++++++++++++++++ 5 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 playwright.network-map-context.config.ts create mode 100644 tests/e2e/network-map-context.spec.ts diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index fbca9df6..8e645da5 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -26,11 +26,17 @@ | 7 | Серверы и мониторинг: детали, формы, URL-контекст, права | Завершён в границах локальной приёмки 2026-09-23; внешние ограничения ниже | | 8 | Трафик и статистика | Завершён 2026-09-28 в границах локальной приёмки; ограничения в блоке 8.13 | | 9 | VPN, Firewall и Filters | Завершён в границах локальной приёмки 2026-09-30; блоки 9.1–9.64, ограничения в 9.64 | -| 10 | Маршрутизация, диагностика, карта и терминал | В работе: 10.1–10.48; оптимизатор принят локально, карта сети в работе | +| 10 | Маршрутизация, диагностика, карта и терминал | В работе: 10.1–10.49; оптимизатор принят локально, карта сети в работе | | 11А | Пользователи, контейнеры, сертификаты и резервные копии | Не начат | | 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Не начат | | 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат | +## Блок 10.49 — поздние ответы при смене источника карты (2026-10-01) + +- Добавлена отдельная source-конфигурация Playwright (:3101, исходный backend :8000 и новый :8101). 12 сценариев snapshot/Flow/DNS × backend/mock × успех/отказ подтверждают, что прежняя карта, сервис и ошибка не попадают в новый источник, а старый ответ не запускает дополнительные чтения. +- Все 12 E2E и lint новых тестов/config прошли. Default suite исключает source-файл; Next серверы запускаются последовательно. +- Далее выбранные детали из текущего снимка, подписи модельных WAN связей и прежние lint ошибки карты; затем URL, клавиатура, телефон и визуальная приёмка. + ## Блок 10.48 — достоверность NetFlow и DNS карты (2026-10-01) - NetFlow принимает полный DTO backend за 5 минут: поля/flags/счётчики не заменяются default нулями. При отказе сохраняется предыдущий снимок, первоначальная неизвестность и время ответа браузера обозначены отдельно; статус приёмника не выдаётся за свежесть измерения. Обновить повторяет оба независимых чтения. diff --git a/docs/ux-ui-stage-10.md b/docs/ux-ui-stage-10.md index f03927df..0aa263a0 100644 --- a/docs/ux-ui-stage-10.md +++ b/docs/ux-ui-stage-10.md @@ -260,3 +260,10 @@ Shared контракт и backend observed исходные значения/д - 6 unit, 20 разных E2E (19 общих + новый slow, 8 Flow повторены после финального изменения) и TypeScript прошли. Новых lint проблем нет. Первые Flow фикстуры не перехватывали health и получили ECONNREFUSED; фикстуры исправлены, повтор чистый. - Далее отдельные source/race E2E, достоверность модельных WAN линий/выбранных объектов, затем URL и доступность/адаптивность карты. + +## Блок 10.49 — поздние ответы при смене источника карты (2026-10-01) + +- Добавлена отдельная source-конфигурация Playwright (:3101, исходный backend :8000 и новый :8101). 12 сценариев snapshot/Flow/DNS × backend/mock × успех/отказ подтверждают, что прежняя карта, сервис и ошибка не попадают в новый источник, а старый ответ не запускает дополнительные чтения. +- Все 12 E2E и lint новых тестов/config прошли. Default suite исключает source-файл; Next серверы запускаются последовательно. +- Далее выбранные детали из текущего снимка, подписи модельных WAN связей и прежние lint ошибки карты; затем URL, клавиатура, телефон и визуальная приёмка. + diff --git a/playwright.config.ts b/playwright.config.ts index db299785..1a29a882 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from "@playwright/test" export default defineConfig({ testDir: "./tests/e2e", - testIgnore: ["**/route-optimizer-context.spec.ts", "**/recursive-routes-context.spec.ts", "**/ospf-context.spec.ts", "**/bgp-context.spec.ts", "**/firewall-context.spec.ts", "**/filters-context.spec.ts", "**/ipsec-context.spec.ts", "**/gre-context.spec.ts", "**/vxlan-context.spec.ts"], + testIgnore: ["**/network-map-context.spec.ts", "**/route-optimizer-context.spec.ts", "**/recursive-routes-context.spec.ts", "**/ospf-context.spec.ts", "**/bgp-context.spec.ts", "**/firewall-context.spec.ts", "**/filters-context.spec.ts", "**/ipsec-context.spec.ts", "**/gre-context.spec.ts", "**/vxlan-context.spec.ts"], fullyParallel: false, workers: 1, retries: 0, diff --git a/playwright.network-map-context.config.ts b/playwright.network-map-context.config.ts new file mode 100644 index 00000000..3af82acb --- /dev/null +++ b/playwright.network-map-context.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "@playwright/test" +import base from "./playwright.config" +const server=base.webServer +if(!server||Array.isArray(server))throw new Error("Ожидается один локальный сервер") +export default defineConfig({ + ...base, use:{...base.use,baseURL:"http://127.0.0.1:3101"},testIgnore:[],testMatch:"network-map-context.spec.ts", + webServer:{...server,command:"node node_modules/next/dist/bin/next dev --hostname 127.0.0.1 --port 3101",url:"http://127.0.0.1:3101",env:{...server.env,NEXT_PUBLIC_BACKEND_URL:"local"}}, +}) diff --git a/tests/e2e/network-map-context.spec.ts b/tests/e2e/network-map-context.spec.ts new file mode 100644 index 00000000..a47b1788 --- /dev/null +++ b/tests/e2e/network-map-context.spec.ts @@ -0,0 +1,37 @@ +import {test,expect} from "@playwright/test" +const empty={"hops":[],"live":false,"rangeMinutes":5,"windowSec":300,"totalBytes":0,"namedBytes":0,"unclassifiedBytes":0,"asnLoaded":false,"countryLoaded":false,"services":[],"serviceEdges":[],"servicePaths":[],"countries":[],"countryEdges":[],"countryPaths":[],"countryServiceGroups":[],"mapServiceMinSharePct":5,"dedupApplied":false,"excludeMeshApplied":false,"excludeOverlayApplied":false} +const headers={"access-control-allow-origin":"*","access-control-allow-methods":"GET,POST,OPTIONS","access-control-allow-headers":"Content-Type,Authorization"} +const wan={id:"w",name:"WAN",iface:"ether1",isp:"ISP",ip:"router.example",maxDl:100,maxUl:100} +const row=(name:string)=>({id:1,name,host:"192.0.2.1",type:"home-router",country:"RU",enabled:true,status:"online",wanUplinks:[wan]}) +for(const change of ["backend","mode"])for(const part of ["snapshot","flow","dns"])for(const fails of [false,true])test("Карта: поздний "+part+"/"+change+"/"+fails,async({page})=>{ + let newReads=0,started=false,completed=false,release!:()=>void;const gate=new Promise(resolve=>{release=resolve}) + 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;if(u.port==="8101")newReads++ + return r.fulfill({headers,json:p==="/api/auth/config"?{required:false}:p==="/api/servers"?[row(u.port==="8101"?"Новая карта":"Прежняя карта")]:p==="/api/filters/gre-tunnels"?{tunnels:[],failures:[]}:p==="/api/uptime/speed-probes"?{probes:[]}:p==="/api/traffic/flow/map-hops"?empty:p==="/api/network/resolve-hosts"?{results:{"router.example":"192.0.2.2"}}:{}}) + }) + await page.goto("/settings");await page.getByRole("link",{name:/^Карта сети/}).click() + await expect(page.getByText("Прежняя карта",{exact:true}).first()).toBeVisible() + await expect(page.getByText(/NetFlow · 5 мин · ответ/)).toBeVisible() + const path=part==="snapshot"?"/api/servers":part==="flow"?"/api/traffic/flow/map-hops":"/api/network/resolve-hosts" + await page.route("**/api/**",async r=>{ + const u=new URL(r.request().url());if(u.port!=="8000"||u.pathname!==path||started)return r.fallback() + started=true;await gate + try{await r.fulfill(fails?{headers,status:503,json:{error:"Старая ошибка карты"}}:{headers,json:part==="snapshot"?[row("Поздняя карта")]:part==="flow"?{...empty,services:[{id:"svc:late",label:"Поздний сервис",category:"Web",bytes:1,bps:1,share:1}]}:{results:{"router.example":null}}})}catch{}finally{completed=true} + }) + await page.getByRole("button",{name:"Обновить",exact:true}).click() + await expect.poll(()=>started).toBe(true);await page.goBack();await expect(page).toHaveURL(/settings/) + if(change==="backend"){const input=page.getByPlaceholder("http://localhost:8000");await input.fill("http://localhost:8101");await input.press("Enter")} + else await page.getByRole("button",{name:"Моковые",exact:true}).click() + await page.getByRole("link",{name:/^Карта сети/}).click() + if(change==="backend"){await expect(page.getByText("Новая карта",{exact:true}).first()).toBeVisible();await expect(page.getByText(/NetFlow · 5 мин · ответ/)).toBeVisible()} + else await expect(page.getByText("Демо · mock",{exact:true})).toBeVisible() + const before=newReads;release();await expect.poll(()=>completed).toBe(true) + await page.evaluate(()=>new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(()=>resolve())))) + await expect(page.getByText("Старая ошибка карты",{exact:false})).toHaveCount(0) + await expect(page.getByText("Поздняя карта",{exact:false})).toHaveCount(0) + await expect(page.getByText("Поздний сервис",{exact:false})).toHaveCount(0) + await expect(page.getByRole("alert").filter({hasText:"DNS не разрешён"})).toHaveCount(0) + if(change==="backend")await expect(page.getByText("Новая карта",{exact:true}).first()).toBeVisible() + expect(newReads).toBe(before) +})