Openstatus www.openstatus.dev
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 193 lines 5.6 kB view raw
1import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test"; 2import { selectNotificationSchema } from "@openstatus/db/src/schema"; 3import { COLORS } from "@openstatus/notification-base"; 4import { 5 sendAlert, 6 sendDegraded, 7 sendRecovery, 8 sendTestSlackMessage, 9} from "./index"; 10 11describe("Slack Notifications", () => { 12 // biome-ignore lint/suspicious/noExplicitAny: <explanation> 13 let fetchMock: any = undefined; 14 15 beforeEach(() => { 16 // @ts-expect-error 17 fetchMock = spyOn(global, "fetch").mockImplementation(() => 18 Promise.resolve(new Response(null, { status: 200 })), 19 ); 20 }); 21 22 afterEach(() => { 23 if (fetchMock) { 24 fetchMock.mockRestore(); 25 } 26 }); 27 28 const createMockMonitor = () => ({ 29 id: "monitor-1", 30 name: "API Health Check", 31 url: "https://api.example.com/health", 32 jobType: "http" as const, 33 periodicity: "5m" as const, 34 status: "active" as const, 35 createdAt: new Date(), 36 updatedAt: new Date(), 37 region: "us-east-1", 38 }); 39 40 const createMockNotification = () => ({ 41 id: 1, 42 name: "Slack Notification", 43 provider: "slack", 44 workspaceId: 1, 45 createdAt: new Date(), 46 updatedAt: new Date(), 47 data: '{"slack":"https://hooks.slack.com/services/url"}', 48 }); 49 50 test("Send Alert", async () => { 51 const monitor = createMockMonitor(); 52 const notification = selectNotificationSchema.parse( 53 createMockNotification(), 54 ); 55 56 await sendAlert({ 57 // @ts-expect-error 58 monitor, 59 notification, 60 statusCode: 500, 61 message: "Something went wrong", 62 cronTimestamp: Date.now(), 63 }); 64 65 expect(fetchMock).toHaveBeenCalledTimes(1); 66 const callArgs = fetchMock.mock.calls[0]; 67 expect(callArgs[0]).toBe("https://hooks.slack.com/services/url"); 68 expect(callArgs[1].method).toBe("POST"); 69 70 const body = JSON.parse(callArgs[1].body); 71 expect(body.attachments).toBeDefined(); 72 expect(body.attachments[0].color).toBe(COLORS.red); 73 expect(body.attachments[0].blocks).toBeDefined(); 74 expect(body.attachments[0].blocks.length).toBeGreaterThan(0); 75 expect(body.attachments[0].blocks[0].text.text).toContain("is failing"); 76 }); 77 78 test("Send Alert without statusCode", async () => { 79 const monitor = createMockMonitor(); 80 const notification = selectNotificationSchema.parse( 81 createMockNotification(), 82 ); 83 84 await sendAlert({ 85 // @ts-expect-error 86 monitor, 87 notification, 88 message: "Connection timeout", 89 cronTimestamp: Date.now(), 90 }); 91 92 expect(fetchMock).toHaveBeenCalledTimes(1); 93 const callArgs = fetchMock.mock.calls[0]; 94 const body = JSON.parse(callArgs[1].body); 95 expect(body.attachments[0].color).toBe(COLORS.red); 96 expect(body.attachments[0].blocks[3].fields[0].text).toContain("Unknown"); 97 }); 98 99 test("Send Recovery", async () => { 100 const monitor = createMockMonitor(); 101 const notification = selectNotificationSchema.parse( 102 createMockNotification(), 103 ); 104 105 await sendRecovery({ 106 // @ts-expect-error 107 monitor, 108 notification, 109 statusCode: 200, 110 message: "Service recovered", 111 cronTimestamp: Date.now(), 112 }); 113 114 expect(fetchMock).toHaveBeenCalledTimes(1); 115 const callArgs = fetchMock.mock.calls[0]; 116 const body = JSON.parse(callArgs[1].body); 117 expect(body.attachments).toBeDefined(); 118 expect(body.attachments[0].color).toBe(COLORS.green); 119 expect(body.attachments[0].blocks[0].text.text).toContain("is recovered"); 120 }); 121 122 test("Send Degraded", async () => { 123 const monitor = createMockMonitor(); 124 const notification = selectNotificationSchema.parse( 125 createMockNotification(), 126 ); 127 128 await sendDegraded({ 129 // @ts-expect-error 130 monitor, 131 notification, 132 statusCode: 503, 133 message: "Service degraded", 134 cronTimestamp: Date.now(), 135 }); 136 137 expect(fetchMock).toHaveBeenCalledTimes(1); 138 const callArgs = fetchMock.mock.calls[0]; 139 const body = JSON.parse(callArgs[1].body); 140 expect(body.attachments).toBeDefined(); 141 expect(body.attachments[0].color).toBe(COLORS.yellow); 142 expect(body.attachments[0].blocks[0].text.text).toContain("is degraded"); 143 }); 144 145 test("Send Test Slack Message", async () => { 146 const webhookUrl = "https://hooks.slack.com/services/test/url"; 147 148 await sendTestSlackMessage(webhookUrl); 149 150 expect(fetchMock).toHaveBeenCalledTimes(1); 151 const callArgs = fetchMock.mock.calls[0]; 152 expect(callArgs[0]).toBe(webhookUrl); 153 154 const body = JSON.parse(callArgs[1].body); 155 expect(body.attachments[0].blocks[0].text.text).toContain( 156 "Test Notification", 157 ); 158 }); 159 160 test("Send Test Slack Message throws error on empty webhookUrl", async () => { 161 fetchMock.mockImplementation(() => 162 Promise.reject(new Error("Network error")), 163 ); 164 165 expect(sendTestSlackMessage("")).rejects.toThrow(); 166 expect(fetchMock).toHaveBeenCalledTimes(0); 167 }); 168 169 test("Handle fetch error gracefully", async () => { 170 fetchMock.mockImplementation(() => 171 Promise.reject(new Error("Network error")), 172 ); 173 174 const monitor = createMockMonitor(); 175 const notification = selectNotificationSchema.parse( 176 createMockNotification(), 177 ); 178 179 // Should not throw - function catches errors internally 180 expect( 181 sendAlert({ 182 // @ts-expect-error 183 monitor, 184 notification, 185 statusCode: 500, 186 message: "Error", 187 cronTimestamp: Date.now(), 188 }), 189 ).rejects.toThrow(); 190 191 expect(fetchMock).toHaveBeenCalledTimes(1); 192 }); 193});