Openstatus www.openstatus.dev
6
fork

Configure Feed

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

add test (#1557)

authored by

Thibault Le Ouay and committed by
GitHub
30186467 9b47fa62

+447 -17
+1 -1
apps/server/package.json
··· 40 40 "devDependencies": { 41 41 "@openstatus/tsconfig": "workspace:*", 42 42 "@types/validator": "13.11.6", 43 - "bun-types": "1.0.11", 43 + "bun-types": "1.3.1", 44 44 "dotenv": "16.3.1" 45 45 } 46 46 }
+4
packages/notifications/email/package.json
··· 3 3 "version": "0.0.0", 4 4 "main": "src/index.ts", 5 5 "description": "Log drains Vercel integration.", 6 + "scripts": { 7 + "test": "bun test" 8 + }, 6 9 "dependencies": { 7 10 "@openstatus/db": "workspace:*", 8 11 "@openstatus/emails": "workspace:*", ··· 18 21 "devDependencies": { 19 22 "@openstatus/tsconfig": "workspace:*", 20 23 "@types/node": "22.10.2", 24 + "bun-types": "1.3.1", 21 25 "typescript": "5.7.2" 22 26 } 23 27 }
+119
packages/notifications/email/src/index.test.ts
··· 1 + import { 2 + describe, 3 + mock, 4 + // jest, 5 + test, 6 + } from "bun:test"; 7 + 8 + import { selectNotificationSchema } from "@openstatus/db/src/schema"; 9 + import { sendAlert, sendDegraded, sendRecovery } from "./index"; 10 + 11 + mock.module("@openstatus/emails/src/client", () => ({ 12 + EmailClient: mock(() => ({ 13 + sendMonitorAlert: mock(async () => {}), 14 + })), 15 + })); 16 + 17 + describe("Email Notifications", () => { 18 + test("Send degraded", async () => { 19 + const monitor = { 20 + id: "monitor-1", 21 + name: "API Health Check", 22 + url: "https://api.example.com/health", 23 + jobType: "http" as const, 24 + periodicity: "5m" as const, 25 + status: "active" as const, // or "down", "degraded" 26 + createdAt: new Date(), 27 + updatedAt: new Date(), 28 + region: "us-east-1", 29 + }; 30 + 31 + const a = { 32 + id: 1, 33 + name: "email Notification", 34 + provider: "email", 35 + workspaceId: 1, 36 + createdAt: new Date(), 37 + updatedAt: new Date(), 38 + data: '{"email":"ping@openstatus.dev"}', 39 + }; 40 + 41 + const n = selectNotificationSchema.parse(a); 42 + await sendDegraded({ 43 + // @ts-expect-error 44 + monitor, 45 + notification: n, 46 + statusCode: 500, 47 + message: "Something went wrong", 48 + cronTimestamp: Date.now(), 49 + }); 50 + }); 51 + 52 + test("Send Recovered", async () => { 53 + const monitor = { 54 + id: "monitor-1", 55 + name: "API Health Check", 56 + url: "https://api.example.com/health", 57 + jobType: "http" as const, 58 + periodicity: "5m" as const, 59 + status: "active" as const, // or "down", "degraded" 60 + createdAt: new Date(), 61 + updatedAt: new Date(), 62 + region: "us-east-1", 63 + }; 64 + 65 + const a = { 66 + id: 1, 67 + name: "Email Notification", 68 + provider: "email", 69 + workspaceId: 1, 70 + createdAt: new Date(), 71 + updatedAt: new Date(), 72 + data: '{"email":"ping@openstatus.dev"}', 73 + }; 74 + 75 + const n = selectNotificationSchema.parse(a); 76 + await sendRecovery({ 77 + // @ts-expect-error 78 + monitor, 79 + notification: n, 80 + statusCode: 500, 81 + message: "Something went wrong", 82 + cronTimestamp: Date.now(), 83 + }); 84 + }); 85 + 86 + test("Send Alert", async () => { 87 + const monitor = { 88 + id: "monitor-1", 89 + name: "API Health Check", 90 + url: "https://api.example.com/health", 91 + jobType: "http" as const, 92 + periodicity: "5m" as const, 93 + status: "active" as const, // or "down", "degraded" 94 + createdAt: new Date(), 95 + updatedAt: new Date(), 96 + region: "us-east-1", 97 + }; 98 + const a = { 99 + id: 1, 100 + name: "PagerDuty Notification", 101 + provider: "email", 102 + workspaceId: 1, 103 + createdAt: new Date(), 104 + updatedAt: new Date(), 105 + data: '{"email":"ping@openstatus.dev"}', 106 + }; 107 + 108 + const n = selectNotificationSchema.parse(a); 109 + 110 + await sendAlert({ 111 + // @ts-expect-error 112 + monitor, 113 + notification: n, 114 + statusCode: 500, 115 + message: "Something went wrong", 116 + cronTimestamp: Date.now(), 117 + }); 118 + }); 119 + });
+6 -2
packages/notifications/email/src/index.ts
··· 9 9 import { regionDict } from "@openstatus/regions"; 10 10 import { env } from "../env"; 11 11 12 - const emailClient = new EmailClient({ apiKey: env.RESEND_API_KEY }); 13 - 14 12 export const sendAlert = async ({ 15 13 monitor, 16 14 notification, ··· 29 27 region?: Region; 30 28 latency?: number; 31 29 }) => { 30 + const emailClient = new EmailClient({ apiKey: env.RESEND_API_KEY }); 31 + 32 32 const config = emailDataSchema.safeParse(JSON.parse(notification.data)); 33 33 34 34 if (!config.success) return; ··· 63 63 region?: Region; 64 64 latency?: number; 65 65 }) => { 66 + const emailClient = new EmailClient({ apiKey: env.RESEND_API_KEY }); 67 + 66 68 const config = emailDataSchema.safeParse(JSON.parse(notification.data)); 67 69 68 70 if (!config.success) return; ··· 95 97 region?: Region; 96 98 latency?: number; 97 99 }) => { 100 + const emailClient = new EmailClient({ apiKey: env.RESEND_API_KEY }); 101 + 98 102 const config = emailDataSchema.safeParse(JSON.parse(notification.data)); 99 103 100 104 if (!config.success) return;
+4 -1
packages/notifications/email/tsconfig.json
··· 1 1 { 2 2 "extends": "@openstatus/tsconfig/nextjs.json", 3 - "include": ["src", "*.ts"] 3 + "include": ["src", "*.ts"], 4 + "compilerOptions": { 5 + "types": ["bun-types"] 6 + } 4 7 }
+1
packages/notifications/pagerduty/package.json
··· 17 17 "@types/node": "20.8.0", 18 18 "@types/react": "19.2.2", 19 19 "@types/react-dom": "19.2.2", 20 + "bun-types": "1.3.1", 20 21 "typescript": "5.7.2" 21 22 } 22 23 }
+1
packages/notifications/pagerduty/src/index.test.ts
··· 15 15 let fetchMock: any = undefined; 16 16 17 17 beforeEach(() => { 18 + // @ts-expect-error 18 19 fetchMock = spyOn(global, "fetch").mockImplementation(() => 19 20 Promise.resolve(new Response(null, { status: 200 })), 20 21 );
+4 -1
packages/notifications/pagerduty/tsconfig.json
··· 1 1 { 2 2 "extends": "@openstatus/tsconfig/nextjs.json", 3 - "include": ["src", "*.ts"] 3 + "include": ["src", "*.ts"], 4 + "compilerOptions": { 5 + "types": ["bun-types"] 6 + } 4 7 }
+4
packages/notifications/slack/package.json
··· 2 2 "name": "@openstatus/notification-slack", 3 3 "version": "0.0.0", 4 4 "main": "src/index.ts", 5 + "scripts": { 6 + "test": "bun test" 7 + }, 5 8 "dependencies": { 6 9 "@openstatus/db": "workspace:*", 7 10 "zod": "3.24.2" ··· 9 12 "devDependencies": { 10 13 "@openstatus/tsconfig": "workspace:*", 11 14 "@types/node": "22.10.2", 15 + "bun-types": "1.3.1", 12 16 "typescript": "5.7.2" 13 17 } 14 18 }
+132
packages/notifications/slack/src/index.test.ts
··· 1 + import { 2 + afterEach, 3 + beforeEach, 4 + describe, 5 + expect, 6 + jest, 7 + spyOn, 8 + test, 9 + } from "bun:test"; 10 + import { selectNotificationSchema } from "@openstatus/db/src/schema"; 11 + import { sendAlert, sendDegraded, sendRecovery } from "./index"; 12 + 13 + describe("Slack Notifications", () => { 14 + // biome-ignore lint/suspicious/noExplicitAny: <explanation> 15 + let fetchMock: any = undefined; 16 + 17 + beforeEach(() => { 18 + // @ts-expect-error 19 + fetchMock = spyOn(global, "fetch").mockImplementation(() => 20 + Promise.resolve(new Response(null, { status: 200 })), 21 + ); 22 + }); 23 + 24 + afterEach(() => { 25 + jest.resetAllMocks(); 26 + }); 27 + 28 + test("Send degraded", async () => { 29 + const monitor = { 30 + id: "monitor-1", 31 + name: "API Health Check", 32 + url: "https://api.example.com/health", 33 + jobType: "http" as const, 34 + periodicity: "5m" as const, 35 + status: "active" as const, // or "down", "degraded" 36 + createdAt: new Date(), 37 + updatedAt: new Date(), 38 + region: "us-east-1", 39 + }; 40 + 41 + const a = { 42 + id: 1, 43 + name: "slack Notification", 44 + provider: "slack", 45 + workspaceId: 1, 46 + createdAt: new Date(), 47 + updatedAt: new Date(), 48 + data: '{"slack":"https://hooks.slack.com/services/url"}', 49 + }; 50 + 51 + const n = selectNotificationSchema.parse(a); 52 + await sendDegraded({ 53 + // @ts-expect-error 54 + monitor, 55 + notification: n, 56 + statusCode: 500, 57 + message: "Something went wrong", 58 + cronTimestamp: Date.now(), 59 + }); 60 + expect(fetchMock).toHaveBeenCalled(); 61 + }); 62 + 63 + test("Send Recovered", async () => { 64 + const monitor = { 65 + id: "monitor-1", 66 + name: "API Health Check", 67 + url: "https://api.example.com/health", 68 + jobType: "http" as const, 69 + periodicity: "5m" as const, 70 + status: "active" as const, // or "down", "degraded" 71 + createdAt: new Date(), 72 + updatedAt: new Date(), 73 + region: "us-east-1", 74 + }; 75 + 76 + const a = { 77 + id: 1, 78 + name: "slack Notification", 79 + provider: "slack", 80 + workspaceId: 1, 81 + createdAt: new Date(), 82 + updatedAt: new Date(), 83 + data: '{"slack":"https://hooks.slack.com/services/url"}', 84 + }; 85 + 86 + const n = selectNotificationSchema.parse(a); 87 + await sendRecovery({ 88 + // @ts-expect-error 89 + monitor, 90 + notification: n, 91 + statusCode: 500, 92 + message: "Something went wrong", 93 + cronTimestamp: Date.now(), 94 + }); 95 + expect(fetchMock).toHaveBeenCalled(); 96 + }); 97 + 98 + test("Send Alert", async () => { 99 + const monitor = { 100 + id: "monitor-1", 101 + name: "API Health Check", 102 + url: "https://api.example.com/health", 103 + jobType: "http" as const, 104 + periodicity: "5m" as const, 105 + status: "active" as const, // or "down", "degraded" 106 + createdAt: new Date(), 107 + updatedAt: new Date(), 108 + region: "us-east-1", 109 + }; 110 + const a = { 111 + id: 1, 112 + name: "slack Notification", 113 + provider: "slack", 114 + workspaceId: 1, 115 + createdAt: new Date(), 116 + updatedAt: new Date(), 117 + data: '{"slack":"https://hooks.slack.com/services/url"}', 118 + }; 119 + 120 + const n = selectNotificationSchema.parse(a); 121 + 122 + await sendAlert({ 123 + // @ts-expect-error 124 + monitor, 125 + notification: n, 126 + statusCode: 500, 127 + message: "Something went wrong", 128 + cronTimestamp: Date.now(), 129 + }); 130 + expect(fetchMock).toHaveBeenCalled(); 131 + }); 132 + });
+4 -1
packages/notifications/slack/tsconfig.json
··· 1 1 { 2 2 "extends": "@openstatus/tsconfig/nextjs.json", 3 - "include": ["src", "*.ts"] 3 + "include": ["src", "*.ts"], 4 + "compilerOptions": { 5 + "types": ["bun-types"] 6 + } 4 7 }
+4
packages/notifications/twillio-sms/package.json
··· 2 2 "name": "@openstatus/notification-twillio-sms", 3 3 "version": "0.0.0", 4 4 "main": "src/index.ts", 5 + "scripts": { 6 + "test": "bun test" 7 + }, 5 8 "dependencies": { 6 9 "@openstatus/db": "workspace:*", 7 10 "@t3-oss/env-core": "0.7.1", ··· 12 15 "@openstatus/tsconfig": "workspace:*", 13 16 "@types/node": "22.10.2", 14 17 "@types/validator": "13.12.0", 18 + "bun-types": "1.3.1", 15 19 "typescript": "5.7.2" 16 20 } 17 21 }
+132
packages/notifications/twillio-sms/src/index.test.ts
··· 1 + import { 2 + afterEach, 3 + beforeEach, 4 + describe, 5 + expect, 6 + jest, 7 + spyOn, 8 + test, 9 + } from "bun:test"; 10 + import { selectNotificationSchema } from "@openstatus/db/src/schema"; 11 + import { sendAlert, sendDegraded, sendRecovery } from "./index"; 12 + 13 + describe("Slack Notifications", () => { 14 + // biome-ignore lint/suspicious/noExplicitAny: <explanation> 15 + let fetchMock: any = undefined; 16 + 17 + beforeEach(() => { 18 + // @ts-expect-error 19 + fetchMock = spyOn(global, "fetch").mockImplementation(() => 20 + Promise.resolve(new Response(null, { status: 200 })), 21 + ); 22 + }); 23 + 24 + afterEach(() => { 25 + jest.resetAllMocks(); 26 + }); 27 + 28 + test("Send degraded", async () => { 29 + const monitor = { 30 + id: "monitor-1", 31 + name: "API Health Check", 32 + url: "https://api.example.com/health", 33 + jobType: "http" as const, 34 + periodicity: "5m" as const, 35 + status: "active" as const, // or "down", "degraded" 36 + createdAt: new Date(), 37 + updatedAt: new Date(), 38 + region: "us-east-1", 39 + }; 40 + 41 + const a = { 42 + id: 1, 43 + name: "slack Notification", 44 + provider: "slack", 45 + workspaceId: 1, 46 + createdAt: new Date(), 47 + updatedAt: new Date(), 48 + data: '{"sms":"+33623456789"}', 49 + }; 50 + 51 + const n = selectNotificationSchema.parse(a); 52 + await sendDegraded({ 53 + // @ts-expect-error 54 + monitor, 55 + notification: n, 56 + statusCode: 500, 57 + message: "Something went wrong", 58 + cronTimestamp: Date.now(), 59 + }); 60 + expect(fetchMock).toHaveBeenCalled(); 61 + }); 62 + 63 + test("Send Recovered", async () => { 64 + const monitor = { 65 + id: "monitor-1", 66 + name: "API Health Check", 67 + url: "https://api.example.com/health", 68 + jobType: "http" as const, 69 + periodicity: "5m" as const, 70 + status: "active" as const, // or "down", "degraded" 71 + createdAt: new Date(), 72 + updatedAt: new Date(), 73 + region: "us-east-1", 74 + }; 75 + 76 + const a = { 77 + id: 1, 78 + name: "slack Notification", 79 + provider: "slack", 80 + workspaceId: 1, 81 + createdAt: new Date(), 82 + updatedAt: new Date(), 83 + data: '{"sms":"+33623456789"}', 84 + }; 85 + 86 + const n = selectNotificationSchema.parse(a); 87 + await sendRecovery({ 88 + // @ts-expect-error 89 + monitor, 90 + notification: n, 91 + statusCode: 500, 92 + message: "Something went wrong", 93 + cronTimestamp: Date.now(), 94 + }); 95 + expect(fetchMock).toHaveBeenCalled(); 96 + }); 97 + 98 + test("Send Alert", async () => { 99 + const monitor = { 100 + id: "monitor-1", 101 + name: "API Health Check", 102 + url: "https://api.example.com/health", 103 + jobType: "http" as const, 104 + periodicity: "5m" as const, 105 + status: "active" as const, // or "down", "degraded" 106 + createdAt: new Date(), 107 + updatedAt: new Date(), 108 + region: "us-east-1", 109 + }; 110 + const a = { 111 + id: 1, 112 + name: "slack Notification", 113 + provider: "slack", 114 + workspaceId: 1, 115 + createdAt: new Date(), 116 + updatedAt: new Date(), 117 + data: '{"sms":"+33623456789"}', 118 + }; 119 + 120 + const n = selectNotificationSchema.parse(a); 121 + 122 + await sendAlert({ 123 + // @ts-expect-error 124 + monitor, 125 + notification: n, 126 + statusCode: 500, 127 + message: "Something went wrong", 128 + cronTimestamp: Date.now(), 129 + }); 130 + expect(fetchMock).toHaveBeenCalled(); 131 + }); 132 + });
+3 -3
packages/notifications/twillio-sms/src/index.ts
··· 37 37 ); 38 38 39 39 try { 40 - fetch( 40 + await fetch( 41 41 `https://api.twilio.com/2010-04-01/Accounts/${env.TWILLIO_ACCOUNT_ID}/Messages.json`, 42 42 { 43 43 method: "post", ··· 85 85 body.set("Body", `Your monitor ${name} / ${monitor.url} is up again`); 86 86 87 87 try { 88 - fetch( 88 + await fetch( 89 89 `https://api.twilio.com/2010-04-01/Accounts/${env.TWILLIO_ACCOUNT_ID}/Messages.json`, 90 90 { 91 91 method: "post", ··· 131 131 body.set("Body", `Your monitor ${name} / ${monitor.url} is degraded `); 132 132 133 133 try { 134 - fetch( 134 + await fetch( 135 135 `https://api.twilio.com/2010-04-01/Accounts/${env.TWILLIO_ACCOUNT_ID}/Messages.json`, 136 136 { 137 137 method: "post",
+4 -1
packages/notifications/twillio-sms/tsconfig.json
··· 1 1 { 2 2 "extends": "@openstatus/tsconfig/nextjs.json", 3 - "include": ["src", "*.ts"] 3 + "include": ["src", "*.ts"], 4 + "compilerOptions": { 5 + "types": ["bun-types"] 6 + } 4 7 }
+24 -7
pnpm-lock.yaml
··· 502 502 specifier: 13.11.6 503 503 version: 13.11.6 504 504 bun-types: 505 - specifier: 1.0.11 506 - version: 1.0.11 505 + specifier: 1.3.1 506 + version: 1.3.1(@types/react@19.2.2) 507 507 dotenv: 508 508 specifier: 16.3.1 509 509 version: 16.3.1 ··· 1497 1497 '@types/node': 1498 1498 specifier: 22.10.2 1499 1499 version: 22.10.2 1500 + bun-types: 1501 + specifier: 1.3.1 1502 + version: 1.3.1(@types/react@19.2.2) 1500 1503 typescript: 1501 1504 specifier: 5.7.2 1502 1505 version: 5.7.2 ··· 1584 1587 '@types/react-dom': 1585 1588 specifier: 19.2.2 1586 1589 version: 19.2.2(@types/react@19.2.2) 1590 + bun-types: 1591 + specifier: 1.3.1 1592 + version: 1.3.1(@types/react@19.2.2) 1587 1593 typescript: 1588 1594 specifier: 5.7.2 1589 1595 version: 5.7.2 ··· 1603 1609 '@types/node': 1604 1610 specifier: 22.10.2 1605 1611 version: 22.10.2 1612 + bun-types: 1613 + specifier: 1.3.1 1614 + version: 1.3.1(@types/react@19.2.2) 1606 1615 typescript: 1607 1616 specifier: 5.7.2 1608 1617 version: 5.7.2 ··· 1631 1640 '@types/validator': 1632 1641 specifier: 13.12.0 1633 1642 version: 13.12.0 1643 + bun-types: 1644 + specifier: 1.3.1 1645 + version: 1.3.1(@types/react@19.2.2) 1634 1646 typescript: 1635 1647 specifier: 5.7.2 1636 1648 version: 5.7.2 ··· 7094 7106 builtins@5.0.1: 7095 7107 resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==} 7096 7108 7097 - bun-types@1.0.11: 7098 - resolution: {integrity: sha512-XaDwjnBlkdTOtBEAcXhDnPSKFMlwFK/526z0iyairYIDhZJMzZM1QU4D7XRiEI2SpKQWexn0S/LN9Mwx5xSJNg==} 7099 - 7100 7109 bun-types@1.0.8: 7101 7110 resolution: {integrity: sha512-2dNB+dBwAcFW7RSd4y5vKycRjouKVklSwPk4EjBKWvcMYUBOqZGGNzV7+b2tfKBG3BeRXnozbnegVKR1azuATg==} 7111 + 7112 + bun-types@1.3.1: 7113 + resolution: {integrity: sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw==} 7114 + peerDependencies: 7115 + '@types/react': ^19 7102 7116 7103 7117 bun-types@1.3.2: 7104 7118 resolution: {integrity: sha512-i/Gln4tbzKNuxP70OWhJRZz1MRfvqExowP7U6JKoI8cntFrtxg7RJK3jvz7wQW54UuvNC8tbKHHri5fy74FVqg==} ··· 18374 18388 dependencies: 18375 18389 semver: 7.7.2 18376 18390 18377 - bun-types@1.0.11: {} 18391 + bun-types@1.0.8: {} 18378 18392 18379 - bun-types@1.0.8: {} 18393 + bun-types@1.3.1(@types/react@19.2.2): 18394 + dependencies: 18395 + '@types/node': 24.0.8 18396 + '@types/react': 19.2.2 18380 18397 18381 18398 bun-types@1.3.2(@types/react@19.2.2): 18382 18399 dependencies: