Files
MikrotikManager/backend/src/services/system-database-partition-restore.test.ts
T

75 lines
3.9 KiB
TypeScript

import assert from "node:assert/strict"
import { test } from "node:test"
import { partitionRestoreAdmissionSql, partitionRestorePrelude, restorePartitionedDatabase } from "./system-database-partition-restore.js"
const toc = "1; 1259 1 TABLE public servers mmapp\n2; 1259 2 TABLE public schema_migrations mmapp\n3; 1259 3 TABLE public server_snapshots mmapp\n"
test("preparation covers only known application parents present in the archive", () => {
const sql = partitionRestorePrelude(toc + "4; 1259 4 TABLE other flow_buckets owner\n5; 1259 5 TABLE public custom_table owner\n")
assert.ok(sql.includes("IN ('server_snapshots')"))
assert.ok(sql.includes("c.conparentid = 0"))
assert.ok(sql.includes("format('ALTER TABLE %I.%I DROP CONSTRAINT %I CASCADE'"))
assert.equal(sql.includes("flow_buckets"), false)
assert.equal(sql.includes("custom_table"), false)
assert.throws(() => partitionRestorePrelude("1; 1259 1 TABLE public unrelated owner\n"), /обязательные таблицы/)
})
test("archive decoding precedes one transactional psql execution with fail-fast SQL", async () => {
const calls: string[] = []
await restorePartitionedDatabase("fixture.dump", "fixture-db", {
prepareSql: async file => { assert.equal(file, "fixture.dump.sql"); calls.push("prepare") },
command: async (command, args) => {
calls.push(command)
if (command === "pg_restore" && args[0] === "--list") return Buffer.from(toc)
if (command === "psql") {
assert.deepEqual(args.slice(0, 6), ["-X", "--single-transaction", "--set", "ON_ERROR_STOP=on", "-d", "fixture-db"])
assert.equal(args[6], "-c"); assert.equal(args[7], partitionRestoreAdmissionSql)
assert.match(args[7], /pg_advisory_xact_lock_shared\(8723110::bigint\)/)
assert.match(args[7], /SET LOCAL lock_timeout='10s'/)
assert.equal(args[8], "-c"); assert.ok(args[9].includes("DROP CONSTRAINT"))
assert.deepEqual(args.slice(10), ["-f", "fixture.dump.sql"])
}
return Buffer.alloc(0)
},
})
assert.deepEqual(calls, ["pg_restore", "prepare", "pg_restore", "psql"])
})
test("psql admission failure is not automatically retried", async () => {
let attempts = 0
await assert.rejects(restorePartitionedDatabase("fixture with spaces.dump", "fixture-db", {
prepareSql: async () => {},
command: async (command, args) => {
if (command === "psql") { attempts++; assert.equal(args[7], partitionRestoreAdmissionSql); throw Error("owned admission refused") }
return Buffer.from(toc)
},
}), /owned admission refused/)
assert.equal(attempts, 1)
})
test("invalid or undecodable archive never starts target SQL", async () => {
for (const phase of ["list", "decode"]) {
let executions = 0
await assert.rejects(restorePartitionedDatabase("fixture.dump", "fixture-db", {
prepareSql: async () => {},
command: async (command, args) => {
if (command === "psql") executions++
if ((phase === "list" && args[0] === "--list") || (phase === "decode" && args[0] === "--clean")) throw Error("archive failed")
return Buffer.from(toc)
},
}), /archive failed/)
assert.equal(executions, 0)
}
})
test("control namespace archive refuses immediately after TOC before decoding or target replacement", async () => {
for (const entry of ["SCHEMA - mmapp_control owner", "TABLE mmapp_control database_schema_state owner", "COMMENT - SCHEMA mmapp_control owner", 'ACL - SCHEMA "MMAPP_CONTROL" owner']) {
const calls: string[] = []
await assert.rejects(restorePartitionedDatabase("fixture.dump", "fixture-db", {
prepareSql: async () => { calls.push("prepare") },
beforeTargetReplacement: () => { calls.push("replacement") },
command: async (command, args) => { calls.push(command + " " + args[0]); return Buffer.from(toc + "4; 2615 4 " + entry + "\n") },
}), /служебное пространство/)
assert.deepEqual(calls, ["pg_restore --list"])
}
})