Files
MikrotikManager/backend/src/services/server-probe-retention.test.ts
T

70 lines
3.8 KiB
TypeScript

import assert from "node:assert/strict"
import { test } from "node:test"
import { createServerProbeRetentionForTests } from "./server-probe-retention.js"
import type { ProbeStorageConnection } from "./server-probe-journal-maintenance.js"
import { sharedDatabaseGate } from "./shared-database-gate.js"
function fixture(owner = false) {
const commands: string[] = [], releases: boolean[] = []
let settingsReads = 0, drift = false, failCommit = false, checkouts = 0
const connection: ProbeStorageConnection = {
async query(text) {
commands.push(text)
if (text.includes("probe-retention:settings")) return { rows: [{ requests_retention_days: drift && ++settingsReads > 5 ? 31 : 30 }] }
if (text.includes("probe-retention:owners")) return { rows: owner ? [{ owner_id: "owned-retention" }] : [] }
if (text.includes("probe-storage:counters")) return { rows: [{ scope: "global" }, { scope: "owner:owned-retention" }] }
if (text.includes("probe-storage:gc-candidates")) return { rows: [] }
if (text === "COMMIT" && failCommit) throw Error("PRIVATE_DRIVER_ERROR")
return { rows: [] }
}, release(unsafe) { releases.push(!!unsafe) },
}
const run = createServerProbeRetentionForTests(async options => {
checkouts++; assert(options.deadlineAt <= Date.now() + 20000); return connection
})
return { run, commands, releases, checkouts: () => checkouts, drift: () => { drift = true }, failCommit: () => { failCommit = true } }
}
test("empty retention uses one bounded backend and no mutation", async () => {
const f = fixture(); assert.deepEqual(await f.run(), { skipped: false, owners: 0, collected: 0 })
assert.equal(f.checkouts(), 1); assert.deepEqual(f.releases, [false])
assert.equal(f.commands.length, 2); assert(f.commands.every(text => text.includes("SELECT")))
})
test("selected owner GC reuses one physical backend for policy fences and transaction", async () => {
const f = fixture(true)
assert.deepEqual(await f.run(), { skipped: false, owners: 1, collected: 0 })
assert.equal(f.checkouts(), 1); assert.deepEqual(f.releases, [false])
assert.equal(f.commands.filter(text => text === "BEGIN").length, 1)
assert.equal(f.commands.filter(text => text === "COMMIT").length, 1)
assert(!f.commands.some(text => /DELETE FROM|\/tool\//.test(text)))
})
test("retention drift rolls back without committing cleanup", async () => {
const f = fixture(true); f.drift(); await assert.rejects(f.run())
assert(!f.commands.includes("COMMIT")); assert(f.commands.includes("ROLLBACK"))
assert.equal(f.releases.length, 1)
})
test("unknown commit destroys once and does not repeat the maintenance command", async () => {
const f = fixture(true); f.failCommit(); await assert.rejects(f.run())
assert.equal(f.commands.filter(text => text === "COMMIT").length, 1)
assert.deepEqual(f.releases, [true])
})
test("maintenance does not overlap and database replacement drains its real lifetime", async () => {
let entered!: () => void, resume!: () => void, checkouts = 0, replacementStarted = false
const started = new Promise<void>(resolve => { entered = resolve })
const pending = new Promise<void>(resolve => { resume = resolve })
const run = createServerProbeRetentionForTests(async () => {
checkouts++
return { async query(text) {
if (text.includes("probe-retention:settings")) {
entered(); await pending; return { rows: [{ requests_retention_days: 30 }] }
}
return { rows: [] }
}, release() {} }
})
const first = run(); await started
assert.deepEqual(await run(), { skipped: true, owners: 0, collected: 0 }); assert.equal(checkouts, 1)
const replacement = sharedDatabaseGate.exclusive(async () => { replacementStarted = true })
await Promise.resolve(); assert.equal(replacementStarted, false)
resume(); await first; await replacement
assert.equal(replacementStarted, true)
})