121 lines
3.6 KiB
TypeScript
121 lines
3.6 KiB
TypeScript
import { EventEmitter } from 'node:events'
|
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import { createDb, closeDb, migrateSchema } from '@authportal/db'
|
|
import { registerShutdown } from '../src/lib/shutdown.js'
|
|
import { buildTestApp } from './helpers.js'
|
|
|
|
function fakeApp(close: () => Promise<void>) {
|
|
return {
|
|
close,
|
|
log: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
|
|
} as unknown as Parameters<typeof registerShutdown>[0]
|
|
}
|
|
|
|
describe('registerShutdown', () => {
|
|
afterEach(() => {
|
|
vi.useRealTimers()
|
|
})
|
|
|
|
it('закрывает приложение один раз и выходит с 0', async () => {
|
|
const close = vi.fn(async () => {})
|
|
const exit = vi.fn()
|
|
const proc = new EventEmitter()
|
|
registerShutdown(fakeApp(close), {
|
|
timeoutMs: 1000,
|
|
exit,
|
|
proc: proc as never,
|
|
})
|
|
proc.emit('SIGTERM')
|
|
await vi.waitFor(() => expect(exit).toHaveBeenCalledWith(0))
|
|
expect(close).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('повторный сигнал форсирует выход, close не вызывается дважды', async () => {
|
|
let release!: () => void
|
|
const close = vi.fn(
|
|
() => new Promise<void>((r) => (release = r)),
|
|
)
|
|
const exit = vi.fn()
|
|
const proc = new EventEmitter()
|
|
registerShutdown(fakeApp(close), {
|
|
timeoutMs: 1000,
|
|
exit,
|
|
proc: proc as never,
|
|
})
|
|
proc.emit('SIGTERM')
|
|
proc.emit('SIGINT')
|
|
expect(exit).toHaveBeenCalledWith(1)
|
|
expect(close).toHaveBeenCalledTimes(1)
|
|
release()
|
|
})
|
|
|
|
it('по таймауту принудительно выходит с кодом 1', async () => {
|
|
vi.useFakeTimers()
|
|
const exit = vi.fn()
|
|
const proc = new EventEmitter()
|
|
registerShutdown(
|
|
fakeApp(() => new Promise<void>(() => {})),
|
|
{ timeoutMs: 500, exit, proc: proc as never },
|
|
)
|
|
proc.emit('SIGTERM')
|
|
await vi.advanceTimersByTimeAsync(501)
|
|
expect(exit).toHaveBeenCalledWith(1)
|
|
})
|
|
|
|
it('ошибка close → выход с кодом 1', async () => {
|
|
const exit = vi.fn()
|
|
const proc = new EventEmitter()
|
|
registerShutdown(
|
|
fakeApp(async () => {
|
|
throw new Error('boom')
|
|
}),
|
|
{ timeoutMs: 1000, exit, proc: proc as never },
|
|
)
|
|
proc.emit('SIGTERM')
|
|
await vi.waitFor(() => expect(exit).toHaveBeenCalledWith(1))
|
|
})
|
|
|
|
it('unhandledRejection запускает shutdown с кодом 1', async () => {
|
|
const exit = vi.fn()
|
|
const proc = new EventEmitter()
|
|
registerShutdown(fakeApp(async () => {}), {
|
|
timeoutMs: 1000,
|
|
exit,
|
|
proc: proc as never,
|
|
})
|
|
proc.emit('unhandledRejection', new Error('x'))
|
|
await vi.waitFor(() => expect(exit).toHaveBeenCalledWith(1))
|
|
})
|
|
|
|
it('dispose снимает обработчики', () => {
|
|
const proc = new EventEmitter()
|
|
const h = registerShutdown(fakeApp(async () => {}), {
|
|
timeoutMs: 1000,
|
|
exit: vi.fn(),
|
|
proc: proc as never,
|
|
})
|
|
h.dispose()
|
|
expect(proc.listenerCount('SIGTERM')).toBe(0)
|
|
expect(proc.listenerCount('unhandledRejection')).toBe(0)
|
|
})
|
|
})
|
|
|
|
describe('closeDb', () => {
|
|
it('идемпотентно закрывает БД', () => {
|
|
const { sqlite } = createDb('sqlite::memory:')
|
|
migrateSchema(sqlite)
|
|
closeDb(sqlite)
|
|
expect(sqlite.open).toBe(false)
|
|
expect(() => closeDb(sqlite)).not.toThrow()
|
|
expect(() => sqlite.prepare('SELECT 1')).toThrow()
|
|
})
|
|
|
|
it('app.close() закрывает БД приложения', async () => {
|
|
const app = await buildTestApp()
|
|
await app.ready()
|
|
expect(app.sqlite.open).toBe(true)
|
|
await app.close()
|
|
expect(app.sqlite.open).toBe(false)
|
|
})
|
|
})
|