Files
MikrotikManager/backend/src/services/alerts-config-execution.test.ts
T

20 lines
3.8 KiB
TypeScript

import {alertsConfigRevision} from "./alerts-revision.js"
import assert from "node:assert/strict"
import {test} from "node:test"
import {executeAlertsConfigReplacement} from "./alerts-config-execution.js"
import {normalizeAlertsConfig} from "@mmapp/contracts/alerts"
const initial={groups:[{id:"g",name:"Group",combineMode:"all" as const,enabled:false,cooldownOverride:null}],rules:[]}
function fixture(){let state=structuredClone(initial),locks:boolean|undefined=true,failWrite=false,badRead=false,wrongRead=false;const calls:string[]=[];return {calls,state:()=>state,conflict:()=>locks=false,unknown:()=>locks=undefined,fail:()=>failWrite=true,bad:()=>badRead=true,wrong:()=>wrongRead=true,deps:{transaction:async(work:(context:typeof initial)=>Promise<unknown>)=>{calls.push("begin");const context=structuredClone(state);try{const receipt=await work(context);state=context;calls.push("commit");return receipt as never}catch(error){calls.push("rollback");throw error}},lock:async()=>{calls.push("lock");return locks},revision:async(context:typeof initial)=>{calls.push("revision");return alertsConfigRevision(context)},groups:async(context:typeof initial)=>{calls.push("groups");return context.groups},write:async(context:typeof initial,value:ReturnType<typeof normalizeAlertsConfig>)=>{calls.push("write");context.groups=value.groups!;if(failWrite)throw Error("insert failed")},read:async(context:typeof initial)=>{calls.push("read");return badRead?{ok:false}:wrongRead?{ok:true,revision:alertsConfigRevision({...context,groups:initial.groups}),...context,groups:initial.groups}:{ok:true,revision:alertsConfigRevision(context),...context}}}}}
test("omitted groups are read and confirmed inside one locked transaction",async()=>{const f=fixture();const receipt=await executeAlertsConfigReplacement({rules:[]},f.deps);assert.equal(receipt.ok,true);assert.deepEqual(receipt.groups,initial.groups);assert.deepEqual(f.calls,["begin","lock","groups","write","read","commit"])})
test("write/readback failures roll back and never repeat",async()=>{for(const step of ["write","read"]){const f=fixture();if(step==="write")f.fail();else f.bad();await assert.rejects(executeAlertsConfigReplacement({groups:[],rules:[]},f.deps));assert.deepEqual(f.state(),initial);assert.equal(f.calls.filter(s=>s==="write").length,1);assert.equal(f.calls.includes("commit"),false);assert.equal(f.calls.at(-1),"rollback")}})
test("false or unknown lock prevents every destructive write",async()=>{for(const unknown of [false,true]){const f=fixture();if(unknown)f.unknown();else f.conflict();await assert.rejects(executeAlertsConfigReplacement({groups:[],rules:[]},f.deps),/выполняется|не подтверждена/);assert.equal(f.calls.includes("write"),false);assert.deepEqual(f.state(),initial)}})
test("literal success with another config rolls back instead of confirming unrelated data",async()=>{const f=fixture();f.wrong();await assert.rejects(executeAlertsConfigReplacement({groups:[],rules:[]},f.deps),/не совпадают/);assert.deepEqual(f.state(),initial);assert.equal(f.calls.at(-1),"rollback")})
test("stale revision is rejected after a preceding commit before every destructive write",async()=>{
const f=fixture(),expectedRevision=alertsConfigRevision(f.state())
const receipt=await executeAlertsConfigReplacement({groups:[],rules:[],expectedRevision},f.deps);assert.equal(receipt.revision,alertsConfigRevision(f.state()));assert.notEqual(receipt.revision,expectedRevision)
const previousWrites=f.calls.filter(call=>call==="write").length
await assert.rejects(executeAlertsConfigReplacement({...initial,expectedRevision},f.deps),/Правила изменились/);assert.equal(f.calls.filter(call=>call==="write").length,previousWrites);assert.deepEqual(f.state(),{groups:[],rules:[]});assert.deepEqual(f.calls.slice(-4),["begin","lock","revision","rollback"])
})