/ tests / utils / outreach-guard.test.js
outreach-guard.test.js
 1  import { test, describe, beforeEach } from 'node:test';
 2  import assert from 'node:assert/strict';
 3  import {
 4    recordOutreachError,
 5    shouldHaltChannel,
 6    getHaltedChannels,
 7    clearHalt,
 8    getChannelErrorStats,
 9  } from '../../src/utils/outreach-guard.js';
10  
11  // Reset guard state between tests by clearing all halts
12  function resetGuard() {
13    for (const ch of ['sms', 'email', 'form', 'x', 'linkedin']) {
14      clearHalt(ch);
15    }
16  }
17  
18  describe('outreach-guard', () => {
19    beforeEach(resetGuard);
20  
21    test('channel is not halted initially', () => {
22      assert.equal(shouldHaltChannel('sms'), false);
23    });
24  
25    test('channel not halted after fewer than 25 errors', () => {
26      for (let i = 0; i < 24; i++) {
27        recordOutreachError('sms', 'outside business hours');
28      }
29      assert.equal(shouldHaltChannel('sms'), false);
30    });
31  
32    test('channel halted after 25 of the same error', () => {
33      for (let i = 0; i < 25; i++) {
34        recordOutreachError('sms', 'outside business hours');
35      }
36      assert.equal(shouldHaltChannel('sms'), true);
37    });
38  
39    test('halting one channel does not halt another', () => {
40      for (let i = 0; i < 25; i++) {
41        recordOutreachError('sms', 'outside business hours');
42      }
43      assert.equal(shouldHaltChannel('email'), false);
44    });
45  
46    test('clearHalt removes the halt', () => {
47      for (let i = 0; i < 25; i++) {
48        recordOutreachError('sms', 'outside business hours');
49      }
50      assert.equal(shouldHaltChannel('sms'), true);
51      clearHalt('sms');
52      assert.equal(shouldHaltChannel('sms'), false);
53    });
54  
55    test('getHaltedChannels returns halted channel info', () => {
56      for (let i = 0; i < 25; i++) {
57        recordOutreachError('email', 'status code 429');
58      }
59      const halted = getHaltedChannels();
60      assert.ok('email' in halted);
61      assert.ok(halted.email.count >= 25);
62    });
63  
64    test('different errors on same channel do not aggregate', () => {
65      // 13 of error A and 12 of error B — neither should trigger
66      for (let i = 0; i < 13; i++) {
67        recordOutreachError('sms', 'error A message');
68      }
69      for (let i = 0; i < 12; i++) {
70        recordOutreachError('sms', 'error B message');
71      }
72      assert.equal(shouldHaltChannel('sms'), false);
73    });
74  
75    test('getChannelErrorStats returns top error per channel', () => {
76      for (let i = 0; i < 10; i++) {
77        recordOutreachError('sms', 'outside business hours');
78      }
79      const stats = getChannelErrorStats();
80      const smsStats = stats.find(s => s.channel === 'sms');
81      assert.ok(smsStats);
82      assert.ok(smsStats.count >= 10);
83    });
84  
85    test('error normalization groups similar errors', () => {
86      // These should all normalize to the same key
87      recordOutreachError('sms', 'Outreach #12345 failed: outside business hours');
88      recordOutreachError('sms', 'Outreach #67890 failed: outside business hours');
89      // Both should map to the same normalized string, so count as 2
90      const stats = getChannelErrorStats();
91      const smsStats = stats.find(s => s.channel === 'sms');
92      assert.ok(smsStats);
93      assert.ok(smsStats.count >= 2);
94    });
95  });