Openstatus
www.openstatus.dev
1import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
2import { sendTest } from "./index";
3
4describe("Webhook sendTest", () => {
5 // biome-ignore lint/suspicious/noExplicitAny: <explanation>
6 let fetchMock: any = undefined;
7
8 beforeEach(() => {
9 fetchMock = spyOn(global, "fetch");
10 });
11
12 afterEach(() => {
13 if (fetchMock) {
14 fetchMock.mockClear();
15 fetchMock.mockRestore();
16 }
17 });
18
19 test("should send test webhook successfully", async () => {
20 const url = "https://example.com/webhook";
21 fetchMock.mockResolvedValue(new Response(null, { status: 200 }));
22
23 const result = await sendTest({ url });
24
25 expect(result).toBe(true);
26 expect(fetchMock).toHaveBeenCalledTimes(1);
27 expect(fetchMock).toHaveBeenCalledWith(
28 url,
29 expect.objectContaining({
30 method: "post",
31 body: expect.any(String),
32 headers: { "Content-Type": "application/json" },
33 }),
34 );
35
36 const callArgs = fetchMock.mock.calls[0];
37 const body = JSON.parse(callArgs[1].body);
38 expect(body).toMatchObject({
39 monitor: {
40 id: 1,
41 name: "test",
42 url: "http://openstat.us",
43 },
44 status: "recovered",
45 statusCode: 200,
46 latency: 1337,
47 });
48 expect(body.cronTimestamp).toBeTypeOf("number");
49 });
50
51 test("should send test webhook with headers", async () => {
52 const url = "https://example.com/webhook";
53 const headers = [
54 { key: "Authorization", value: "Bearer token123" },
55 { key: "Content-Type", value: "application/json" },
56 ];
57 fetchMock.mockResolvedValue(new Response(null, { status: 200 }));
58
59 const result = await sendTest({ url, headers });
60
61 expect(result).toBe(true);
62 expect(fetchMock).toHaveBeenCalledTimes(1);
63 expect(fetchMock).toHaveBeenCalledWith(
64 url,
65 expect.objectContaining({
66 method: "post",
67 body: expect.any(String),
68 headers: {
69 Authorization: "Bearer token123",
70 "Content-Type": "application/json",
71 },
72 }),
73 );
74 });
75
76 test("should throw error when response is not ok", async () => {
77 const url = "https://example.com/webhook";
78 fetchMock.mockResolvedValue(new Response(null, { status: 400 }));
79
80 await expect(sendTest({ url })).rejects.toThrow("Failed to send test");
81 expect(fetchMock).toHaveBeenCalledTimes(1);
82 });
83
84 test("should throw error when fetch fails", async () => {
85 const url = "https://example.com/webhook";
86 const networkError = new Error("Network error");
87 fetchMock.mockRejectedValue(networkError);
88
89 await expect(sendTest({ url })).rejects.toThrow("Failed to send test");
90 expect(fetchMock).toHaveBeenCalledTimes(1);
91 });
92
93 test("should send test webhook with empty headers array", async () => {
94 const url = "https://example.com/webhook";
95 const headers: { key: string; value: string }[] = [];
96 fetchMock.mockResolvedValue(new Response(null, { status: 200 }));
97
98 const result = await sendTest({ url, headers });
99
100 expect(result).toBe(true);
101 expect(fetchMock).toHaveBeenCalledTimes(1);
102 const callArgs = fetchMock.mock.calls[0];
103 // Empty headers array should result in empty object after transformHeaders
104 expect(callArgs[1].headers).toEqual({});
105 });
106
107 test("should send test webhook with 500 status code", async () => {
108 const url = "https://example.com/webhook";
109 fetchMock.mockResolvedValue(new Response(null, { status: 500 }));
110
111 await expect(sendTest({ url })).rejects.toThrow("Failed to send test");
112 expect(fetchMock).toHaveBeenCalledTimes(1);
113 });
114
115 test("should send test webhook with 201 status code (success)", async () => {
116 const url = "https://example.com/webhook";
117 fetchMock.mockResolvedValue(new Response(null, { status: 201 }));
118
119 const result = await sendTest({ url });
120
121 expect(result).toBe(true);
122 expect(fetchMock).toHaveBeenCalledTimes(1);
123 });
124});