import { describe, expect, test } from 'bun:test'; import { failingKeysFromFindings } from '../alerting/builtin-monitors'; import { ancestorKeysFor, machineAlertKey } from '../alerting/suppression'; import { auditMachinesReachable } from './machines-reachable'; describe('auditMachinesReachable', () => { test('no findings when every machine is reachable', async () => { const result = await auditMachinesReachable({ results: [ { hostname: 'iot', ipAddress: '10.0.0.10', reachable: true }, { hostname: 'dns-ext', ipAddress: '203.0.113.5', reachable: true }, ], }); expect(result).toEqual([]); }); test('per-machine drift finding for one unreachable host', async () => { const result = await auditMachinesReachable({ results: [ { hostname: 'iot', ipAddress: '10.0.0.10', reachable: true }, { hostname: 'dns-ext', ipAddress: '203.0.113.5', reachable: false, message: 'Connection timed out', }, ], }); expect(result).toHaveLength(1); expect(result[0]).toMatchObject({ category: 'machines_reachable', severity: 'drift', code: 'machine_unreachable', // Hostname, not the DB UUID — this assertion encoded the #596 bug. subject: 'dns-ext', actionable: false, }); expect(result[0].message).toContain('dns-ext'); expect(result[0].details).toContain('Connection timed out'); expect(result[0].remediation).toContain('celilo machine remove dns-ext'); }); test('collapses to "all_machines_unreachable" when every machine fails', async () => { const result = await auditMachinesReachable({ results: [ { hostname: 'iot', ipAddress: '10.0.0.10', reachable: false, message: 'host down', }, { hostname: 'dns-ext', ipAddress: '203.0.113.5', reachable: false, message: 'host down', }, ], }); expect(result).toHaveLength(1); expect(result[0]).toMatchObject({ category: 'machines_reachable', severity: 'drift', code: 'all_machines_unreachable', subject: 'system', actionable: false, }); expect(result[0].message).toContain('All 2 machines unreachable'); }); /** * celilo#596. The finding's subject becomes the alert key, and suppression * resolves a machine's ancestor key from its HOSTNAME. Subjecting on the DB * UUID produced a key nothing could ever match, so an unreachable machine * suppressed nothing and every module on it paged independently — the exact * cascade suppression exists to prevent. * * The prefix-only assertion in `e2e/tests/alert-ack-return-leg.test.ts` * (`toContain('builtin:machines_reachable/machine:')`) passes for either * value, which is why this survived. These assert the WHOLE key. */ describe('the alert key is one suppression can match (#596)', () => { test('producer and consumer derive the same key', async () => { const findings = await auditMachinesReachable({ results: [ { hostname: 'iot', ipAddress: '10.0.0.10', reachable: true }, { hostname: 'dns-ext', ipAddress: '203.0.113.5', reachable: false, message: 'Connection timed out', }, ], }); const keys = failingKeysFromFindings('machines_reachable', findings, 'warning').map( (k) => k.key, ); expect(keys).toEqual(['builtin:machines_reachable/machine:dns-ext']); // The identity that actually matters: asserted against the consumer's own // constructor rather than a second literal, so the two cannot drift apart // while both still look right. expect(keys[0]).toBe(machineAlertKey('dns-ext')); }); test('an unreachable machine suppresses a module deployed on it', async () => { const findings = await auditMachinesReachable({ results: [ { hostname: 'iot', ipAddress: '10.0.0.10', reachable: true }, { hostname: 'dns-ext', ipAddress: '203.0.113.5', reachable: false, message: 'down' }, ], }); const firing = failingKeysFromFindings('machines_reachable', findings, 'warning').map( (k) => k.key, ); const ancestors = ancestorKeysFor('module:homebridge/check:service_running', { moduleSystems: [ { moduleId: 'homebridge', hostname: 'dns-ext', zone: 'internal', infraType: 'machine' }, ], zoneProviders: [], }); // The behaviour suppression.ts documents: the machine's own alert is what // explains the module's. Before the fix the intersection was empty. expect(ancestors.some((a) => firing.includes(a))).toBe(true); }); }); test('does NOT collapse when only one machine is in the pool', async () => { // A single machine failing is per-machine, not a system-wide signal. const result = await auditMachinesReachable({ results: [ { hostname: 'iot', ipAddress: '10.0.0.10', reachable: false, message: 'down', }, ], }); expect(result).toHaveLength(1); expect(result[0].code).toBe('machine_unreachable'); }); });