fix(alerts): проверять правила и группы до замены конфигурации

This commit is contained in:
Denozordec committed 2026-10-03 17:16:21 +07:00
1 parent 3556a378f2
commit 6093442bcf
10 files changed
+61 -10

No files matched your search

@@ -0,0 +1,12 @@
import assert from "node:assert/strict"
import {test} from "node:test"
import {normalizeAlertsConfig,type PutRulesBody} from "./alerts.js"
const rule={id:"r",name:"Server",type:"server" as const,target:"legacy",condition:"down",severity:"critical" as const,enabled:false,cooldown:"5м" as const,chatId:""}
test("Alerts normalization preserves policy and produces shared summaries/ordered OR arrays",()=>{
const result=normalizeAlertsConfig({rules:[{...rule,targets:[" a ","b","c"],conditions:[" x ","y","z","q"],confirmStabilitySec:0,recoveryMode:"never",recoveryStabilitySec:0}],groups:[]})
assert.deepEqual(result.rules[0].targets,["a","b","c"]);assert.deepEqual(result.rules[0].conditions,["x","y","z","q"]);assert.equal(result.rules[0].target,"a, b +1");assert.equal(result.rules[0].condition,"x · y · +2");assert.equal(result.rules[0].enabled,false);assert.equal(result.rules[0].recoveryMode,"never");assert.equal(result.rules[0].confirmStabilitySec,null);assert.equal(result.rules[0].recoveryStabilitySec,null);assert.equal(result.rules[0].cooldown,"5м")
})
test("known empty differs from omitted groups; legacy target/condition retained",()=>{assert.deepEqual(normalizeAlertsConfig({rules:[],groups:[]}),{rules:[],groups:[]});const result=normalizeAlertsConfig({rules:[{...rule,groupId:"g"}]});assert.equal(result.groups,undefined);assert.deepEqual(result.rules[0].targets,["legacy"]);assert.deepEqual(result.rules[0].conditions,["down"]);assert.equal(result.rules[0].groupId,"g")})
test("duplicates, dangling groups, whitespace-only objects/conditions and invalid delays reject",()=>{
for(const payload of [{rules:[rule,rule]},{rules:[{...rule,groupId:"missing"}],groups:[]},{rules:[{...rule,targets:[" "]}]},{rules:[{...rule,conditions:[" "]}]},{rules:[{...rule,confirmStabilitySec:1.5}]},{rules:[],groups:[{id:"g",name:"Group",combineMode:"any",enabled:false,cooldownOverride:null},{id:"g",name:"Group",combineMode:"any",enabled:false,cooldownOverride:null}]}])assert.throws(()=>normalizeAlertsConfig(payload as PutRulesBody))
})
+23
View File
@@ -107,3 +107,26 @@ export const alertsRulesReceiptSchema = z.object({ok:z.literal(true),...alertsCo
export const telegramSettingsReceiptSchema = z.object({ok:z.literal(true),telegram:telegramPublicSchema}).strict()
export const telegramTestReceiptSchema = z.object({ok:z.literal(true)}).strict()
export type AlertsResponse = z.output<typeof alertsResponseSchema>
/** Normalize the stored rule representation without altering OR/AND or cooldown policies. */
export function normalizeAlertsConfig(input:PutRulesBody){
const parsed=putRulesSchema.strict().safeParse(input)
if(!parsed.success)throw Error("Проверьте правила и группы оповещений")
const groups=parsed.data.groups?.map(group=>({...group,name:group.name.trim()||group.id}))
const groupIds=groups===undefined?undefined:new Set(groups.map(group=>group.id))
const ids=new Set<string>()
if(groups!==undefined&&groupIds!.size!==groups.length)throw Error("Повторяющиеся ID групп")
const rules=parsed.data.rules.map(rule=>{
if(ids.has(rule.id))throw Error("Повторяющиеся ID правил")
ids.add(rule.id)
const targets=(rule.targets?.length?rule.targets:[rule.target??""]).map(value=>value.trim()).filter(Boolean)
const conditions=(rule.conditions?.length?rule.conditions:[rule.condition??""]).map(value=>value.trim()).filter(Boolean)
if(!targets.length||!conditions.length)throw Error("Нужны непустые объекты и условия правила")
const groupId=rule.groupId?.trim()||null
if(groupId!==null&&groupIds!==undefined&&!groupIds.has(groupId))throw Error("Неизвестная группа правила")
const target=targets.length<=2?targets.join(", "):targets[0]+", "+targets[1]+" +"+(targets.length-2)
const condition=conditions.length===1?conditions[0]!:conditions.length<=3?conditions.join(" · "):conditions[0]+" · "+conditions[1]+" · +"+(conditions.length-2)
return {...rule,target,targets,condition,conditions,groupId,confirmStabilitySec:rule.confirmStabilitySec||null,recoveryMode:rule.recoveryMode??"always",recoveryStabilitySec:rule.recoveryStabilitySec||null}
})
return {...(groups!==undefined?{groups}:{}),rules}
}