cms.c2sgmbh/tests/unit/monitoring/alert-evaluator.unit.spec.ts
2026-02-15 00:30:21 +00:00

100 lines
3.4 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import {
getMetricValue,
evaluateCondition,
AlertEvaluator,
} from '@/lib/monitoring/alert-evaluator'
describe('getMetricValue', () => {
it('resolves simple paths', () => {
const metrics = { system: { cpuUsagePercent: 92 } }
expect(getMetricValue(metrics, 'system.cpuUsagePercent')).toBe(92)
})
it('resolves deeply nested paths', () => {
const metrics = { services: { redis: { memoryUsedMB: 512 } } }
expect(getMetricValue(metrics, 'services.redis.memoryUsedMB')).toBe(512)
})
it('returns undefined for non-existent paths', () => {
const metrics = { system: { cpuUsagePercent: 92 } }
expect(getMetricValue(metrics, 'system.nonExistent')).toBeUndefined()
})
it('returns undefined for non-numeric values', () => {
const metrics = { system: { name: 'test' } }
expect(getMetricValue(metrics, 'system.name')).toBeUndefined()
})
it('returns undefined when traversing through null', () => {
const metrics = { system: null }
expect(getMetricValue(metrics as Record<string, unknown>, 'system.cpu')).toBeUndefined()
})
it('resolves top-level numeric values', () => {
const metrics = { uptime: 3600 }
expect(getMetricValue(metrics, 'uptime')).toBe(3600)
})
})
describe('evaluateCondition', () => {
it('gt: fires when value exceeds threshold', () => {
expect(evaluateCondition('gt', 92, 80)).toBe(true)
expect(evaluateCondition('gt', 80, 80)).toBe(false)
expect(evaluateCondition('gt', 45, 80)).toBe(false)
})
it('lt: fires when value is below threshold', () => {
expect(evaluateCondition('lt', 5, 10)).toBe(true)
expect(evaluateCondition('lt', 10, 10)).toBe(false)
expect(evaluateCondition('lt', 15, 10)).toBe(false)
})
it('gte: fires when value >= threshold', () => {
expect(evaluateCondition('gte', 80, 80)).toBe(true)
expect(evaluateCondition('gte', 81, 80)).toBe(true)
expect(evaluateCondition('gte', 79, 80)).toBe(false)
})
it('lte: fires when value <= threshold', () => {
expect(evaluateCondition('lte', 10, 10)).toBe(true)
expect(evaluateCondition('lte', 9, 10)).toBe(true)
expect(evaluateCondition('lte', 11, 10)).toBe(false)
})
it('eq: fires when value equals threshold', () => {
expect(evaluateCondition('eq', 100, 100)).toBe(true)
expect(evaluateCondition('eq', 99, 100)).toBe(false)
})
it('returns false for unknown condition', () => {
expect(evaluateCondition('unknown' as never, 50, 50)).toBe(false)
})
})
describe('AlertEvaluator.shouldFire', () => {
it('allows first fire', () => {
const evaluator = new AlertEvaluator()
expect(evaluator.shouldFire('rule-1', 15)).toBe(true)
})
it('blocks during cooldown', () => {
const evaluator = new AlertEvaluator()
expect(evaluator.shouldFire('rule-1', 15)).toBe(true)
expect(evaluator.shouldFire('rule-1', 15)).toBe(false)
})
it('different rules have independent cooldowns', () => {
const evaluator = new AlertEvaluator()
expect(evaluator.shouldFire('rule-1', 15)).toBe(true)
expect(evaluator.shouldFire('rule-2', 15)).toBe(true)
})
it('allows fire after cooldown expires', () => {
const evaluator = new AlertEvaluator()
expect(evaluator.shouldFire('rule-1', 0)).toBe(true)
// With 0-minute cooldown, immediate re-fire should be allowed
// (elapsed time > 0 which is >= 0)
expect(evaluator.shouldFire('rule-1', 0)).toBe(true)
})
})