Files
MikrotikManager/backend/src/services/database-request-gate.test.ts
T

75 lines
3.8 KiB
TypeScript

import assert from "node:assert/strict"
import { test } from "node:test"
import Fastify from "fastify"
import { DatabaseRequestGate } from "./database-request-gate.js"
import { installDatabaseRequestGate, registerDatabaseStreamAbort } from "../plugins/database-request-gate.js"
function deferred() {
let resolve!: () => void
const promise = new Promise<void>(r => { resolve = r })
return { promise, resolve }
}
test("exclusive operation waits for handler settlement and blocks new requests", async () => {
const gate = new DatabaseRequestGate(), pending = deferred(), entered = deferred()
const request = gate.request(async () => { entered.resolve(); await pending.promise })
await entered.promise
let commands = 0
const operation = gate.exclusive(async () => { commands++ })
await assert.rejects(gate.request(async () => {}), /повторите запрос позже/)
await assert.rejects(gate.exclusive(async () => {}), /уже выполняется/)
assert.equal(commands, 0)
pending.resolve(); await request; await operation
assert.equal(commands, 1)
await gate.request(async () => {})
})
test("drain timeout cancels command without declaring an active handler finished", async () => {
const gate = new DatabaseRequestGate(), pending = deferred()
const request = gate.request(() => pending.promise)
let commands = 0
await assert.rejects(gate.exclusive(async () => { commands++ }, 10), /операция с базой отменена/)
assert.equal(commands, 0)
const retry = gate.exclusive(async () => { commands++ })
await Promise.resolve(); assert.equal(commands, 0)
pending.resolve(); await request; await retry
assert.equal(commands, 1)
})
test("stream cancellation waits for its asynchronous cleanup", async () => {
const gate = new DatabaseRequestGate(), aborted = deferred(), cleanup = deferred()
const stream = gate.request(async () => { await aborted.promise; await cleanup.promise }, aborted.resolve)
let commands = 0
const operation = gate.exclusive(async () => { commands++ })
await aborted.promise; assert.equal(commands, 0)
cleanup.resolve(); await stream; await operation; assert.equal(commands, 1)
})
test("Fastify gate covers GET, keeps health available and drains late stream registration", async () => {
const app = Fastify(); installDatabaseRequestGate(app)
const entered = deferred(), read = deferred(), cleanup = deferred(), command = deferred(), leave = deferred()
app.get('/health', async () => ({ ok: true }))
await app.register(async child => {
child.get('/traffic/flow/live', async request => {
entered.resolve(); await read.promise
const aborted = deferred(); registerDatabaseStreamAbort(request, aborted.resolve)
await aborted.promise; await cleanup.promise
return { closed: true }
})
child.get('/servers', async () => ({ servers: [] }))
child.post('/system/database/restore', async () => { command.resolve(); await leave.promise; return { ok: true } })
}, { prefix: '/api' })
try {
const stream = app.inject('/api/traffic/flow/live'); await entered.promise
const operation = app.inject({ method: 'POST', url: '/api/system/database/restore' })
// inject schedules asynchronously: wait for gate to reject a subsequent GET.
await new Promise(r => setTimeout(r, 10))
assert.equal((await app.inject('/api/servers')).statusCode, 409)
assert.equal((await app.inject('/health')).statusCode, 200)
read.resolve(); cleanup.resolve(); await stream; await command.promise
assert.equal((await app.inject({ method: 'POST', url: '/api/system/database/restore' })).statusCode, 409)
leave.resolve(); assert.equal((await operation).statusCode, 200)
assert.equal((await app.inject('/api/servers')).statusCode, 200)
} finally { read.resolve(); cleanup.resolve(); leave.resolve(); await app.close() }
})