compliance-validator-supplement.test.js
1 /** 2 * Supplement tests for src/utils/compliance-validator.js 3 * 4 * Tests validateCompliance() directly without mock.module. 5 * Covers: email channel checks, SMS channel checks, blocked channels, 6 * unknown country fallback, CAN-SPAM physical address check. 7 */ 8 9 import { test, describe } from 'node:test'; 10 import assert from 'node:assert/strict'; 11 12 // Set required env var before importing the module (CAN-SPAM physical address) 13 process.env.CAN_SPAM_PHYSICAL_ADDRESS = '123 Test St, Test City, State 12345'; 14 15 import { validateCompliance } from '../../src/utils/compliance-validator.js'; 16 17 // ─── validateCompliance ─────────────────────────────────────────────────────── 18 19 describe('validateCompliance', () => { 20 describe('email channel', () => { 21 test('returns ok for basic valid email proposal (AU)', () => { 22 const result = validateCompliance( 23 'Hi, I noticed your website could benefit from CRO improvements.', 24 'email', 25 'AU' 26 ); 27 assert.ok(typeof result === 'object'); 28 assert.ok('ok' in result); 29 assert.ok('blocked' in result); 30 }); 31 32 test('blocks email with RE: subject prefix', () => { 33 const proposal = 'Subject: RE: Your website\n\nHi there, this is about CRO.'; 34 const result = validateCompliance(proposal, 'email', 'AU'); 35 assert.equal(result.ok, false); 36 assert.equal(result.blocked, true); 37 assert.ok(result.reason.includes('RE:')); 38 }); 39 40 test('blocks email with FWD: subject prefix', () => { 41 const proposal = 'Subject: FWD: Check this out\n\nForwarded message.'; 42 const result = validateCompliance(proposal, 'email', 'AU'); 43 assert.equal(result.ok, false); 44 assert.equal(result.blocked, true); 45 }); 46 47 test('returns ok for valid subject line without RE:/FWD:', () => { 48 const result = validateCompliance( 49 'Subject: Website Opportunity for Your Business\n\nHi there.', 50 'email', 51 'NZ' 52 ); 53 // Should not block on subject alone (no CAN-SPAM addr required for NZ) 54 assert.ok(typeof result.ok === 'boolean'); 55 }); 56 57 test('handles null country code by defaulting to AU', () => { 58 assert.doesNotThrow(() => { 59 validateCompliance('Simple proposal text.', 'email', null); 60 }); 61 }); 62 63 test('handles undefined country code by defaulting to AU', () => { 64 assert.doesNotThrow(() => { 65 validateCompliance('Simple proposal text.', 'email', undefined); 66 }); 67 }); 68 }); 69 70 describe('sms channel', () => { 71 test('returns ok for short SMS proposal (AU)', () => { 72 const result = validateCompliance('Hi! Quick question about your website.', 'sms', 'AU'); 73 assert.ok(typeof result === 'object'); 74 assert.ok('ok' in result); 75 }); 76 77 test('modifiedText is null when no sender ID appended (AU does not require body sender ID)', () => { 78 const result = validateCompliance('Short SMS text here.', 'sms', 'AU'); 79 // Whether modifiedText is null depends on AU requirements — just check it doesn't throw 80 assert.ok(result.modifiedText === null || typeof result.modifiedText === 'string'); 81 }); 82 83 test('returns ok for US SMS with short text', () => { 84 const result = validateCompliance( 85 'Hi! I can help optimize your website. Reply STOP to opt out.', 86 'sms', 87 'US' 88 ); 89 assert.ok(typeof result.ok === 'boolean'); 90 }); 91 92 test('handles lowercase country code', () => { 93 assert.doesNotThrow(() => { 94 validateCompliance('Test SMS text.', 'sms', 'au'); 95 }); 96 }); 97 }); 98 99 describe('unknown channel', () => { 100 test('returns ok for unknown channel (no specific checks apply)', () => { 101 const result = validateCompliance('Some text.', 'form', 'AU'); 102 assert.equal(result.ok, true); 103 assert.equal(result.blocked, false); 104 }); 105 }); 106 107 describe('return shape', () => { 108 test('always returns ok, blocked, reason, modifiedText fields', () => { 109 const result = validateCompliance('Test text.', 'sms', 'AU'); 110 assert.ok('ok' in result, 'should have ok'); 111 assert.ok('blocked' in result, 'should have blocked'); 112 assert.ok('reason' in result, 'should have reason'); 113 assert.ok('modifiedText' in result, 'should have modifiedText'); 114 }); 115 116 test('reason is null when ok', () => { 117 const result = validateCompliance('Valid SMS text.', 'sms', 'AU'); 118 if (result.ok) { 119 assert.equal(result.reason, null); 120 } 121 }); 122 123 test('ok is false when blocked is true', () => { 124 const result = validateCompliance('Subject: RE: Test\n\nBody text.', 'email', 'AU'); 125 if (result.blocked) { 126 assert.equal(result.ok, false); 127 } 128 }); 129 }); 130 });