Files
MikrotikManager/backend/src/services/gre-native-mutation.test.ts
T

59 lines
6.5 KiB
TypeScript

import assert from "node:assert/strict"
import { greNativeMutationSchema, planGreNativeMutation, greNativeSelector } from "./gre-native-mutation.js"
import { greDependencyPaths, greMutationProof, type GreMutationState } from "./gre-mutation-state.js"
import { executeGreMutationOps, verifyGreMutationOp } from "./gre-mutation.js"
const state=():GreMutationState=>({serverConfiguration:"source",gre:[{".id":"*G",name:"own-gre",disabled:"true","dont-fragment":"no"}],ipv4:[],ipv6:[],dependencies:Object.fromEntries(greDependencyPaths.map(path=>[path,[]])),bindings:[],filters:[]})
const rename=greNativeMutationSchema.parse({operation:"update",id:"*G",fields:{name:"renamed-gre"}}),remove=greNativeMutationSchema.parse({operation:"delete",id:"*G"})
const before=state(),op=planGreNativeMutation(rename,before)
assert.deepEqual(op,{op:"patch",path:"/interface/gre/*G",body:{name:"renamed-gre"}})
assert.deepEqual(planGreNativeMutation(remove,before),{op:"delete",path:"/interface/gre/*G"})
for(const mutation of [{operation:"update",id:"*G",fields:{}},{operation:"update",id:"*G",fields:{"ipsec-secret":"raw"}},{operation:"delete",id:"*G,*OTHER"}])assert.equal(greNativeMutationSchema.safeParse(mutation).success,false)
for(const input of [rename,remove]){
const bound=state();bound.bindings=[{id:"binding",interfaceName:"own-gre"}];assert.throws(()=>planGreNativeMutation(input,bound),/привязкой/)
const filter=state();filter.filters=[{id:1,gatewayTunnelId:"own-gre",gateway:""}];assert.throws(()=>planGreNativeMutation(input,filter),/фильтром/)
const address=state();address.ipv4=[{".id":"*A",address:"192.0.2.1/24",interface:"own-gre",dynamic:"false"}];assert.throws(()=>planGreNativeMutation(input,address),/адресом/)
const lla=state();lla.ipv6=[{".id":"*LL",address:"fe80::1/64",interface:"own-gre",dynamic:"true"}];assert.doesNotThrow(()=>planGreNativeMutation(input,lla))
for(const path of ["/interface/bridge/port","/interface/vlan","/ip/vrf","/routing/ospf/interface-template","/routing/bfd/configuration","/routing/bgp/connection","/ip/route","/ip/firewall/nat","/ipv6/firewall/filter"]){
const referenced=state();referenced.dependencies[path]=[{".id":"*D","in-interface":"own-gre"}];assert.throws(()=>planGreNativeMutation(input,referenced),/используется/)
}
const nativeIdList=state();nativeIdList.dependencies["/interface/list/member"]=[{".id":"*M",interface:"*G,*OTHER"}];assert.throws(()=>planGreNativeMutation(input,nativeIdList),/используется/)
}
const scalar=greNativeMutationSchema.parse({operation:"update",id:"*G",fields:{mtu:1400}}),bound=state();bound.bindings=[{id:"binding",interfaceName:"own-gre"}]
for (const name of ["own-gre", "туннель-gre", 'quoted"name', "literal\\name"]) {
const referenced = state(); referenced.gre[0].name = name
const encoded = [...Buffer.from(name)].map(byte => "\\" + byte.toString(16).padStart(2, "0")).join("")
referenced.dependencies["/routing/filter/rule"] = [{ ".id": "*D", rule: `set gw "${encoded}";` }]
assert.throws(() => planGreNativeMutation(remove, referenced), /используется/)
}
assert.deepEqual(planGreNativeMutation(scalar,bound),{op:"patch",path:"/interface/gre/*G",body:{mtu:"1400"}})
const collision=state();collision.dependencies["/interface"]=[{".id":"*E",name:"renamed-gre"}];assert.throws(()=>planGreNativeMutation(rename,collision),/уже используется/)
const counters=state();counters.dependencies["/ip/firewall/filter"]=[{".id":"*F",chain:"forward",action:"accept",packets:"1",bytes:"100"}]
const tick=structuredClone(counters);tick.dependencies["/ip/firewall/filter"][0].packets="9000";assert.deepEqual(greMutationProof(counters),greMutationProof(tick))
tick.dependencies["/ip/firewall/filter"][0].action="drop";assert.notDeepEqual(greMutationProof(counters),greMutationProof(tick))
const after=structuredClone(before);after.gre[0].name="renamed-gre";assert.equal(verifyGreMutationOp(before,after,op),true)
const hasComment=state();hasComment.gre[0].comment="clear me"
const omittedComment=structuredClone(hasComment);delete omittedComment.gre[0].comment
assert.equal(verifyGreMutationOp(hasComment,omittedComment,{op:"patch",path:"/interface/gre/*G",body:{comment:""}}),true)
const hasLocal=state();hasLocal.gre[0]["local-address"]="192.0.2.1"
const omittedLocal=structuredClone(hasLocal);delete omittedLocal.gre[0]["local-address"]
assert.equal(verifyGreMutationOp(hasLocal,omittedLocal,{op:"patch",path:"/interface/gre/*G",body:{"local-address":""}}),true)
const omittedSecret=state(),explicitEmptySecret=state();explicitEmptySecret.gre[0]["ipsec-secret"]="";assert.notDeepEqual(greMutationProof(omittedSecret),greMutationProof(explicitEmptySecret))
after.gre[0]["dont-fragment"]="yes";assert.equal(verifyGreMutationOp(before,after,op),false)
const current=state();let commands=0
const ignored=await executeGreMutationOps(greNativeSelector(rename),current,[op],{read:async()=>structuredClone(current),command:async()=>{commands++}})
assert.equal(ignored.verified,false);assert.equal(commands,1)
commands=0
await assert.rejects(executeGreMutationOps(greNativeSelector(rename),current,[op],{read:async()=>structuredClone(current),command:async()=>{commands++;current.gre[0].name="renamed-gre";throw new Error("lost reply")}}),/lost reply/)
assert.equal(commands,1)
const lateDynamic=state(),initialDynamic=structuredClone(lateDynamic);commands=0
lateDynamic.dependencies["/ip/route"]=[{".id":"*LEARNED",dynamic:"true",gateway:"192.0.2.2%own-gre"}]
assert.deepEqual(greMutationProof(lateDynamic),greMutationProof(initialDynamic))
await assert.rejects(executeGreMutationOps(greNativeSelector(remove),initialDynamic,[planGreNativeMutation(remove,initialDynamic)],{read:async()=>structuredClone(lateDynamic),validate:fresh=>{planGreNativeMutation(remove,fresh)},command:async()=>{commands++}}),/используется/)
assert.equal(commands,0)
const ordered=state();ordered.dependencies["/ip/firewall/nat"]=[{".id":"*F1",action:"accept"},{".id":"*F2",action:"drop"}]
const reordered=structuredClone(ordered);reordered.dependencies["/ip/firewall/nat"].reverse();assert.notDeepEqual(greMutationProof(ordered),greMutationProof(reordered))
const opaqueMtu=state();opaqueMtu.gre[0].mtu="auto"
const changedOpaqueMtu=structuredClone(opaqueMtu);changedOpaqueMtu.gre[0].mtu="unverified";assert.notDeepEqual(greMutationProof(opaqueMtu),greMutationProof(changedOpaqueMtu))
console.log("Native GRE aliases: exact one-interface mutation, user/filter/address/protocol/firewall references, identity lists, opaque proof, counter tolerance, explicit rename and no retry")