Files
MikrotikManager/backend/src/services/internet-path-snapshot.test.ts
T

38 lines
3.4 KiB
TypeScript

import assert from "node:assert/strict"
import {collectConfirmedInternetPath, readInternetPathRows, readStoredInternetPathPayload, mapStoredInternetPathResponse} from "./internet-path-snapshot.js"
const servers=[{id:1,type:"home-router",enabled:true},{id:2,type:"home-router",enabled:false},{id:3,type:"exit-node",enabled:true}]
const sampledAt="2026-10-04T00:00:00Z"
const defaults={servers:async()=>servers,gre:async()=>[],filters:async()=>[],speed:async()=>[],route:async(id:number)=>{assert.equal(id,1);return {gateway:null,routingMark:null}},wan:async(id:number)=>{assert.equal(id,1);return {defaultGateway:null,defaultInterface:null,uplinks:[]}}}
const good=await collectConfirmedInternetPath(sampledAt,defaults)
assert.equal(good.collectionComplete,true);assert.deepEqual(Object.keys(good.wanRuntimeByHomeId),["1"]);assert.equal(good.sampledAt,sampledAt)
// A failed mandatory source must not reach the persistence boundary.
for(const source of ["servers","gre","filters","speed","route","wan"] as const){
let saved=0
await assert.rejects(async()=>{await collectConfirmedInternetPath(sampledAt,{...defaults,[source]:async()=>{throw Error("denied")}});saved++},/denied/)
assert.equal(saved,0)
}
for(const source of ["route","wan"] as const)await assert.rejects(collectConfirmedInternetPath(sampledAt,{...defaults,[source]:async()=>null}),/не подтвердило/)
assert.deepEqual(readInternetPathRows([],"routes"),[])
assert.deepEqual(readInternetPathRows([{active:"true",gateway:"192.0.2.1"}],"routes"),[{active:"true",gateway:"192.0.2.1"}])
for(const raw of [null,{},[null],[[]],[{active:12}],[{active:true}]])assert.throws(()=>readInternetPathRows(raw,"routes"),/повреждён/)
console.log("internet path snapshot tests ok: complete, disabled home, six failed readers, missing runtime, malformed RouterOS")
assert.deepEqual(readStoredInternetPathPayload('{"collectionComplete":true}'),{collectionComplete:true})
assert.deepEqual(readStoredInternetPathPayload(good),good)
for(const raw of [null,"null","[1]","bad",[],1])assert.throws(()=>readStoredInternetPathPayload(raw))
// A syntactically valid string record may still be an API error, not an entity.
for(const [fields,row] of [[ ["dst-address"],{"message":"denied"} ],[["interface"],{}],[["address","interface"],{address:"192.0.2.1/24"}],[[".id","name","local-address","remote-address"],{".id":"*1",name:"gre", "local-address":"0.0.0.0"}] ] as Array<[string[],Record<string,string>]>)assert.throws(()=>readInternetPathRows([row],"test",fields),/повреждён/)
assert.deepEqual(readInternetPathRows([],"routes",["dst-address"]),[])
assert.equal(readInternetPathRows([{ "dst-address":"0.0.0.0/0",active:"true" }],"routes",["dst-address"]).length,1)
assert.equal(readInternetPathRows([{ ".id":"*1",name:"gre", "local-address":"0.0.0.0","remote-address":"192.0.2.2" }],"gre",[".id","name","local-address","remote-address"]).length,1)
for (const stored of ["2026-10-04 07:00:00+07", "2026-10-04T00:00:00Z"]) {
const response=mapStoredInternetPathResponse({payloadJson:good,sampledAt:stored})
assert.equal(response.sampledAt,"2026-10-04T00:00:00.000Z")
assert.deepEqual(response.snapshot,good)
}
for (const invalid of ["", "bad", null]) assert.throws(()=>mapStoredInternetPathResponse({payloadJson:good,sampledAt:invalid as string}))
assert.throws(()=>mapStoredInternetPathResponse({payloadJson:"bad",sampledAt}))
console.log("internet path HTTP timestamp: PostgreSQL/ISO instant preserved; invalid metadata rejected")