diff --git a/src/workers/__tests__/sms-cluster-worker.test.ts b/src/workers/__tests__/sms-cluster-worker.test.ts index 8a1adc96..a4340225 100644 --- a/src/workers/__tests__/sms-cluster-worker.test.ts +++ b/src/workers/__tests__/sms-cluster-worker.test.ts @@ -1,5 +1,11 @@ import cluster from 'cluster'; -import { startSMSClusterWorker } from '../sms-cluster-worker'; +import { + startSMSClusterWorker, + sanitizeAndValidateSMS, + getSecurityEvents, + getQueueSize, + getRateLimitStatus +} from '../sms-cluster-worker'; jest.mock('cluster', () => ({ isPrimary: true, @@ -14,41 +20,172 @@ jest.mock('os', () => ({ describe('SMS Cluster Worker', () => { beforeEach(() => { jest.clearAllMocks(); + // Clear security events before each test + const events = getSecurityEvents(); + events.length = 0; }); - it('should fork a worker for each CPU if primary', () => { - // Override cluster.isPrimary just in case - Object.defineProperty(cluster, 'isPrimary', { value: true, configurable: true }); + describe('Cluster Management', () => { + it('should fork a worker for each CPU if primary', () => { + Object.defineProperty(cluster, 'isPrimary', { value: true, configurable: true }); - const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); + const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); - startSMSClusterWorker(); + startSMSClusterWorker(); - expect(cluster.fork).toHaveBeenCalledTimes(4); - expect(consoleSpy).toHaveBeenCalledWith( - expect.stringContaining('Setting up cluster with 4 workers'), - ); + expect(cluster.fork).toHaveBeenCalledTimes(4); + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining('Setting up cluster with 4 workers'), + ); - consoleSpy.mockRestore(); + consoleSpy.mockRestore(); + }); + + it('should bind an exit handler to auto-heal workers', () => { + Object.defineProperty(cluster, 'isPrimary', { value: true, configurable: true }); + + startSMSClusterWorker(); + + expect(cluster.on).toHaveBeenCalledWith('exit', expect.any(Function)); + }); + + it('should execute worker logic if not primary', () => { + Object.defineProperty(cluster, 'isPrimary', { value: false, configurable: true }); + const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); + + startSMSClusterWorker(); + + expect(cluster.fork).not.toHaveBeenCalled(); + expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Worker')); + + consoleSpy.mockRestore(); + }); + + it('should set worker resource limits', () => { + Object.defineProperty(cluster, 'isPrimary', { value: true, configurable: true }); + + startSMSClusterWorker(); + + expect(cluster.fork).toHaveBeenCalledWith( + expect.objectContaining({ + WORKER_ID: expect.any(Number), + NODE_OPTIONS: expect.stringContaining('--max-old-space-size=512'), + }) + ); + }); }); - it('should bind an exit handler to auto-heal workers', () => { - Object.defineProperty(cluster, 'isPrimary', { value: true, configurable: true }); + describe('Input Validation', () => { + it('should validate correct phone numbers', () => { + const result = sanitizeAndValidateSMS('+15551234567', 'Test message'); + expect(result.valid).toBe(true); + expect(result.sanitized).toBeDefined(); + }); + + it('should reject invalid phone number format', () => { + const result = sanitizeAndValidateSMS('5551234567', 'Test message'); + expect(result.valid).toBe(false); + expect(result.reason).toContain('Invalid phone number format'); + }); - startSMSClusterWorker(); + it('should reject phone numbers that are too long', () => { + const result = sanitizeAndValidateSMS('+155512345678901234567', 'Test message'); + expect(result.valid).toBe(false); + expect(result.reason).toContain('too long'); + }); - expect(cluster.on).toHaveBeenCalledWith('exit', expect.any(Function)); + it('should reject non-string phone numbers', () => { + const result = sanitizeAndValidateSMS(1234567890 as any, 'Test message'); + expect(result.valid).toBe(false); + expect(result.reason).toContain('must be a string'); + }); + + it('should reject empty messages', () => { + const result = sanitizeAndValidateSMS('+15551234567', ''); + expect(result.valid).toBe(false); + expect(result.reason).toContain('cannot be empty'); + }); + + it('should reject messages that are too long', () => { + const longMessage = 'a'.repeat(2000); + const result = sanitizeAndValidateSMS('+15551234567', longMessage); + expect(result.valid).toBe(false); + expect(result.reason).toContain('too long'); + }); + + it('should sanitize message content', () => { + const result = sanitizeAndValidateSMS('+15551234567', ''); + expect(result.valid).toBe(true); + expect(result.sanitized?.message).not.toContain('