62 lines
6.7 KiB
TypeScript
62 lines
6.7 KiB
TypeScript
import assert from "node:assert/strict"
|
|
import { createServer } from "node:http"
|
|
import Fastify from "fastify"
|
|
import { pool, closePool } from "../db/index.js"
|
|
import { initDatabase } from "../db/bootstrap.js"
|
|
import { collectTrafficOnce } from "./traffic-collector.js"
|
|
import { recordTrafficCollectionAttempt } from "./traffic-collection-state.js"
|
|
import { buildSignalSnapshotFromCollectors } from "./alert-engine/signals-from-collectors.js"
|
|
import trafficRoutes from "../routes/traffic.js"
|
|
import { clearAlertCollectorHooksTimer } from "./alert-collector-hooks.js"
|
|
|
|
const database=process.env.MMAPP_OWN_SQL_DB
|
|
if(!database) console.log("traffic history SQL: skipped; explicit owned database required")
|
|
else {
|
|
assert(/^mm_accept_166_\d+$/.test(database));assert.equal(new URL(process.env.DATABASE_URL!).pathname,"/"+database)
|
|
assert.equal((await pool.query("SELECT current_database() AS name")).rows[0].name,database)
|
|
const prefix="own-history178-"+Date.now()+"-",userId=prefix+"user"
|
|
let serverId:number|undefined,deny=false,counter="1000",cases=0,requests=0
|
|
const http=createServer((req,res)=>{requests++;res.setHeader("Content-Type","application/json");if(deny){res.statusCode=401;res.end('{"message":"Unauthorized"}')}else res.end(JSON.stringify(req.url?.includes("/wireguard/")?[]:[{".id":"*1",name:"ether1",running:"true",disabled:"false","rx-byte":counter,"tx-byte":counter}]))})
|
|
await new Promise<void>(resolve=>http.listen(0,"127.0.0.1",resolve));const address=http.address();assert(address&&typeof address==="object")
|
|
const app=Fastify();await app.register(trafficRoutes)
|
|
const get=async(path:string)=>{const r=await app.inject({method:"GET",url:path});assert.equal(r.statusCode,200);return r.json()}
|
|
try {
|
|
await initDatabase()
|
|
const updatedAt=new Date(Date.now()-3_600_000).toISOString()
|
|
serverId=Number((await pool.query("INSERT INTO servers(name,host,port,use_ssl,enabled,username,password,updated_at) VALUES($1,'127.0.0.1',$2,false,true,'fixture','fixture',$3) RETURNING id",[prefix+"server",address.port,updatedAt])).rows[0].id)
|
|
await pool.query("INSERT INTO traffic_settings(id,enabled,interval_sec) VALUES(1,true,30) ON CONFLICT(id) DO UPDATE SET enabled=true,interval_sec=30")
|
|
await pool.query("INSERT INTO app_users(id,login,name) VALUES($1,$2,$3)",[userId,prefix+"login","History fixture"])
|
|
await pool.query("INSERT INTO user_interface_bindings(id,user_id,server_id,interface_name,interface_type) VALUES($1,$2,$3,'ether1','ether')",[prefix+"binding",userId,serverId])
|
|
for(const offset of[-60_000,-30_000])await pool.query("INSERT INTO traffic_samples(server_id,interface_name,sampled_at,rx_bytes,tx_bytes,flags) VALUES($1,'ether1',$2,1000,1000,1)",[serverId,new Date(Date.now()+offset).toISOString()])
|
|
const detail=async()=> (await get(`/traffic/servers/${serverId}?range=1h`)).server
|
|
let dto=await detail();assert.equal(dto.observations.rxNow,false);assert(dto.observations.rxSeries.every((v:boolean)=>!v));cases++
|
|
await collectTrafficOnce();dto=await detail();assert.equal(dto.observations.rxNow,false);cases++
|
|
await pool.query("DELETE FROM traffic_samples WHERE server_id=$1 AND (flags & 4)=0",[serverId])
|
|
await pool.query("UPDATE traffic_samples SET sampled_at=$1 WHERE server_id=$2",[new Date(Date.now()-30_000).toISOString(),serverId])
|
|
counter="3751000";const success=await collectTrafficOnce();dto=await detail();assert.equal(dto.observations.currentState,"current");assert.equal(dto.observations.rxNow,true);assert(dto.rxNow>0.9);cases++
|
|
await pool.query("INSERT INTO scheduler_runs(id,job_key,started_at,finished_at,status,result_json) VALUES($1,'traffic',$2,$3,'ok',$4::jsonb)",[prefix+"run",success.sampledAt,new Date().toISOString(),JSON.stringify(success)])
|
|
assert.equal((await buildSignalSnapshotFromCollectors()).trafficByServer.length,1);cases++
|
|
const succeeded=dto.observations.lastSucceededAt
|
|
deny=true;await collectTrafficOnce();dto=await detail();assert.equal(dto.observations.currentState,"error");assert.equal(dto.observations.rxNow,false);assert.equal(dto.observations.lastSucceededAt,succeeded);assert(dto.observations.rxSeries.some(Boolean));cases++
|
|
assert.deepEqual((await buildSignalSnapshotFromCollectors()).trafficByServer,[]);cases++
|
|
const users=(await get("/traffic/users?range=1h")).users, bound=(await get("/traffic/bound-interfaces?range=1h")).interfaces
|
|
assert.equal(users.find((u:{id:string})=>u.id===userId).observations.currentState,"error");assert.equal(bound.find((b:{userId:string})=>b.userId===userId).observations.rxNow,false);cases++
|
|
const interfaces=(await get(`/traffic/servers/${serverId}/interfaces`)).interfaces;assert.equal(interfaces[0].rxNow,null);cases++
|
|
const manual=await app.inject({method:"POST",url:"/traffic/collect-now"});assert.equal(manual.statusCode,502);assert.equal(manual.json().ok,false)
|
|
assert((await pool.query("SELECT status FROM scheduler_runs WHERE job_key='traffic' ORDER BY finished_at DESC LIMIT 1")).rows[0].status==="error");cases++
|
|
deny=false;counter="3751000";const recovery=await app.inject({method:"POST",url:"/traffic/collect-now"});assert.equal(recovery.statusCode,200);dto=await detail();assert.equal(dto.observations.currentState,"current");assert.equal(dto.rxNow,0);cases++
|
|
await pool.query("UPDATE traffic_collection_state SET last_attempt_at=$1,last_succeeded_at=$1 WHERE server_id=$2",[new Date(Date.now()-100_000).toISOString(),serverId]);dto=await detail();assert.equal(dto.observations.currentState,"stale");assert.equal(dto.observations.rxNow,false);assert(dto.observations.rxSeries.some(Boolean));cases++
|
|
await recordTrafficCollectionAttempt({id:serverId,updatedAt},"pending",new Date().toISOString());dto=await detail();assert.equal(dto.observations.currentState,"pending");cases++
|
|
await pool.query("UPDATE servers SET updated_at=$1 WHERE id=$2",[new Date(Date.now()+1).toISOString(),serverId]);dto=await detail();assert.equal(dto.observations.currentState,"unknown");cases++
|
|
await pool.query("UPDATE servers SET enabled=false WHERE id=$1",[serverId]);const before=requests;await collectTrafficOnce();assert.equal(requests,before);dto=await detail();assert.equal(dto.observations.currentState,"disabled");cases++
|
|
console.log(`traffic history owned SQL: ${cases} cases; legacy/current/failure/history/users/interfaces/manual/source/stale/pending/config/disabled passed`)
|
|
} finally {
|
|
clearAlertCollectorHooksTimer()
|
|
await app.close();await new Promise<void>(resolve=>http.close(()=>resolve()))
|
|
await pool.query("DELETE FROM app_users WHERE id=$1",[userId])
|
|
await pool.query("DELETE FROM scheduler_runs WHERE id LIKE $1",[prefix+"%"])
|
|
if(serverId!==undefined){await pool.query("DELETE FROM traffic_samples WHERE server_id=$1",[serverId]);await pool.query("DELETE FROM servers WHERE id=$1",[serverId])}
|
|
await closePool()
|
|
}
|
|
}
|