kin: auto-commit after pipeline

This commit is contained in:
Gros Frumos 2026-03-17 16:30:24 +02:00
parent 76a88714e4
commit 4bc421e117
5 changed files with 195 additions and 6 deletions

View file

@ -212,3 +212,81 @@ describe('KIN-099: toast auto-dismiss через 8 секунд', () => {
expect(wrapper.find('.border-red-700').exists()).toBe(true)
})
})
// ─────────────────────────────────────────────────────────────
// Критерий 5 (KIN-099 issue severity=medium):
// onUnmounted очищает setTimeout таймеры watchdog-тостов
// ─────────────────────────────────────────────────────────────
describe('KIN-099: onUnmounted очищает setTimeout таймеры watchdog-тостов', () => {
it('clearTimeout вызывается для активного таймера при unmount компонента', async () => {
vi.mocked(api.notifications).mockResolvedValue([
makeNotification('KIN-099', 'Process died unexpectedly (PID 12345)'),
])
const clearTimeoutSpy = vi.spyOn(globalThis, 'clearTimeout')
const wrapper = mount(EscalationBanner)
await flushPromises()
// Toast must be visible with an active auto-dismiss timer
expect(wrapper.find('.border-red-700').exists()).toBe(true)
wrapper.unmount()
// onUnmounted should have called clearTimeout for the toast timer
expect(clearTimeoutSpy).toHaveBeenCalled()
})
it('После unmount таймер авто-dismiss не срабатывает (нет ошибок)', async () => {
vi.mocked(api.notifications).mockResolvedValue([
makeNotification('KIN-050', 'Process died unexpectedly (PID 11111)'),
])
const wrapper = mount(EscalationBanner)
await flushPromises()
expect(wrapper.find('.border-red-700').exists()).toBe(true)
// Unmount before 8 second timer fires — timer should be cancelled
wrapper.unmount()
// Advance past 8 seconds — should not throw even though component is gone
vi.advanceTimersByTime(10000)
await flushPromises()
// Pass = no errors thrown after unmount
})
})
// ─────────────────────────────────────────────────────────────
// Критерий 6 (KIN-099 issue severity=low):
// localStorage kin_dismissed_watchdog_toasts — сохранение работает
// Лимит реализован в KIN-OBS-017 (WATCHDOG_MAX_STORED = 100)
// ─────────────────────────────────────────────────────────────
describe('KIN-099: localStorage dismissed watchdog — сохранение (лимит: KIN-OBS-017)', () => {
it('Несколько dismissed task_id корректно сохраняются в localStorage', async () => {
const notifications = ['KIN-051', 'KIN-052', 'KIN-053'].map(id =>
makeNotification(id, 'Process died unexpectedly (PID 1234)')
)
vi.mocked(api.notifications).mockResolvedValue(notifications)
const wrapper = mount(EscalationBanner)
await flushPromises()
// Dismiss all toasts one by one
for (let i = 0; i < 3; i++) {
const btn = wrapper.find('.border-red-700 button')
if (btn.exists()) await btn.trigger('click')
await flushPromises()
}
const stored = localStorageMock.getItem('kin_dismissed_watchdog_toasts')
expect(stored).toBeTruthy()
const parsed = JSON.parse(stored!)
expect(parsed).toContain('KIN-051')
expect(parsed).toContain('KIN-052')
expect(parsed).toContain('KIN-053')
// Size limit is capped at WATCHDOG_MAX_STORED (100) — only last 100 IDs stored
})
})

View file

@ -46,8 +46,10 @@ function loadDismissedWatchdog(): Set<string> {
}
}
const WATCHDOG_MAX_STORED = 100
function saveDismissedWatchdog(ids: Set<string>) {
localStorage.setItem(WATCHDOG_TOAST_KEY, JSON.stringify([...ids]))
localStorage.setItem(WATCHDOG_TOAST_KEY, JSON.stringify([...ids].slice(-WATCHDOG_MAX_STORED)))
}
const dismissedWatchdog = ref<Set<string>>(loadDismissedWatchdog())
@ -117,6 +119,10 @@ onMounted(async () => {
onUnmounted(() => {
if (pollTimer) clearInterval(pollTimer)
// KIN-099: clear watchdog toast auto-dismiss timers to prevent memory leaks
for (const toast of watchdogToasts.value) {
if (toast.timerId) clearTimeout(toast.timerId)
}
})
</script>