Skip to content

Commit f39b983

Browse files
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>
1 parent be77c64 commit f39b983

1 file changed

Lines changed: 177 additions & 0 deletions

File tree

tests/sendWebhook.test.js

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
const { sendWebhook } = require('../utils/utils');
2+
3+
describe('sendWebhook', () => {
4+
const mockBrowser = {
5+
tabs: {
6+
query: jest.fn(),
7+
},
8+
runtime: {
9+
getBrowserInfo: jest.fn(),
10+
getPlatformInfo: jest.fn(),
11+
},
12+
i18n: {
13+
getMessage: jest.fn((key, ...args) => `${key} ${args.join(' ')}`.trim()),
14+
},
15+
};
16+
17+
const originalFetch = global.fetch;
18+
const originalBrowser = global.browser;
19+
const originalConsoleError = console.error;
20+
21+
beforeEach(() => {
22+
global.fetch = jest.fn(() =>
23+
Promise.resolve({
24+
ok: true,
25+
json: () => Promise.resolve({}),
26+
})
27+
);
28+
global.browser = mockBrowser;
29+
console.error = jest.fn();
30+
31+
// Default mock implementations
32+
mockBrowser.tabs.query.mockResolvedValue([{
33+
id: 1,
34+
windowId: 1,
35+
index: 0,
36+
url: 'https://example.com',
37+
title: 'Example Domain',
38+
pinned: false,
39+
audible: false,
40+
mutedInfo: {},
41+
incognito: false,
42+
status: 'complete'
43+
}]);
44+
mockBrowser.runtime.getBrowserInfo.mockResolvedValue({ name: 'Firefox', version: '90.0' });
45+
mockBrowser.runtime.getPlatformInfo.mockResolvedValue({ os: 'linux', arch: 'x86-64' });
46+
mockBrowser.i18n.getMessage.mockClear();
47+
});
48+
49+
afterEach(() => {
50+
global.fetch = originalFetch;
51+
global.browser = originalBrowser;
52+
console.error = originalConsoleError;
53+
jest.clearAllMocks();
54+
});
55+
56+
test('should send test webhook', async () => {
57+
const webhook = { url: 'https://test.com' };
58+
await sendWebhook(webhook, true);
59+
60+
expect(global.fetch).toHaveBeenCalledWith(
61+
webhook.url,
62+
expect.objectContaining({
63+
method: 'POST',
64+
body: expect.stringContaining('"test":true'),
65+
})
66+
);
67+
});
68+
69+
test('should send real webhook with tab info', async () => {
70+
const webhook = { url: 'https://real.com' };
71+
await sendWebhook(webhook, false);
72+
73+
expect(mockBrowser.tabs.query).toHaveBeenCalled();
74+
expect(global.fetch).toHaveBeenCalledWith(
75+
webhook.url,
76+
expect.objectContaining({
77+
method: 'POST',
78+
body: expect.stringContaining('"title":"Example Domain"'),
79+
})
80+
);
81+
});
82+
83+
test('should include custom headers', async () => {
84+
const webhook = {
85+
url: 'https://headers.com',
86+
headers: [{ key: 'X-Custom', value: 'MyValue' }]
87+
};
88+
await sendWebhook(webhook, true);
89+
90+
expect(global.fetch).toHaveBeenCalledWith(
91+
expect.any(String),
92+
expect.objectContaining({
93+
headers: expect.objectContaining({
94+
'X-Custom': 'MyValue',
95+
'Content-Type': 'application/json'
96+
})
97+
})
98+
);
99+
});
100+
101+
test('should support GET method', async () => {
102+
const webhook = {
103+
url: 'https://get.com',
104+
method: 'GET'
105+
};
106+
await sendWebhook(webhook, true);
107+
108+
expect(global.fetch).toHaveBeenCalledWith(
109+
expect.stringContaining('https://get.com/?payload='),
110+
expect.objectContaining({
111+
method: 'GET',
112+
body: undefined
113+
})
114+
);
115+
});
116+
117+
test('should replace placeholders in custom payload', async () => {
118+
const webhook = {
119+
url: 'https://custom.com',
120+
customPayload: '{"text": "Page: {{tab.title}}", "id": {{tab.id}}}'
121+
};
122+
await sendWebhook(webhook, false);
123+
124+
expect(global.fetch).toHaveBeenCalledWith(
125+
expect.any(String),
126+
expect.objectContaining({
127+
body: JSON.stringify({
128+
text: "Page: Example Domain",
129+
id: 1
130+
})
131+
})
132+
);
133+
});
134+
135+
test('should handle string replacements in JSON values correctly', async () => {
136+
const webhook = {
137+
url: 'https://custom.com',
138+
customPayload: '{"browser": {{browser}}}'
139+
};
140+
await sendWebhook(webhook, false);
141+
142+
// The current implementation replaces {{browser}} with a stringified JSON string,
143+
// so the resulting payload has "browser": "{\"name\":...}"
144+
expect(global.fetch).toHaveBeenCalledWith(
145+
expect.any(String),
146+
expect.objectContaining({
147+
body: expect.stringContaining('"browser":"{\\"name\\":\\"Firefox\\",\\"version\\":\\"90.0\\"}"')
148+
})
149+
);
150+
});
151+
152+
test('should throw error if no active tab found', async () => {
153+
mockBrowser.tabs.query.mockResolvedValue([]);
154+
155+
await expect(sendWebhook({ url: 'https://fail.com' }, false))
156+
.rejects.toThrow('popupErrorNoActiveTab');
157+
});
158+
159+
test('should throw error on fetch failure', async () => {
160+
global.fetch.mockResolvedValue({
161+
ok: false,
162+
status: 500
163+
});
164+
165+
await expect(sendWebhook({ url: 'https://fail.com' }, true))
166+
.rejects.toThrow('popupErrorHttp 500');
167+
});
168+
169+
test('should throw error on invalid custom payload JSON', async () => {
170+
const webhook = {
171+
url: 'https://custom.com',
172+
customPayload: '{invalid_json}'
173+
};
174+
await expect(sendWebhook(webhook, false))
175+
.rejects.toThrow('popupErrorCustomPayloadJsonParseError');
176+
});
177+
});

0 commit comments

Comments
 (0)