diff --git a/packages/plugin-whatsapp/__tests__/client.test.ts b/packages/plugin-whatsapp/__tests__/client.test.ts new file mode 100644 index 00000000000..96ed53f5778 --- /dev/null +++ b/packages/plugin-whatsapp/__tests__/client.test.ts @@ -0,0 +1,120 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import axios from 'axios'; +import { WhatsAppClient } from '../src/client'; +import { WhatsAppConfig, WhatsAppMessage } from '../src/types'; + +vi.mock('axios', () => { + const mockPost = vi.fn(); + return { + default: { + create: () => ({ + post: mockPost + }) + } + }; +}); + +describe('WhatsAppClient', () => { + let client: WhatsAppClient; + let mockPost: any; + + const mockConfig: WhatsAppConfig = { + accessToken: 'test-token', + phoneNumberId: 'test-phone-id', + webhookVerifyToken: 'test-webhook-token', + businessAccountId: 'test-business-id' + }; + + beforeEach(() => { + vi.clearAllMocks(); + client = new WhatsAppClient(mockConfig); + mockPost = (axios.create() as any).post; + }); + + describe('sendMessage', () => { + it('should send a text message correctly', async () => { + const mockMessage: WhatsAppMessage = { + type: 'text', + to: '1234567890', + content: 'Hello, World!' + }; + + const expectedPayload = { + messaging_product: 'whatsapp', + recipient_type: 'individual', + to: mockMessage.to, + type: mockMessage.type, + text: { body: mockMessage.content } + }; + + const mockResponse = { data: { message_id: 'test-id' } }; + mockPost.mockResolvedValue(mockResponse); + + const response = await client.sendMessage(mockMessage); + + expect(mockPost).toHaveBeenCalledWith(`/${mockConfig.phoneNumberId}/messages`, expectedPayload); + expect(response).toEqual(mockResponse); + }); + + it('should send a template message correctly', async () => { + const mockMessage: WhatsAppMessage = { + type: 'template', + to: '1234567890', + content: { + name: 'test_template', + language: { + code: 'en' + }, + components: [{ + type: 'body', + parameters: [{ + type: 'text', + text: 'Test Parameter' + }] + }] + } + }; + + const expectedPayload = { + messaging_product: 'whatsapp', + recipient_type: 'individual', + to: mockMessage.to, + type: mockMessage.type, + template: mockMessage.content + }; + + const mockResponse = { data: { message_id: 'test-id' } }; + mockPost.mockResolvedValue(mockResponse); + + const response = await client.sendMessage(mockMessage); + + expect(mockPost).toHaveBeenCalledWith(`/${mockConfig.phoneNumberId}/messages`, expectedPayload); + expect(response).toEqual(mockResponse); + }); + + it('should handle API errors correctly', async () => { + const mockMessage: WhatsAppMessage = { + type: 'text', + to: '1234567890', + content: 'Hello, World!' + }; + + const mockError = new Error('API Error'); + mockPost.mockRejectedValue(mockError); + + await expect(client.sendMessage(mockMessage)).rejects.toThrow('API Error'); + }); + }); + + describe('verifyWebhook', () => { + it('should verify webhook token correctly', async () => { + const result = await client.verifyWebhook(mockConfig.webhookVerifyToken!); + expect(result).toBe(true); + }); + + it('should reject invalid webhook token', async () => { + const result = await client.verifyWebhook('invalid-token'); + expect(result).toBe(false); + }); + }); +}); diff --git a/packages/plugin-whatsapp/__tests__/handlers/message.handler.test.ts b/packages/plugin-whatsapp/__tests__/handlers/message.handler.test.ts new file mode 100644 index 00000000000..abbf0c8760b --- /dev/null +++ b/packages/plugin-whatsapp/__tests__/handlers/message.handler.test.ts @@ -0,0 +1,67 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { MessageHandler } from '../../src/handlers/message.handler'; +import { WhatsAppClient } from '../../src/client'; +import { WhatsAppMessage } from '../../src/types'; + +describe('MessageHandler', () => { + let messageHandler: MessageHandler; + let mockClient: WhatsAppClient; + + beforeEach(() => { + mockClient = { + sendMessage: vi.fn(), + } as any as WhatsAppClient; + + messageHandler = new MessageHandler(mockClient); + }); + + it('should successfully send a message', async () => { + const mockMessage: WhatsAppMessage = { + type: 'text', + to: '1234567890', + content: 'Test message' + }; + + const mockResponse = { + messaging_product: 'whatsapp', + contacts: [{ input: '1234567890', wa_id: 'WHATSAPP_ID' }], + messages: [{ id: 'MESSAGE_ID' }] + }; + + (mockClient.sendMessage as any).mockResolvedValue({ data: mockResponse }); + + const result = await messageHandler.send(mockMessage); + + expect(mockClient.sendMessage).toHaveBeenCalledWith(mockMessage); + expect(result).toEqual(mockResponse); + }); + + it('should handle client errors with error message', async () => { + const mockMessage: WhatsAppMessage = { + type: 'text', + to: '1234567890', + content: 'Test message' + }; + + const errorMessage = 'API Error'; + (mockClient.sendMessage as any).mockRejectedValue(new Error(errorMessage)); + + await expect(messageHandler.send(mockMessage)) + .rejects + .toThrow(`Failed to send WhatsApp message: ${errorMessage}`); + }); + + it('should handle unknown errors', async () => { + const mockMessage: WhatsAppMessage = { + type: 'text', + to: '1234567890', + content: 'Test message' + }; + + (mockClient.sendMessage as any).mockRejectedValue('Unknown error'); + + await expect(messageHandler.send(mockMessage)) + .rejects + .toThrow('Failed to send WhatsApp message'); + }); +}); diff --git a/packages/plugin-whatsapp/__tests__/handlers/webhook.handler.test.ts b/packages/plugin-whatsapp/__tests__/handlers/webhook.handler.test.ts new file mode 100644 index 00000000000..92b2e4ad848 --- /dev/null +++ b/packages/plugin-whatsapp/__tests__/handlers/webhook.handler.test.ts @@ -0,0 +1,134 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { WebhookHandler } from '../../src/handlers/webhook.handler'; +import { WhatsAppClient } from '../../src/client'; +import { WhatsAppWebhookEvent } from '../../src/types'; + +describe('WebhookHandler', () => { + let webhookHandler: WebhookHandler; + let mockClient: WhatsAppClient; + let consoleSpy: any; + + beforeEach(() => { + mockClient = {} as WhatsAppClient; + webhookHandler = new WebhookHandler(mockClient); + consoleSpy = vi.spyOn(console, 'log'); + }); + + afterEach(() => { + consoleSpy.mockRestore(); + }); + + it('should handle message events correctly', async () => { + const mockMessage = { + from: '1234567890', + id: 'msg_id', + timestamp: '1234567890', + text: { + body: 'Test message' + } + }; + + const mockEvent: WhatsAppWebhookEvent = { + object: 'whatsapp_business_account', + entry: [{ + id: 'BUSINESS_ID', + changes: [{ + value: { + messaging_product: 'whatsapp', + metadata: { + display_phone_number: '1234567890', + phone_number_id: 'PHONE_ID' + }, + messages: [mockMessage] + } + }] + }] + }; + + await webhookHandler.handle(mockEvent); + + expect(consoleSpy).toHaveBeenCalledWith('Received message:', mockMessage); + }); + + it('should handle status updates correctly', async () => { + const mockStatus = { + id: 'status_id', + status: 'delivered', + timestamp: '1234567890', + recipient_id: '1234567890' + }; + + const mockEvent: WhatsAppWebhookEvent = { + object: 'whatsapp_business_account', + entry: [{ + id: 'BUSINESS_ID', + changes: [{ + value: { + messaging_product: 'whatsapp', + metadata: { + display_phone_number: '1234567890', + phone_number_id: 'PHONE_ID' + }, + statuses: [mockStatus] + }, + field: '' + }] + }] + }; + + await webhookHandler.handle(mockEvent); + + expect(consoleSpy).toHaveBeenCalledWith('Received status update:', mockStatus); + }); + + it('should handle events with both messages and statuses', async () => { + const mockMessage = { + from: '1234567890', + id: 'msg_id', + timestamp: '1234567890', + text: { + body: 'Test message' + } + }; + + const mockStatus = { + id: 'status_id', + status: 'delivered', + timestamp: '1234567890', + recipient_id: '1234567890' + }; + + const mockEvent: WhatsAppWebhookEvent = { + object: 'whatsapp_business_account', + entry: [{ + id: 'BUSINESS_ID', + changes: [{ + value: { + messaging_product: 'whatsapp', + metadata: { + display_phone_number: '1234567890', + phone_number_id: 'PHONE_ID' + }, + messages: [mockMessage], + statuses: [mockStatus] + } + }] + }] + }; + + await webhookHandler.handle(mockEvent); + + expect(consoleSpy).toHaveBeenCalledWith('Received message:', mockMessage); + expect(consoleSpy).toHaveBeenCalledWith('Received status update:', mockStatus); + }); + + it('should handle errors correctly', async () => { + const mockEvent = {} as WhatsAppWebhookEvent; + + // The handler should not throw an error for an empty event + await expect(webhookHandler.handle(mockEvent)).resolves.not.toThrow(); + + // Verify that no messages or statuses were processed + expect(consoleSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/plugin-whatsapp/package.json b/packages/plugin-whatsapp/package.json index cf237cb4911..9b94b3ef081 100644 --- a/packages/plugin-whatsapp/package.json +++ b/packages/plugin-whatsapp/package.json @@ -22,7 +22,8 @@ "scripts": { "build": "tsup --format esm --dts", "dev": "tsup --format esm --dts --watch", - "test": "jest", + "test": "vitest run", + "coverage": "vitest run --coverage", "lint": "eslint --fix --cache ." }, "dependencies": { @@ -30,12 +31,10 @@ "axios": "1.7.8" }, "devDependencies": { - "@types/jest": "29.5.14", "@types/node": "20.17.9", "@typescript-eslint/eslint-plugin": "8.16.0", "@typescript-eslint/parser": "8.16.0", - "jest": "29.7.0", - "ts-jest": "29.2.5", - "typescript": "5.6.3" + "typescript": "5.6.3", + "vitest": "^1.2.1" } }