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, '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 + recordFired', () => { it('allows first fire', () => { const evaluator = new AlertEvaluator() expect(evaluator.shouldFire('rule-1', 15)).toBe(true) }) it('blocks during cooldown after recordFired', () => { const evaluator = new AlertEvaluator() expect(evaluator.shouldFire('rule-1', 15)).toBe(true) evaluator.recordFired('rule-1') expect(evaluator.shouldFire('rule-1', 15)).toBe(false) }) it('allows re-check if recordFired was not called', () => { const evaluator = new AlertEvaluator() expect(evaluator.shouldFire('rule-1', 15)).toBe(true) // Without recordFired, the cooldown is not active expect(evaluator.shouldFire('rule-1', 15)).toBe(true) }) it('different rules have independent cooldowns', () => { const evaluator = new AlertEvaluator() expect(evaluator.shouldFire('rule-1', 15)).toBe(true) evaluator.recordFired('rule-1') expect(evaluator.shouldFire('rule-2', 15)).toBe(true) }) it('allows fire after cooldown expires', () => { const evaluator = new AlertEvaluator() evaluator.recordFired('rule-1') // With 0-minute cooldown, immediate re-fire should be allowed expect(evaluator.shouldFire('rule-1', 0)).toBe(true) }) })