Files
MikrotikManager/tests/e2e/alerts-context.spec.ts

58 lines
13 KiB
TypeScript

import {emptyOutbox} from "./fixtures/alerts"
import {test,expect,type Page} from "@playwright/test"
import {emptyAlerts,alertsWithRule,heldOutbox} from "./fixtures/alerts"
const headers={"access-control-allow-origin":"*","access-control-allow-methods":"GET,PUT,POST,OPTIONS","access-control-allow-headers":"Content-Type,Authorization,If-Match"}
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("Alerts source: ABA discards old primary and target outcomes/"+failed,async({page})=>{
const startedPaths=new Set<string>();let old=true,started=0,completed=0,release!:()=>void;const gate=new Promise<void>(resolve=>release=resolve)
await page.route("**/health",r=>r.fulfill({headers,json:{status:"ok"}}))
await page.route("**/api/**",async r=>{const url=new URL(r.request().url()),path=url.pathname;if(path==="/api/alerts/outbox")return r.fulfill({headers,json:emptyOutbox});if(path==="/api/auth/config")return r.fulfill({headers,json:{required:false}})
if(old&&url.port==="8000"&&["/api/alerts","/api/bgp/sessions","/api/filters/gre-tunnels"].includes(path)){started++;startedPaths.add(path);await gate;try{await r.fulfill(failed?{headers,status:503,json:{error:"OLD ALERTS FAILED"}}:{headers,json:path==="/api/alerts"?{...emptyAlerts,telegram:{...emptyAlerts.telegram,chatId:"OLD CHAT"}}:path==="/api/bgp/sessions"?[{serverName:"old",name:"peer"}]:{tunnels:[{name:"old",serverId:"1"}]}})}catch{}finally{completed++};return}
return r.fulfill({headers,json:path==="/api/alerts"?emptyAlerts:path==="/api/filters/gre-tunnels"?{tunnels:[],failures:[]}:[]})})
try{await page.goto("/alerts");await expect.poll(()=>startedPaths.size).toBe(3);await page.goto("/settings");await source(page,"http://localhost:8101");old=false;await source(page,"http://localhost:8000");await page.goto("/alerts");await expect(page.getByText("Всего: 0",{exact:true})).toBeVisible();release();await expect.poll(()=>completed).toBe(started);await expect(page.getByText("OLD ALERTS FAILED",{exact:true})).toHaveCount(0);await expect(page.locator('input[value="OLD CHAT"]')).toHaveCount(0);for(const label of ["BGP","GRE"])await expect(page.getByRole("region",{name:"Каталог "+label,exact:true})).toContainText("Получено объектов: 0")
}finally{release()}
})
const configuredTelegram={chatId:"-100fixture",tokenConfigured:true,connected:true,messageThreadId:9}
for(const kind of ["save","test"] as const)for(const failed of [false,true])test("Telegram source: ABA retains unknown checkpoint and ignores old receipt/"+kind+"/"+failed,async({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;if(path==="/api/alerts/outbox")return r.fulfill({headers,json:emptyOutbox});return r.fulfill({headers,json:path==="/api/auth/config"?{required:false}:path==="/api/alerts"?{...emptyAlerts,telegram:configuredTelegram}:path==="/api/filters/gre-tunnels"?{tunnels:[],failures:[]}:[]})})
let started=false,completed=false,calls=0,release!:()=>void;const gate=new Promise<void>(resolve=>release=resolve)
await page.route("http://localhost:8000/api/alerts/telegram"+(kind==="test"?"/test":""),async r=>{calls++;started=true;await gate;try{await r.fulfill(failed?{headers,status:503,json:{error:"OLD TELEGRAM FAILED"}}:{headers,json:kind==="test"?{ok:true}:{ok:true,telegram:{...configuredTelegram,chatId:"-100draft"}}})}catch{}finally{completed=true}})
const form=()=>page.getByRole("region",{name:"Параметры Telegram",exact:true})
try{await page.goto("/alerts");await expect(page.getByRole("textbox",{name:"Chat ID по умолчанию",exact:true})).toHaveValue(configuredTelegram.chatId);if(kind==="save")await page.getByRole("textbox",{name:"Chat ID по умолчанию",exact:true}).fill("-100draft");await form().getByRole("button",{name:kind==="save"?"Сохранить в БД":"Отправить тестовое сообщение",exact:true}).click();await expect.poll(()=>started).toBe(true)
await page.goto("/settings");await source(page,"http://localhost:8101");await page.goto("/alerts");await expect(form().getByRole("button",{name:"Отправить тестовое сообщение",exact:true})).toBeEnabled();await page.goto("/settings");await source(page,"http://localhost:8000");await page.goto("/alerts");await expect(form()).toContainText("Результат запроса неизвестен");release();await expect.poll(()=>completed).toBe(true);await expect(form().getByRole("button",{name:"Отправить тестовое сообщение",exact:true})).toBeDisabled();await expect(form()).not.toContainText("Параметры Telegram сохранены в БД");await expect(form()).not.toContainText("API подтвердил приём");await expect(form()).not.toContainText("OLD TELEGRAM FAILED");await expect(page.getByRole("textbox",{name:"Chat ID по умолчанию",exact:true})).toHaveValue(configuredTelegram.chatId);expect(calls).toBe(1)
}finally{release()}
})
for(const failed of [false,true])test("Rules source: ABA ignores old save and retains scoped unknown checkpoint/"+failed,async({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;if(path==="/api/alerts/outbox")return r.fulfill({headers,json:emptyOutbox});return r.fulfill({headers,json:path==="/api/auth/config"?{required:false}:path==="/api/alerts"?alertsWithRule:path==="/api/filters/gre-tunnels"?{tunnels:[],failures:[]}:[]})})
let started=false,completed=false,calls=0,release!:()=>void;const gate=new Promise<void>(resolve=>release=resolve)
await page.route("http://localhost:8000/api/alerts/rules",async r=>{calls++;started=true;const body=r.request().postDataJSON();await gate;try{await r.fulfill(failed?{headers,status:503,json:{error:"OLD RULES FAILED"}}:{headers,json:{ok:true,revision:"2".repeat(64),groups:body.groups,rules:body.rules.map((row:object)=>({...row,lastFiredAt:null}))}})}catch{}finally{completed=true}})
const toggle=()=>page.getByRole("switch",{name:"Включить правило Fixture rule",exact:true})
try{await page.goto("/alerts");await expect(toggle()).toBeEnabled();await toggle().click();await expect.poll(()=>started).toBe(true);await page.goto("/settings");await source(page,"http://localhost:8101");await page.goto("/alerts");await expect(toggle()).toBeEnabled();await page.goto("/settings");await source(page,"http://localhost:8000");await page.goto("/alerts");await expect(page.getByText(/Результат сохранения правил неизвестен/)).toBeVisible();release();await expect.poll(()=>completed).toBe(true);await expect(toggle()).toBeChecked();await expect(toggle()).toBeDisabled();await expect(page.getByText("Правила и группы сохранены в БД",{exact:true})).toHaveCount(0);await expect(page.getByText("OLD RULES FAILED",{exact:true})).toHaveCount(0);expect(calls).toBe(1)}finally{release()}
})
for(const failed of [false,true])test("Preview source: late ABA receipt cannot clear Telegram checkpoint/"+failed,async({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;if(path==="/api/alerts/outbox")return r.fulfill({headers,json:emptyOutbox});return r.fulfill({headers,json:path==="/api/auth/config"?{required:false}:path==="/api/alerts"?{...alertsWithRule,telegram:configuredTelegram}:path==="/api/filters/gre-tunnels"?{tunnels:[],failures:[]}:[]})})
let started=false,completed=false,calls=0,release!:()=>void;const gate=new Promise<void>(resolve=>release=resolve)
await page.route("http://localhost:8000/api/alerts/telegram/test",async r=>{calls++;started=true;await gate;try{await r.fulfill(failed?{headers,status:503,json:{error:"OLD PREVIEW FAILED"}}:{headers,json:{ok:true}})}catch{}finally{completed=true}})
try{await page.goto("/alerts");await page.getByRole("button",{name:"Изменить правило Fixture rule",exact:true}).click();await page.getByRole("dialog").getByRole("button",{name:"Тест в Telegram",exact:true}).click();await expect.poll(()=>started).toBe(true);await page.goto("/settings");await source(page,"http://localhost:8101");await source(page,"http://localhost:8000");await page.goto("/alerts");await page.getByRole("button",{name:"Изменить правило Fixture rule",exact:true}).click();await expect(page.getByRole("dialog")).toContainText("Результат Telegram неизвестен");release();await expect.poll(()=>completed).toBe(true);await expect(page.getByRole("dialog").getByRole("button",{name:"Тест в Telegram",exact:true})).toBeDisabled();await expect(page.getByRole("dialog")).not.toContainText("API подтвердил приём");await expect(page.getByRole("dialog")).not.toContainText("OLD PREVIEW FAILED");expect(calls).toBe(1)}finally{release()}
})
for(const failed of [false,true])test("Queue source: ABA ignores old held list or error/"+failed,async({page})=>{
let old=true,started=0,completed=0,release!:()=>void;const gate=new Promise<void>(resolve=>release=resolve)
await page.route("**/health",r=>r.fulfill({headers,json:{status:"ok"}}));await page.route("**/api/**",async r=>{
const url=new URL(r.request().url()),path=url.pathname
if(path==="/api/alerts/outbox"&&old&&url.port==="8000"){started++;await gate;try{await r.fulfill(failed?{headers,status:503,json:{error:"OLD QUEUE FAILED"}}:{headers,json:{...emptyOutbox,counts:{...emptyOutbox.counts,total:1,unknown:1},items:[{id:"old",resolutionVersion:0,status:"unknown",ruleName:"OLD HELD",createdAt:"2026-10-03T00:00:00Z",attemptedAt:"2026-10-03T00:00:00Z"}]}})}catch{}finally{completed++};return}
return r.fulfill({headers,json:path==="/api/auth/config"?{required:false}:path==="/api/alerts"?emptyAlerts:path==="/api/alerts/outbox"?emptyOutbox:path==="/api/filters/gre-tunnels"?{tunnels:[],failures:[]}:[]})})
try{await page.goto("/alerts");await expect.poll(()=>started).toBeGreaterThan(0);await expect(page.getByText("Всего: 0",{exact:true})).toBeVisible();await page.goto("/settings");await source(page,"http://localhost:8101");old=false;await source(page,"http://localhost:8000");await page.goto("/alerts");const panel=page.getByRole("region",{name:"Очередь Telegram",exact:true});await expect(panel).toContainText("Ожидают: 0");release();await expect.poll(()=>completed).toBe(started);await expect(panel).not.toContainText("OLD QUEUE");await expect(panel).not.toContainText("OLD HELD");await expect(panel).toContainText("Ожидают: 0")}finally{release()}
})
for(const failed of [false,true])test("Queue retry source: ABA retains unknown checkpoint and ignores late receipt/"+failed,async({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({headers,json:path==="/api/auth/config"?{required:false}:path==="/api/alerts"?emptyAlerts:path==="/api/alerts/outbox"?heldOutbox:path==="/api/filters/gre-tunnels"?{tunnels:[],failures:[]}:[]})})
let started=false,completed=false,calls=0,release!:()=>void;const gate=new Promise<void>(resolve=>release=resolve)
await page.route("http://localhost:8000/api/alerts/outbox/held-fixture/retry",async r=>{calls++;started=true;await gate;try{await r.fulfill(failed?{headers,status:503,json:{error:"OLD RETRY FAILED"}}:{headers,json:{ok:true,id:"held-fixture",status:"pending",resolutionVersion:4}})}catch{}finally{completed=true}})
try{await page.goto("/alerts");await page.getByRole("button",{name:"Повторить сообщение Held retry fixture"}).click();const dialog=page.getByRole("alertdialog");await dialog.getByRole("checkbox",{name:"Проверил чат и принимаю риск дубликата"}).check();await dialog.getByRole("button",{name:"Поставить в очередь повторно"}).click();await expect.poll(()=>started).toBe(true);await page.goto("/settings");await source(page,"http://localhost:8101");await page.goto("/alerts");await expect(page.getByRole("button",{name:"Повторить сообщение Held retry fixture"})).toBeEnabled();await page.goto("/settings");await source(page,"http://localhost:8000");await page.goto("/alerts");const panel=page.getByRole("region",{name:"Очередь Telegram",exact:true});await expect(panel).toContainText("Результат повтора неизвестен");release();await expect.poll(()=>completed).toBe(true);await expect(panel.getByRole("button",{name:"Повторить сообщение Held retry fixture"})).toBeDisabled();await expect(panel).not.toContainText("Сообщение поставлено в очередь");await expect(panel).not.toContainText("OLD RETRY FAILED");expect(calls).toBe(1)}finally{release()}
})