From 980ed1c7e1f34c4cf4fb5ff8637191267cac1d05 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 25 Feb 2026 13:03:30 +0000 Subject: [PATCH] test: add comprehensive tests for sendWebhook function Added unit tests for `sendWebhook` in `tests/sendWebhook.test.js`. Tests cover: - Test mode and real mode payloads - Custom headers inclusion - HTTP GET and POST methods - Custom payload placeholder replacements - Error handling (no active tab, fetch error, JSON parse error) Verified by running `npm test`. All tests passed. Deleted temporary `bun.lock` to avoid committing unwanted lockfile changes. Verified `utils/utils.js` exports `sendWebhook` correctly. Co-authored-by: cmuench <211294+cmuench@users.noreply.github.com> --- tests/sendWebhook.test.js | 177 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 tests/sendWebhook.test.js diff --git a/tests/sendWebhook.test.js b/tests/sendWebhook.test.js new file mode 100644 index 0000000..f788d50 --- /dev/null +++ b/tests/sendWebhook.test.js @@ -0,0 +1,177 @@ +const { sendWebhook } = require('../utils/utils'); + +describe('sendWebhook', () => { + const mockBrowser = { + tabs: { + query: jest.fn(), + }, + runtime: { + getBrowserInfo: jest.fn(), + getPlatformInfo: jest.fn(), + }, + i18n: { + getMessage: jest.fn((key, ...args) => `${key} ${args.join(' ')}`.trim()), + }, + }; + + const originalFetch = global.fetch; + const originalBrowser = global.browser; + const originalConsoleError = console.error; + + beforeEach(() => { + global.fetch = jest.fn(() => + Promise.resolve({ + ok: true, + json: () => Promise.resolve({}), + }) + ); + global.browser = mockBrowser; + console.error = jest.fn(); + + // Default mock implementations + mockBrowser.tabs.query.mockResolvedValue([{ + id: 1, + windowId: 1, + index: 0, + url: 'https://example.com', + title: 'Example Domain', + pinned: false, + audible: false, + mutedInfo: {}, + incognito: false, + status: 'complete' + }]); + mockBrowser.runtime.getBrowserInfo.mockResolvedValue({ name: 'Firefox', version: '90.0' }); + mockBrowser.runtime.getPlatformInfo.mockResolvedValue({ os: 'linux', arch: 'x86-64' }); + mockBrowser.i18n.getMessage.mockClear(); + }); + + afterEach(() => { + global.fetch = originalFetch; + global.browser = originalBrowser; + console.error = originalConsoleError; + jest.clearAllMocks(); + }); + + test('should send test webhook', async () => { + const webhook = { url: 'https://test.com' }; + await sendWebhook(webhook, true); + + expect(global.fetch).toHaveBeenCalledWith( + webhook.url, + expect.objectContaining({ + method: 'POST', + body: expect.stringContaining('"test":true'), + }) + ); + }); + + test('should send real webhook with tab info', async () => { + const webhook = { url: 'https://real.com' }; + await sendWebhook(webhook, false); + + expect(mockBrowser.tabs.query).toHaveBeenCalled(); + expect(global.fetch).toHaveBeenCalledWith( + webhook.url, + expect.objectContaining({ + method: 'POST', + body: expect.stringContaining('"title":"Example Domain"'), + }) + ); + }); + + test('should include custom headers', async () => { + const webhook = { + url: 'https://headers.com', + headers: [{ key: 'X-Custom', value: 'MyValue' }] + }; + await sendWebhook(webhook, true); + + expect(global.fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + headers: expect.objectContaining({ + 'X-Custom': 'MyValue', + 'Content-Type': 'application/json' + }) + }) + ); + }); + + test('should support GET method', async () => { + const webhook = { + url: 'https://get.com', + method: 'GET' + }; + await sendWebhook(webhook, true); + + expect(global.fetch).toHaveBeenCalledWith( + expect.stringContaining('https://get.com/?payload='), + expect.objectContaining({ + method: 'GET', + body: undefined + }) + ); + }); + + test('should replace placeholders in custom payload', async () => { + const webhook = { + url: 'https://custom.com', + customPayload: '{"text": "Page: {{tab.title}}", "id": {{tab.id}}}' + }; + await sendWebhook(webhook, false); + + expect(global.fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + body: JSON.stringify({ + text: "Page: Example Domain", + id: 1 + }) + }) + ); + }); + + test('should handle string replacements in JSON values correctly', async () => { + const webhook = { + url: 'https://custom.com', + customPayload: '{"browser": {{browser}}}' + }; + await sendWebhook(webhook, false); + + // The current implementation replaces {{browser}} with a stringified JSON string, + // so the resulting payload has "browser": "{\"name\":...}" + expect(global.fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + body: expect.stringContaining('"browser":"{\\"name\\":\\"Firefox\\",\\"version\\":\\"90.0\\"}"') + }) + ); + }); + + test('should throw error if no active tab found', async () => { + mockBrowser.tabs.query.mockResolvedValue([]); + + await expect(sendWebhook({ url: 'https://fail.com' }, false)) + .rejects.toThrow('popupErrorNoActiveTab'); + }); + + test('should throw error on fetch failure', async () => { + global.fetch.mockResolvedValue({ + ok: false, + status: 500 + }); + + await expect(sendWebhook({ url: 'https://fail.com' }, true)) + .rejects.toThrow('popupErrorHttp 500'); + }); + + test('should throw error on invalid custom payload JSON', async () => { + const webhook = { + url: 'https://custom.com', + customPayload: '{invalid_json}' + }; + await expect(sendWebhook(webhook, false)) + .rejects.toThrow('popupErrorCustomPayloadJsonParseError'); + }); +});