Files

50 lines
5.9 KiB
TypeScript

import assert from "node:assert/strict"
import {test} from "node:test"
import {getAlerts,saveAlertRules,saveAlertsTelegram,testAlertsTelegram} from "./alerts"
const telegram={chatId:"",tokenConfigured:false,connected:false,messageThreadId:null}
const revision="1".repeat(64)
const empty={revision,telegram,groups:[],rules:[],history:[],meta:{servers:[],greClients:[],probeTargets:[],rttLossTargets:[],trafficServers:[],serversDetail:[]}}
test("Alerts reader preserves known empty/false/null and rejects malformed collections, public secrets and dates",async()=>{
const original=globalThis.fetch;try{
globalThis.fetch=async()=>Response.json(empty);assert.deepEqual(await getAlerts("https://backend.test"),empty)
for(const raw of [{},{...empty,rules:null},{...empty,groups:undefined},{...empty,telegram:{...telegram,connected:true}},{...empty,telegram:{...telegram,token:"secret"}},{...empty,history:[{id:"h",ruleName:"r",severity:"info",message:"m",sent:false,firedAt:"invalid",source:"db"}]}]){globalThis.fetch=async()=>Response.json(raw);await assert.rejects(getAlerts("https://backend.test"),/не подтверждены/)}
}finally{globalThis.fetch=original}
})
test("Alerts mutations require literal complete receipts and never repeat unknown requests",async()=>{
const original=globalThis.fetch;try{
for(const call of [()=>saveAlertRules("https://backend.test",{rules:[],groups:[]},{expectedRevision:revision}),()=>saveAlertsTelegram("https://backend.test",{chatId:""}),()=>testAlertsTelegram("https://backend.test")])for(const raw of [{},{ok:false},{ok:"true"},{ok:true,error:"failed"}]){let calls=0;globalThis.fetch=async()=>{calls++;return Response.json(raw)};await assert.rejects(call(),/не подтвержден/);assert.equal(calls,1)}
globalThis.fetch=async()=>Response.json({ok:true,revision,groups:[],rules:[]});assert.equal((await saveAlertRules("https://backend.test",{groups:[],rules:[]},{expectedRevision:revision})).ok,true)
globalThis.fetch=async()=>Response.json({ok:true});assert.deepEqual(await testAlertsTelegram("https://backend.test"),{ok:true})
}finally{globalThis.fetch=original}
})
test("Telegram selective save confirms sent values and rejects mismatched configured secret/thread/chat",async()=>{
const original=globalThis.fetch;try{
globalThis.fetch=async(_url,init)=>{assert.deepEqual(JSON.parse(init?.body as string),{chatId:" 123 ",messageThreadId:null});return Response.json({ok:true,telegram:{...telegram,chatId:"123"}})}
assert.equal((await saveAlertsTelegram("https://backend.test",{chatId:" 123 ",messageThreadId:null})).telegram.chatId,"123")
globalThis.fetch=async()=>Response.json({ok:true,telegram})
for(const patch of [{chatId:"123"},{messageThreadId:1},{token:"fixture-secret"}])await assert.rejects(saveAlertsTelegram("https://backend.test",patch),/не совпадают/)
assert.equal((await saveAlertsTelegram("https://backend.test",{token:null})).telegram.tokenConfigured,false)
}finally{globalThis.fetch=original}
})
test("invalid Telegram thread/empty patch and abort do not send requests",async()=>{
const original=globalThis.fetch;let calls=0;try{globalThis.fetch=async()=>{calls++;return Response.json({ok:true})};await assert.rejects(saveAlertsTelegram("https://backend.test",{}),/Нет изменённых/);for(const id of [0,-1,1.5,NaN]){await assert.rejects(saveAlertsTelegram("https://backend.test",{messageThreadId:id}),/Проверьте/);await assert.rejects(testAlertsTelegram("https://backend.test",{messageThreadId:id}),/Проверьте/)}const controller=new AbortController();controller.abort();await assert.rejects(getAlerts("https://backend.test",{signal:controller.signal}));assert.equal(calls,0)}finally{globalThis.fetch=original}
})
test("rules save checks IDs, fields and normalized target/condition arrays without substituting computed labels",async()=>{
const original=globalThis.fetch;try{
const rule={id:"r",name:"Server",type:"server" as const,target:"host",targets:[" host "],groupId:null,condition:"down",conditions:[" down "],severity:"critical" as const,enabled:false,cooldown:"5м" as const,confirmStabilitySec:0,recoveryMode:"never" as const,recoveryStabilitySec:null,chatId:""}
const saved={...rule,targets:["host"],conditions:["down"],confirmStabilitySec:null,lastFiredAt:null}
globalThis.fetch=async()=>Response.json({ok:true,revision,groups:[],rules:[saved]});assert.equal((await saveAlertRules("https://backend.test",{rules:[rule],groups:[]},{expectedRevision:revision})).rules[0].enabled,false)
for(const wrong of [{...saved,enabled:true},{...saved,targets:["other"]},{...saved,name:"Other"}]){globalThis.fetch=async()=>Response.json({ok:true,revision,groups:[],rules:[wrong]});await assert.rejects(saveAlertRules("https://backend.test",{rules:[rule],groups:[]},{expectedRevision:revision}),/не совпадают/)}
let calls=0;globalThis.fetch=async()=>{calls++;return Response.json({ok:true})};await assert.rejects(saveAlertRules("https://backend.test",{rules:[rule,rule]},{expectedRevision:revision}),/Повторяющиеся/);await assert.rejects(saveAlertRules("https://backend.test",{rules:[{...rule,targets:[" "],conditions:[" "]}]},{expectedRevision:revision}),/непустые/);assert.equal(calls,0)
}finally{globalThis.fetch=original}
})
test("rules client requires read revision, sends quoted If-Match and does not retry conflict",async()=>{
const original=globalThis.fetch;let calls=0;try{
globalThis.fetch=async(_url,init)=>{calls++;assert.equal(new Headers(init?.headers).get("If-Match"),'"'+revision+'"');assert.deepEqual(JSON.parse(String(init?.body)),{rules:[],groups:[]});return Response.json({error:"Fixture stale revision"},{status:412})}
await assert.rejects(saveAlertRules("https://backend.test",{groups:[],rules:[]}),/Обновите снимок/);assert.equal(calls,0)
await assert.rejects(saveAlertRules("https://backend.test",{groups:[],rules:[]},{expectedRevision:revision}),/Fixture stale revision/);assert.equal(calls,1)
}finally{globalThis.fetch=original}
})