75 lines
3.9 KiB
TypeScript
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"])
|
|
}
|
|
})
|