59 lines
6.5 KiB
TypeScript
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")
|