// tests/main/shutdown-registry.test.ts // 종료 등록부 — 역순 정리, 멱등, 멈춘 항목이 종료를 막지 않음. import { describe, it, expect, vi } from 'vitest' import { ShutdownRegistry } from '../../src/main/shutdown-registry' describe('ShutdownRegistry', () => { it('등록의 역순으로 정리한다', async () => { const order: string[] = [] const registry = new ShutdownRegistry() registry.register({ name: 'a', dispose: () => { order.push('a') } }) registry.register({ name: 'b', dispose: async () => { order.push('b') } }) registry.register({ name: 'c', dispose: () => { order.push('c') } }) const report = await registry.disposeAll(1000) expect(order).toEqual(['c', 'b', 'a']) expect(report.completed).toEqual(['c', 'b', 'a']) }) it('여러 번 불러도 한 번만 정리한다', async () => { const dispose = vi.fn() const registry = new ShutdownRegistry() registry.register({ name: 'x', dispose }) await Promise.all([registry.disposeAll(1000), registry.disposeAll(1000)]) await registry.disposeAll(1000) expect(dispose).toHaveBeenCalledTimes(1) }) it('실패한 항목은 기록하고 나머지를 계속 정리한다', async () => { const onError = vi.fn() const later = vi.fn() const registry = new ShutdownRegistry({ onError }) registry.register({ name: 'later', dispose: later }) registry.register({ name: 'throws', dispose: () => { throw new Error('sync boom') } }) registry.register({ name: 'rejects', dispose: async () => { throw new Error('async boom') } }) const report = await registry.disposeAll(1000) expect(report.failed).toEqual(['rejects', 'throws']) expect(report.completed).toEqual(['later']) expect(later).toHaveBeenCalledTimes(1) expect(onError).toHaveBeenCalledTimes(2) }) it('멈춘 항목이 있어도 상한 시간 안에 끝나고, 남은 항목의 dispose 는 걸어 둔다', async () => { vi.useFakeTimers() try { const last = vi.fn() const registry = new ShutdownRegistry() registry.register({ name: 'last', dispose: last }) registry.register({ name: 'hangs', dispose: () => new Promise(() => undefined) }) const running = registry.disposeAll(500) await vi.advanceTimersByTimeAsync(501) const report = await running expect(report.timedOut).toEqual(['hangs', 'last']) // 기다리진 않지만 동기 구간(프로세스 kill 등)은 실행된다 expect(last).toHaveBeenCalledTimes(1) } finally { vi.useRealTimers() } }) it('등록 취소한 항목은 정리하지 않는다', async () => { const dispose = vi.fn() const registry = new ShutdownRegistry() const unregister = registry.register({ name: 'x', dispose }) unregister() await registry.disposeAll(1000) expect(dispose).not.toHaveBeenCalled() }) })