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 ["", "{}", "proxy"]){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", ""]){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} })