fix(settings): проверить контекст и завершить локальную приёмку базы
This commit is contained in:
1 parent
57293479d1
commit
596fea4814
8 files changed
+46
-3
No files matched your search
+3
-1
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
## Актуальная точка продолжения — 2026-10-03
|
## Актуальная точка продолжения — 2026-10-03
|
||||||
|
|
||||||
Этап 11Б в работе; последний проверенный блок 11Б.3.6.2.3.1. Следующий блок 11Б.3.6.2.3.2: browser source/ABA/unmount и соседняя Settings регрессия; затем локальная приёмка System DB. Затем Alerts/Releases/probe scheduling, приёмка 11Б и этап 12.
|
Этап 11Б в работе; последний проверенный блок 11Б.3.6.2.3.2; System DB принят локально с внешними ограничениями. Следующий блок 11Б.4.1: строгие contracts/API оповещений; затем backend и UI приёмка Alerts. Затем Alerts/Releases/probe scheduling, приёмка 11Б и этап 12.
|
||||||
|
|
||||||
Реальные pg_dump/pg_restore, PG rollback и остановка UDP worker требуют внешней приёмки. Датированные продолжения ниже — история; точный следующий шаг также указан в начале [журнала](ux-ui-progress.md).
|
Реальные pg_dump/pg_restore, PG rollback и остановка UDP worker требуют внешней приёмки. Датированные продолжения ниже — история; точный следующий шаг также указан в начале [журнала](ux-ui-progress.md).
|
||||||
|
|
||||||
@@ -302,3 +302,5 @@ REST запрос → ожидание → подтверждённый output /
|
|||||||
2026-10-03: завершён 11Б.3.6.2.2.2, подтверждённый backup/restore отделён от restart/audit warnings; nonqueued concurrency, HTTP/JWT/API/UI проверки прошли локально. Далее DB форма 11Б.3.6.2.3.
|
2026-10-03: завершён 11Б.3.6.2.2.2, подтверждённый backup/restore отделён от restart/audit warnings; nonqueued concurrency, HTTP/JWT/API/UI проверки прошли локально. Далее DB форма 11Б.3.6.2.3.
|
||||||
|
|
||||||
2026-10-03: завершён 11Б.3.6.2.3.1, отдельная DB форма/lock/checkpoint/адаптивность; 10 E2E, API/proxy tests и build прошли. Далее source/ABA/unmount и приёмка DB.
|
2026-10-03: завершён 11Б.3.6.2.3.1, отдельная DB форма/lock/checkpoint/адаптивность; 10 E2E, API/proxy tests и build прошли. Далее source/ABA/unmount и приёмка DB.
|
||||||
|
|
||||||
|
2026-10-03: 11Б.3.6.2.3.2 — 6 source и 18 соседних E2E прошли; база принята в локальных границах. Далее 11Б.4.1 Alerts contracts/API.
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
| 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 | Маршрутизация, диагностика, карта и терминал | Завершён в границах локальной приёмки 2026-10-01; блоки 10.1–10.71, внешние ограничения в 10.71 |
|
| 10 | Маршрутизация, диагностика, карта и терминал | Завершён в границах локальной приёмки 2026-10-01; блоки 10.1–10.71, внешние ограничения в 10.71 |
|
||||||
| 11А | Пользователи, контейнеры, сертификаты и резервные копии | Завершён в границах локальной приёмки 2026-10-01; 11А.1–11А.33, внешние ограничения в 11А.33 |
|
| 11А | Пользователи, контейнеры, сертификаты и резервные копии | Завершён в границах локальной приёмки 2026-10-01; 11А.1–11А.33, внешние ограничения в 11А.33 |
|
||||||
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | В работе: 11Б.1–11Б.3.6.2.3.1; scheduler и GeoIP приняты локально; NetFlow принят локально; Evolution принят локально; далее общие Settings |
|
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | В работе: 11Б.1–11Б.3.6.2.3.2; scheduler и GeoIP приняты локально; NetFlow принят локально; Evolution принят локально; далее общие Settings |
|
||||||
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат |
|
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат |
|
||||||
|
|
||||||
|
|
||||||
@@ -36,6 +36,13 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Блок 11Б.3.6.2.3.2 — source и локальная приёмка базы (2026-10-03)
|
||||||
|
|
||||||
|
- 6 browser source сценариев прошли: поздний success/error backup при ABA, переход в demo, принадлежность unknown checkpoint backend при ABA, reload во время POST с поздним success/error без публикации/повтора. Смена источника не доказывает server cancellation; checkpoint старого источника остаётся.
|
||||||
|
- 18 соседних General Settings + DB form/receipt E2E прошли; актуальные 1440 screenshots сохранены, 320 General просмотрен. В regression dev console один aborted/неперехваченный старый Evolution reader дошёл до отсутствующего backend (ECONNREFUSED); тесты UI прошли, реальная интеграция этим не проверена. Специализированные DB ответы подменялись по HTTP.
|
||||||
|
- System DB принят локально в границах 11Б.3.6.2.1–11Б.3.6.2.3.2: API/restore executor/lifecycle/HTTP/JWT/lock/proxy/form/source/adaptive. Реальный pg_dump/pg_restore, PostgreSQL rollback/advisory overlap, остановка UDP worker и Portal остаются внешней приёмкой. SessionStorage может быть недоступен: guard тогда живёт в памяти, не гарантирует reload persistence. Префикс PGDMP не доказывает целостность архива.
|
||||||
|
- Следующий блок 11Б.4.1: аудит оповещений и strict Alerts read/write/test contracts/API. Далее backend receipts/конкуренция, UI drafts/source/known/error/rights и адаптивная приёмка; Releases/probe scheduling и 11Б/12 остаются.
|
||||||
|
|
||||||
## Блок 11Б.3.6.2.3.1 — отдельная форма базы и неизвестный результат (2026-10-03)
|
## Блок 11Б.3.6.2.3.1 — отдельная форма базы и неизвестный результат (2026-10-03)
|
||||||
|
|
||||||
- Inline DB handlers вынесены в SystemDatabaseSection: source-keyed lifetime, AbortController/active guard, проверка settings admin и backend health, общий synchronous lock backup/restore, удержание confirmation и запрет cancel во время POST. Результат/ошибки/warnings показаны внутри формы; убраны дублирующие toast сообщения.
|
- Inline DB handlers вынесены в SystemDatabaseSection: source-keyed lifetime, AbortController/active guard, проверка settings admin и backend health, общий synchronous lock backup/restore, удержание confirmation и запрет cancel во время POST. Результат/ошибки/warnings показаны внутри формы; убраны дублирующие toast сообщения.
|
||||||
|
|||||||
@@ -232,3 +232,5 @@ ReUI PRO/base-nova; существующие Frame, Data Grid v9, KpiStatGrid, S
|
|||||||
| Блок | Референс | Схема |
|
| Блок | Референс | Схема |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| 11Б.3.6.2.3.1 База приложения | [Settings 16](https://reui.io/preview/base/settings-16?ref=mcp) | Источник и доступ → скачать / выбрать файл → подтверждение → результат + warnings; неизвестный результат → проверка данных → разрешение нового запроса |
|
| 11Б.3.6.2.3.1 База приложения | [Settings 16](https://reui.io/preview/base/settings-16?ref=mcp) | Источник и доступ → скачать / выбрать файл → подтверждение → результат + warnings; неизвестный результат → проверка данных → разрешение нового запроса |
|
||||||
|
|
||||||
|
Завершён 11Б.3.6.2.3.2: 6 source/ABA/reload и 18 General/DB E2E прошли, DB принят локально. Реальный PG/UDP worker/Portal не принят. Далее 11Б.4.1 — strict Alerts contracts/API, затем backend/UI.
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 176 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 231 KiB After Width: | Height: | Size: 217 KiB |
@@ -2,7 +2,7 @@ import { defineConfig } from "@playwright/test"
|
|||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
testDir: "./tests/e2e",
|
testDir: "./tests/e2e",
|
||||||
testIgnore: ["**/settings-context.spec.ts", "**/evobgp-context.spec.ts", "**/netflow-context.spec.ts", "**/geoip-context.spec.ts", "**/data-collection-context.spec.ts", "**/backups-context.spec.ts", "**/certificates-context.spec.ts", "**/containers-context.spec.ts", "**/users-context.spec.ts", "**/probes-context.spec.ts", "**/terminal-context.spec.ts", "**/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"],
|
testIgnore: ["**/system-database-context.spec.ts", "**/settings-context.spec.ts", "**/evobgp-context.spec.ts", "**/netflow-context.spec.ts", "**/geoip-context.spec.ts", "**/data-collection-context.spec.ts", "**/backups-context.spec.ts", "**/certificates-context.spec.ts", "**/containers-context.spec.ts", "**/users-context.spec.ts", "**/probes-context.spec.ts", "**/terminal-context.spec.ts", "**/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,
|
fullyParallel: false,
|
||||||
workers: 1,
|
workers: 1,
|
||||||
retries: 0,
|
retries: 0,
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
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: "system-database-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" } } })
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import {test,expect,type Page} from "@playwright/test"
|
||||||
|
const headers={"access-control-allow-origin":"*","access-control-allow-methods":"GET,POST,OPTIONS","access-control-allow-headers":"Content-Type,Authorization","access-control-expose-headers":"Content-Disposition,X-System-Database-Outcome"}
|
||||||
|
async function setup(page:Page){await page.route("**/health",r=>r.fulfill({headers,json:{status:"ok"}}));await page.route("**/api/**",r=>{const path=new URL(r.request().url()).pathname;return r.fulfill(path==="/api/auth/config"?{headers,json:{required:false}}:{headers,status:503,json:{error:"outside source fixture"}})});await page.goto("/settings");await expect(page.getByRole("button",{name:"Выбрать файл",exact:true})).toBeEnabled()}
|
||||||
|
async function source(page:Page,value:string){const input=page.getByRole("textbox",{name:"URL бекенда",exact:true});await input.fill(value);await input.press("Enter");await expect(input).toHaveValue(value);await expect(page.getByText("Доступен",{exact:true})).toBeVisible()}
|
||||||
|
for(const failed of [false,true])test("System DB source: ABA discards old backup outcome/"+failed,async({page})=>{
|
||||||
|
await setup(page);let started=false,completed=false,release!:()=>void,downloads=0;const gate=new Promise<void>(resolve=>release=resolve);page.on("download",()=>downloads++)
|
||||||
|
await page.route("http://localhost:8000/api/system/database/backup",async r=>{started=true;await gate;try{await r.fulfill(failed?{headers,status:503,json:{error:"OLD BACKUP FAILED"}}:{headers,body:Buffer.from("PGDMPfixture"),contentType:"application/octet-stream"})}catch{}finally{completed=true}})
|
||||||
|
try{await page.getByRole("region",{name:"Операции базы приложения"}).getByRole("button",{name:"Скачать",exact:true}).click();await expect.poll(()=>started).toBe(true);await source(page,"http://localhost:8101");await source(page,"http://localhost:8000");await expect(page.getByRole("button",{name:"Выбрать файл",exact:true})).toBeEnabled();release();await expect.poll(()=>completed).toBe(true);await expect(page.getByText("Бэкап базы приложения подготовлен к скачиванию",{exact:true})).toHaveCount(0);await expect(page.getByText("OLD BACKUP FAILED",{exact:true})).toHaveCount(0);expect(downloads).toBe(0)}finally{release()}
|
||||||
|
})
|
||||||
|
test("System DB source: unknown restore checkpoint belongs to its backend and survives ABA",async({page})=>{
|
||||||
|
await setup(page);let calls=0;await page.route("**/api/system/database/restore",r=>{calls++;return r.fulfill({headers,json:{ok:false}})})
|
||||||
|
await page.getByRole("button",{name:"Выбрать файл",exact:true}).click();await page.getByRole("dialog").locator('input[type="file"]').setInputFiles({name:"fixture.dump",mimeType:"application/octet-stream",buffer:Buffer.from("PGDMPfixture")});await page.getByRole("button",{name:"Продолжить",exact:true}).click();await page.getByRole("button",{name:"Восстановить",exact:true}).click();await expect(page.getByRole("button",{name:"Восстановить",exact:true})).toBeDisabled();await expect(page.getByRole("alert")).toContainText("не подтверждено");await page.getByRole("button",{name:"Отмена",exact:true}).click()
|
||||||
|
await source(page,"http://localhost:8101");await expect(page.getByRole("button",{name:"Выбрать файл",exact:true})).toBeEnabled();await source(page,"http://localhost:8000");await expect(page.getByRole("button",{name:"Выбрать файл",exact:true})).toBeDisabled();await expect(page.getByText(/Запрос мог выполниться на сервере/)).toBeVisible();expect(calls).toBe(1)
|
||||||
|
})
|
||||||
|
test("System DB source: switching to demo aborts pending backup publication",async({page})=>{
|
||||||
|
await setup(page);let started=false,completed=false,release!:()=>void;const gate=new Promise<void>(resolve=>release=resolve)
|
||||||
|
await page.route("**/api/system/database/backup",async r=>{started=true;await gate;try{await r.fulfill({headers,body:Buffer.from("PGDMPfixture"),contentType:"application/octet-stream"})}catch{}finally{completed=true}})
|
||||||
|
try{await page.getByRole("region",{name:"Операции базы приложения"}).getByRole("button",{name:"Скачать",exact:true}).click();await expect.poll(()=>started).toBe(true);await page.getByRole("button",{name:"Моковые",exact:true}).click();await expect(page.getByRole("button",{name:"Выбрать файл",exact:true})).toBeDisabled();release();await expect.poll(()=>completed).toBe(true);await expect(page.getByText("Бэкап базы приложения подготовлен к скачиванию",{exact:true})).toHaveCount(0)}finally{release()}
|
||||||
|
})
|
||||||
|
|
||||||
|
for(const failed of [false,true])test("System DB source: reload during restore retains checkpoint and ignores late receipt/"+failed,async({page})=>{
|
||||||
|
await setup(page);let started=false,completed=false,calls=0,release!:()=>void;const gate=new Promise<void>(resolve=>release=resolve)
|
||||||
|
await page.route("**/api/system/database/restore",async r=>{calls++;started=true;await gate;try{await r.fulfill(failed?{headers,status:503,json:{error:"OLD RESTORE FAILED"}}:{headers,json:{ok:true}})}catch{}finally{completed=true}})
|
||||||
|
try{
|
||||||
|
await page.getByRole("button",{name:"Выбрать файл",exact:true}).click();await page.getByRole("dialog").locator('input[type="file"]').setInputFiles({name:"fixture.dump",mimeType:"application/octet-stream",buffer:Buffer.from("PGDMPfixture")});await page.getByRole("button",{name:"Продолжить",exact:true}).click();await page.getByRole("button",{name:"Восстановить",exact:true}).click();await expect.poll(()=>started).toBe(true);await page.reload();await expect(page.getByRole("button",{name:"Выбрать файл",exact:true})).toBeDisabled();release();await expect.poll(()=>completed).toBe(true);await expect(page.getByText("База приложения восстановлена",{exact:true})).toHaveCount(0);await expect(page.getByText("OLD RESTORE FAILED",{exact:true})).toHaveCount(0);await expect(page.getByText(/Запрос мог выполниться на сервере/)).toBeVisible();expect(calls).toBe(1)
|
||||||
|
}finally{release()}
|
||||||
|
})
|
||||||
Reference in new issue
Block a user