Files
MikrotikManager/shared/api/system-database.test.ts
T

50 lines
5.0 KiB
TypeScript

import assert from "node:assert/strict"
import {test} from "node:test"
import {downloadSystemDatabaseBackup,restoreSystemDatabaseBackup} from "./system-database"
const dump=new File(["PGDMPfixture"],"fixture.dump")
test("export requires PGDMP bytes and a safe filename; fallback is dump, not db",async()=>{
const original=globalThis.fetch;try{
globalThis.fetch=async()=>new Response("PGDMPfixture",{headers:{"Content-Disposition":"attachment; filename*=UTF-8''backup%20fixture.dump"}})
const result=await downloadSystemDatabaseBackup("https://backend.test");assert.equal(result.filename,"backup fixture.dump");assert.equal(await result.blob.text(),"PGDMPfixture")
globalThis.fetch=async()=>new Response("PGDMPfixture");assert.equal((await downloadSystemDatabaseBackup("https://backend.test")).filename,"mikrotik-manager.dump")
for(const bytes of ["", "{}", "<html>proxy</html>"]){globalThis.fetch=async()=>new Response(bytes);await assert.rejects(downloadSystemDatabaseBackup("https://backend.test"),/PGDMP/)}
for(const filename of ["../backup.dump","a/b.dump","a\\b.dump"]){globalThis.fetch=async()=>new Response("PGDMPfixture",{headers:{"Content-Disposition":`attachment; filename="${filename}"`}});await assert.rejects(downloadSystemDatabaseBackup("https://backend.test"),/Имя файла/)}
}finally{globalThis.fetch=original}
})
test("restore validates empty/invalid/oversized file and abort before any POST",async()=>{
const original=globalThis.fetch;let calls=0;try{globalThis.fetch=async()=>{calls++;return Response.json({ok:true})};for(const bytes of ["", "sqlite", "<html>"]){await assert.rejects(restoreSystemDatabaseBackup("https://backend.test",new File([bytes],"wrong.dump")),/PGDMP/)}
const huge=new File(["PGDMP"],"huge.dump");Object.defineProperty(huge,"size",{value:512*1024*1024+1});await assert.rejects(restoreSystemDatabaseBackup("https://backend.test",huge),/512 МБ/)
const controller=new AbortController();controller.abort();await assert.rejects(restoreSystemDatabaseBackup("https://backend.test",dump,{signal:controller.signal}));assert.equal(calls,0)
}finally{globalThis.fetch=original}
})
test("restore needs literal receipt; malformed/false/204/HTTP error never retry",async()=>{
const original=globalThis.fetch;try{
let calls=0;globalThis.fetch=async(_url,init)=>{calls++;assert.equal(init?.body,dump);assert.equal(new Headers(init?.headers).get("Content-Type"),"application/octet-stream");assert.ok(init?.signal);return Response.json({ok:true,eventError:"audit unavailable"})}
assert.deepEqual(await restoreSystemDatabaseBackup("https://backend.test",dump),{ok:true,eventError:"audit unavailable"});assert.equal(calls,1)
for(const raw of [{},{ok:false},{ok:"true"},{ok:true,eventError:5},{ok:true,error:"restore failed"}]){calls=0;globalThis.fetch=async()=>{calls++;return Response.json(raw)};await assert.rejects(restoreSystemDatabaseBackup("https://backend.test",dump),/не подтверждено/);assert.equal(calls,1)}
calls=0;globalThis.fetch=async()=>{calls++;return new Response(null,{status:204})};await assert.rejects(restoreSystemDatabaseBackup("https://backend.test",dump),/не подтверждено/);assert.equal(calls,1)
calls=0;globalThis.fetch=async()=>{calls++;return Response.json({error:"restore unknown"},{status:503})};await assert.rejects(restoreSystemDatabaseBackup("https://backend.test",dump),/restore unknown/);assert.equal(calls,1)
}finally{globalThis.fetch=original}
})
test("backup warning header is strict and malformed metadata preserves valid archive with warning",async()=>{
const original=globalThis.fetch;try{
const warnings={listenerError:"listener warning",schedulerError:"scheduler warning",eventError:"event warning"}
globalThis.fetch=async()=>new Response("PGDMPfixture",{headers:{"X-System-Database-Outcome":encodeURIComponent(JSON.stringify(warnings))}})
assert.deepEqual((await downloadSystemDatabaseBackup("https://backend.test")).warnings,warnings)
for(const header of ["%","false",encodeURIComponent(JSON.stringify({listenerError:4})),encodeURIComponent(JSON.stringify({error:"failed"}))]){
globalThis.fetch=async()=>new Response("PGDMPfixture",{headers:{"X-System-Database-Outcome":header}})
const result=await downloadSystemDatabaseBackup("https://backend.test");assert.equal(await result.blob.text(),"PGDMPfixture");assert.match(result.warnings.eventError!,/не подтверждены/)
}
}finally{globalThis.fetch=original}
})
test("restore dispatch checkpoint runs only after local validation and immediately before POST",async()=>{
const original=globalThis.fetch;const calls:string[]=[];try{
globalThis.fetch=async()=>{calls.push("POST");return Response.json({ok:true})}
const options={onDispatch:()=>calls.push("checkpoint")}
await assert.rejects(restoreSystemDatabaseBackup("https://backend.test",new File(["wrong"],"wrong.dump"),options));assert.deepEqual(calls,[])
await restoreSystemDatabaseBackup("https://backend.test",dump,options);assert.deepEqual(calls,["checkpoint","POST"])
}finally{globalThis.fetch=original}
})