government-email-filtering.test.js
1 #!/usr/bin/env node 2 3 /** 4 * Tests for government email filtering 5 * Ensures we don't send outreach to government email addresses 6 */ 7 8 import { test, mock } from 'node:test'; 9 import assert from 'node:assert/strict'; 10 import { fileURLToPath } from 'url'; 11 import { dirname } from 'path'; 12 13 // Mock LLM provider BEFORE importing modules that use it 14 const callLLMMock = mock.fn(); 15 const getProviderMock = mock.fn(() => 'openrouter'); 16 const getProviderDisplayNameMock = mock.fn(() => 'OpenRouter'); 17 18 mock.module('../../src/utils/llm-provider.js', { 19 namedExports: { 20 callLLM: callLLMMock, 21 getProvider: getProviderMock, 22 getProviderDisplayName: getProviderDisplayNameMock, 23 }, 24 }); 25 26 mock.module('../../src/utils/llm-usage-tracker.js', { 27 namedExports: { 28 logLLMUsage: mock.fn(async () => {}), 29 }, 30 }); 31 32 // Now import modules 33 import { getAllContacts } from '../../src/contacts/prioritize.js'; 34 import { generateProposalVariants } from '../../src/proposal-generator-v2.js'; 35 36 const __filename = fileURLToPath(import.meta.url); 37 const __dirname = dirname(__filename); 38 39 test('Government email detection patterns', () => { 40 // This is testing the internal function, so we'll recreate it here 41 function isGovernmentEmail(email) { 42 if (!email || typeof email !== 'string') return false; 43 44 const lower = email.toLowerCase().trim(); 45 const govPatterns = [ 46 /\.gov$/i, 47 /\.gov\.[a-z]{2}$/i, 48 /\.gc\.ca$/i, 49 /\.govt\.nz$/i, 50 /\.gob\.[a-z]{2}$/i, 51 /\.gouv\.[a-z]{2}$/i, 52 /\.go\.[a-z]{2}$/i, 53 /\.gov\.br$/i, 54 /\.mil$/i, 55 /\.mil\.[a-z]{2}$/i, 56 ]; 57 58 const domain = lower.split('@')[1]; 59 if (!domain) return false; 60 61 return govPatterns.some(pattern => pattern.test(domain)); 62 } 63 64 // Test US federal government emails 65 assert.equal(isGovernmentEmail('john@example.gov'), true, 'Should detect .gov'); 66 assert.equal(isGovernmentEmail('jane@whitehouse.gov'), true, 'Should detect .gov'); 67 68 // Test country-specific government emails 69 assert.equal(isGovernmentEmail('alice@example.gov.au'), true, 'Should detect .gov.au'); 70 assert.equal(isGovernmentEmail('bob@example.gov.uk'), true, 'Should detect .gov.uk'); 71 assert.equal(isGovernmentEmail('charlie@example.gov.in'), true, 'Should detect .gov.in'); 72 73 // Test Canada 74 assert.equal(isGovernmentEmail('dave@example.gc.ca'), true, 'Should detect .gc.ca'); 75 76 // Test New Zealand 77 assert.equal(isGovernmentEmail('eve@example.govt.nz'), true, 'Should detect .govt.nz'); 78 79 // Test Spanish-speaking countries 80 assert.equal(isGovernmentEmail('frank@example.gob.mx'), true, 'Should detect .gob.mx'); 81 assert.equal(isGovernmentEmail('grace@example.gob.es'), true, 'Should detect .gob.es'); 82 83 // Test French-speaking countries 84 assert.equal(isGovernmentEmail('henri@example.gouv.fr'), true, 'Should detect .gouv.fr'); 85 86 // Test Asian countries 87 assert.equal(isGovernmentEmail('yuki@example.go.jp'), true, 'Should detect .go.jp'); 88 assert.equal(isGovernmentEmail('kim@example.go.kr'), true, 'Should detect .go.kr'); 89 90 // Test Brazil 91 assert.equal(isGovernmentEmail('maria@example.gov.br'), true, 'Should detect .gov.br'); 92 93 // Test military domains 94 assert.equal(isGovernmentEmail('soldier@example.mil'), true, 'Should detect .mil'); 95 assert.equal(isGovernmentEmail('officer@example.mil.au'), true, 'Should detect .mil.au'); 96 97 // Test non-government emails (should return false) 98 assert.equal(isGovernmentEmail('user@example.com'), false, 'Should not detect .com'); 99 assert.equal(isGovernmentEmail('admin@company.com.au'), false, 'Should not detect .com.au'); 100 assert.equal(isGovernmentEmail('info@business.co.uk'), false, 'Should not detect .co.uk'); 101 assert.equal(isGovernmentEmail('contact@service.org'), false, 'Should not detect .org'); 102 103 // Test edge cases 104 assert.equal(isGovernmentEmail(''), false, 'Should handle empty string'); 105 assert.equal(isGovernmentEmail(null), false, 'Should handle null'); 106 assert.equal(isGovernmentEmail(undefined), false, 'Should handle undefined'); 107 assert.equal(isGovernmentEmail('invalid-email'), false, 'Should handle invalid email'); 108 }); 109 110 test('getAllContacts filters out government emails', () => { 111 const contactsJson = { 112 email_addresses: [ 113 'john@example.com', // Normal email - should be included 114 'admin@agency.gov', // US gov - should be filtered 115 'alice@department.gov.au', // AU gov - should be filtered 116 'support@business.co.uk', // Normal email - should be included 117 ], 118 }; 119 120 const contacts = getAllContacts(contactsJson, 'AU'); 121 122 // Should only have 2 non-government emails 123 const emailContacts = contacts.filter(c => c.channel === 'email'); 124 assert.equal(emailContacts.length, 2, 'Should have 2 non-government emails'); 125 126 // Verify the correct emails were kept 127 const emailAddresses = emailContacts.map(c => c.uri); 128 assert.ok(emailAddresses.includes('john@example.com'), 'Should include normal .com email'); 129 assert.ok( 130 emailAddresses.includes('support@business.co.uk'), 131 'Should include normal .co.uk email' 132 ); 133 assert.ok(!emailAddresses.includes('admin@agency.gov'), 'Should filter .gov email'); 134 assert.ok(!emailAddresses.includes('alice@department.gov.au'), 'Should filter .gov.au email'); 135 }); 136 137 test('government emails filtered from getAllContacts contact extraction', () => { 138 // Test that getAllContacts (prioritize.js) filters gov emails during extraction. 139 // This is the actual behavior - gov emails never reach proposal/outreach storage. 140 const contactData = { 141 email_addresses: [ 142 'contact@test-agency.gov.au', // Government email - should be filtered 143 'info@contractor.com', // Normal contractor email - should be included 144 ], 145 }; 146 147 const contacts = getAllContacts(contactData, 'AU'); 148 149 // Government email should be filtered out 150 const govContact = contacts.find(c => c.uri === 'contact@test-agency.gov.au'); 151 assert.equal( 152 govContact, 153 undefined, 154 'Government email should be filtered during contact extraction' 155 ); 156 157 // Normal contractor email should be included 158 const normalContact = contacts.find(c => c.uri === 'info@contractor.com'); 159 assert.ok(normalContact, 'Normal contractor email should be included in contacts'); 160 assert.equal(normalContact.channel, 'email', 'Contact channel should be email'); 161 });