Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/openstatus. ๐ซ Status page with uptime monitoring & API monitoring as code ๐ซ openstatus.dev
bun drizzle-orm monitoring monitoring-as-code nextjs observability on-call open-source shadcn-ui status-page statuspage synthetic-monitoring tinybird turso uptime uptime-checker uptime-monitor
Something went wrong. Try again.
53 kB ยท 1695 lines
TypeScript
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696import { and, db, eq, isNotNull, isNull } from "@openstatus/db";import { monitorIncidentTable, monitor, page, pageComponent, pageSubscriber, workspace,} from "@openstatus/db/src/schema";import { afterAll, beforeAll, describe, expect, test } from "vitest";
/** * End-to-end integration tests for the full unsubscribe flow. * These tests simulate the complete user journey: * subscribe -> verify -> receive email -> unsubscribe */
let testPageId: number;let testWorkspaceId: number;const testSlug = "e2e-unsubscribe-test-page";const testEmail = "e2e-test-user@example.com";let subscriberToken: string;
beforeAll(async () => { // Clean up any existing test data await db.delete(pageSubscriber).where(eq(pageSubscriber.email, testEmail)); await db.delete(page).where(eq(page.slug, testSlug));
// Get an existing workspace (use workspace id 1 from seed data) const existingWorkspace = await db.query.workspace.findFirst({ where: eq(workspace.id, 1), });
if (!existingWorkspace) { throw new Error( "Test workspace not found. Please ensure seed data exists.", ); }
testWorkspaceId = existingWorkspace.id;
// Create a test page const testPage = await db .insert(page) .values({ workspaceId: testWorkspaceId, title: "E2E Test Status Page", description: "A test page for E2E unsubscribe flow tests", slug: testSlug, customDomain: "", }) .returning() .get();
testPageId = testPage.id;});
afterAll(async () => { // Clean up test data await db.delete(pageSubscriber).where(eq(pageSubscriber.email, testEmail)); await db.delete(page).where(eq(page.slug, testSlug));});
describe("Full unsubscribe flow: subscribe -> verify -> unsubscribe", () => { test("Step 1: User subscribes to status page", async () => { // Simulate subscription by inserting a subscriber const subscriber = await db .insert(pageSubscriber) .values({ pageId: testPageId, email: testEmail, token: crypto.randomUUID(), expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7), // 7 days }) .returning() .get();
expect(subscriber.id).toBeDefined(); expect(subscriber.email).toBe(testEmail); expect(subscriber.token).toBeDefined(); expect(subscriber.acceptedAt).toBeNull(); expect(subscriber.unsubscribedAt).toBeNull();
if (!subscriber.token) { throw new Error("Subscriber token is undefined"); }
subscriberToken = subscriber.token; });
test("Step 2: User verifies their email subscription", async () => { // Verify the subscription await db .update(pageSubscriber) .set({ acceptedAt: new Date() }) .where(eq(pageSubscriber.token, subscriberToken));
// Verify the subscription is now active const subscriber = await db.query.pageSubscriber.findFirst({ where: eq(pageSubscriber.token, subscriberToken), });
expect(subscriber?.acceptedAt).not.toBeNull(); expect(subscriber?.unsubscribedAt).toBeNull(); });
test("Step 3: Verified subscriber is included in email recipient list", async () => { // This query mirrors the exact query used in statusReports/post.ts const subscribers = await db .select() .from(pageSubscriber) .where( and( eq(pageSubscriber.pageId, testPageId), isNotNull(pageSubscriber.acceptedAt), isNull(pageSubscriber.unsubscribedAt), ), ) .all();
expect(subscribers.length).toBe(1); expect(subscribers[0].email).toBe(testEmail); expect(subscribers[0].token).toBe(subscriberToken); });
test("Step 4: User clicks unsubscribe and sets unsubscribedAt", async () => { // Simulate the unsubscribe action await db .update(pageSubscriber) .set({ unsubscribedAt: new Date() }) .where(eq(pageSubscriber.token, subscriberToken));
// Verify the unsubscription const subscriber = await db.query.pageSubscriber.findFirst({ where: eq(pageSubscriber.token, subscriberToken), });
expect(subscriber?.unsubscribedAt).not.toBeNull(); expect(subscriber?.unsubscribedAt).toBeInstanceOf(Date); });
test("Step 5: Unsubscribed user is excluded from email recipient list", async () => { // This query mirrors the exact query used in statusReports/post.ts const subscribers = await db .select() .from(pageSubscriber) .where( and( eq(pageSubscriber.pageId, testPageId), isNotNull(pageSubscriber.acceptedAt), isNull(pageSubscriber.unsubscribedAt), ), ) .all();
expect(subscribers.length).toBe(0); });});
describe("Confirmation page displays correct information", () => { let confirmPageToken: string;
beforeAll(async () => { // Create a fresh subscriber for confirmation page tests await db .delete(pageSubscriber) .where(eq(pageSubscriber.email, "confirm-page-test@example.com"));
const subscriber = await db .insert(pageSubscriber) .values({ pageId: testPageId, email: "confirm-page-test@example.com", token: crypto.randomUUID(), acceptedAt: new Date(), // Already verified expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7), }) .returning() .get();
if (!subscriber.token) { throw new Error("Subscriber token is undefined"); }
confirmPageToken = subscriber.token; });
afterAll(async () => { await db .delete(pageSubscriber) .where(eq(pageSubscriber.email, "confirm-page-test@example.com")); });
test("Confirmation page displays correct page name", async () => { const subscriber = await db.query.pageSubscriber.findFirst({ where: eq(pageSubscriber.token, confirmPageToken), with: { page: true, }, });
expect(subscriber?.page.title).toBe("E2E Test Status Page"); });
test("Confirmation page displays masked email (first char + *** + @domain)", async () => { const subscriber = await db.query.pageSubscriber.findFirst({ where: eq(pageSubscriber.token, confirmPageToken), });
if (!subscriber) { throw new Error("Subscriber not found"); }
const email = subscriber.email; if (!email) throw new Error("Subscriber email not found"); expect(email).toBe("confirm-page-test@example.com");
// Apply the same masking logic as in the API const [localPart, domain] = email.split("@"); const maskedEmail = localPart.length > 0 ? `${localPart[0]}***@${domain}` : `***@${domain}`;
expect(maskedEmail).toBe("c***@example.com"); });
test("Email masking works for single character local part", async () => { const email = "a@example.com"; const [localPart, domain] = email.split("@"); const maskedEmail = localPart.length > 0 ? `${localPart[0]}***@${domain}` : `***@${domain}`;
expect(maskedEmail).toBe("a***@example.com"); });
test("Email masking works for long local part", async () => { const email = "verylongemailaddress@example.com"; const [localPart, domain] = email.split("@"); const maskedEmail = localPart.length > 0 ? `${localPart[0]}***@${domain}` : `***@${domain}`;
expect(maskedEmail).toBe("v***@example.com"); });});
describe("Clicking confirm sets unsubscribedAt timestamp", () => { let unsubscribeToken: string;
beforeAll(async () => { // Create a fresh subscriber await db .delete(pageSubscriber) .where(eq(pageSubscriber.email, "unsubscribe-click-test@example.com"));
const subscriber = await db .insert(pageSubscriber) .values({ pageId: testPageId, email: "unsubscribe-click-test@example.com", token: crypto.randomUUID(), acceptedAt: new Date(), // Already verified expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7), }) .returning() .get();
if (!subscriber.token) { throw new Error("Subscriber token is undefined"); }
unsubscribeToken = subscriber.token; });
afterAll(async () => { await db .delete(pageSubscriber) .where(eq(pageSubscriber.email, "unsubscribe-click-test@example.com")); });
test("Before clicking confirm, unsubscribedAt is null", async () => { const subscriber = await db.query.pageSubscriber.findFirst({ where: eq(pageSubscriber.token, unsubscribeToken), });
expect(subscriber?.unsubscribedAt).toBeNull(); });
test("After clicking confirm, unsubscribedAt is set to current timestamp", async () => { const beforeUnsubscribe = new Date();
// Simulate clicking "Confirm Unsubscribe" await db .update(pageSubscriber) .set({ unsubscribedAt: new Date() }) .where(eq(pageSubscriber.token, unsubscribeToken));
const afterUnsubscribe = new Date();
const subscriber = await db.query.pageSubscriber.findFirst({ where: eq(pageSubscriber.token, unsubscribeToken), });
if (!subscriber) { throw new Error("Subscriber not found"); }
expect(subscriber.unsubscribedAt).not.toBeNull(); expect(subscriber.unsubscribedAt).toBeInstanceOf(Date);
// Verify the timestamp is within the expected range if (!subscriber.unsubscribedAt) { throw new Error("Subscriber unsubscribedAt is undefined"); }
// SQLite stores timestamps in seconds, so we compare at second precision const unsubscribedTime = Math.floor( subscriber.unsubscribedAt.getTime() / 1000, ); const beforeTime = Math.floor(beforeUnsubscribe.getTime() / 1000); const afterTime = Math.floor(afterUnsubscribe.getTime() / 1000);
expect(unsubscribedTime).toBeGreaterThanOrEqual(beforeTime); expect(unsubscribedTime).toBeLessThanOrEqual(afterTime); });
test("Subscriber state transitions correctly through the flow", async () => { // Verify the subscriber has completed the full lifecycle const subscriber = await db.query.pageSubscriber.findFirst({ where: eq(pageSubscriber.token, unsubscribeToken), });
// Has been verified (acceptedAt is set) expect(subscriber?.acceptedAt).not.toBeNull();
// Has been unsubscribed (unsubscribedAt is set) expect(subscriber?.unsubscribedAt).not.toBeNull();
// Token is still present (for audit purposes) expect(subscriber?.token).toBe(unsubscribeToken); });});
describe("Unsubscribed user does not receive new emails", () => { let unsubscribedToken: string; let pendingToken: string;
beforeAll(async () => { // Clean up and create multiple subscribers with different states await db .delete(pageSubscriber) .where(eq(pageSubscriber.email, "active-user@example.com")); await db .delete(pageSubscriber) .where(eq(pageSubscriber.email, "unsubscribed-user@example.com")); await db .delete(pageSubscriber) .where(eq(pageSubscriber.email, "pending-user@example.com"));
// Active subscriber const active = await db .insert(pageSubscriber) .values({ pageId: testPageId, email: "active-user@example.com", token: crypto.randomUUID(), acceptedAt: new Date(), unsubscribedAt: null, expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7), }) .returning() .get();
if (!active.token) { throw new Error("Active subscriber token is undefined"); }
// Unsubscribed subscriber const unsubscribed = await db .insert(pageSubscriber) .values({ pageId: testPageId, email: "unsubscribed-user@example.com", token: crypto.randomUUID(), acceptedAt: new Date(), unsubscribedAt: new Date(), expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7), }) .returning() .get();
if (!unsubscribed.token) { throw new Error("Unsubscribed subscriber token is undefined"); }
unsubscribedToken = unsubscribed.token;
// Pending (unverified) subscriber const pending = await db .insert(pageSubscriber) .values({ pageId: testPageId, email: "pending-user@example.com", token: crypto.randomUUID(), acceptedAt: null, unsubscribedAt: null, expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7), }) .returning() .get();
if (!pending.token) { throw new Error("Pending subscriber token is undefined"); }
pendingToken = pending.token; });
afterAll(async () => { await db .delete(pageSubscriber) .where(eq(pageSubscriber.email, "active-user@example.com")); await db .delete(pageSubscriber) .where(eq(pageSubscriber.email, "unsubscribed-user@example.com")); await db .delete(pageSubscriber) .where(eq(pageSubscriber.email, "pending-user@example.com")); });
test("Email query returns only active subscribers with valid tokens", async () => { // This mirrors the exact query pattern used in email-sending routes const emailRecipients = await db .select({ email: pageSubscriber.email, token: pageSubscriber.token, }) .from(pageSubscriber) .where( and( eq(pageSubscriber.pageId, testPageId), isNotNull(pageSubscriber.acceptedAt), isNull(pageSubscriber.unsubscribedAt), ), ) .all();
// Should only include active subscriber expect(emailRecipients.length).toBeGreaterThanOrEqual(1);
const emails = emailRecipients.map((r) => r.email); expect(emails).toContain("active-user@example.com"); expect(emails).not.toContain("unsubscribed-user@example.com"); expect(emails).not.toContain("pending-user@example.com"); });
test("Unsubscribed users are filtered out even with acceptedAt set", async () => { // Verify the unsubscribed user has acceptedAt set const unsubscribedUser = await db.query.pageSubscriber.findFirst({ where: eq(pageSubscriber.token, unsubscribedToken), });
expect(unsubscribedUser?.acceptedAt).not.toBeNull(); expect(unsubscribedUser?.unsubscribedAt).not.toBeNull();
// Query with proper filters const subscribers = await db .select() .from(pageSubscriber) .where( and( eq(pageSubscriber.pageId, testPageId), isNotNull(pageSubscriber.acceptedAt), isNull(pageSubscriber.unsubscribedAt), ), ) .all();
const foundUnsubscribed = subscribers.find( (s) => s.email === "unsubscribed-user@example.com", ); expect(foundUnsubscribed).toBeUndefined(); });
test("Pending users are filtered out (not verified)", async () => { // Verify the pending user has no acceptedAt const pendingUser = await db.query.pageSubscriber.findFirst({ where: eq(pageSubscriber.token, pendingToken), });
expect(pendingUser?.acceptedAt).toBeNull();
// Query with proper filters const subscribers = await db .select() .from(pageSubscriber) .where( and( eq(pageSubscriber.pageId, testPageId), isNotNull(pageSubscriber.acceptedAt), isNull(pageSubscriber.unsubscribedAt), ), ) .all();
const foundPending = subscribers.find( (s) => s.email === "pending-user@example.com", ); expect(foundPending).toBeUndefined(); });
test("Email recipients list includes token for unsubscribe URL generation", async () => { const emailRecipients = await db .select({ email: pageSubscriber.email, token: pageSubscriber.token, }) .from(pageSubscriber) .where( and( eq(pageSubscriber.pageId, testPageId), isNotNull(pageSubscriber.acceptedAt), isNull(pageSubscriber.unsubscribedAt), ), ) .all();
// Filter for valid tokens (as done in email sending routes) const validRecipients = emailRecipients.filter( (r): r is { email: string; token: string } => r.token !== null, );
expect(validRecipients.length).toBeGreaterThanOrEqual(1);
// Each valid recipient should have a UUID token for (const recipient of validRecipients) { expect(recipient.token).toMatch( /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, ); } });});
describe("Re-subscription after unsubscribe flow", () => { let resubscribeToken: string;
beforeAll(async () => { // Clean up await db .delete(pageSubscriber) .where(eq(pageSubscriber.email, "resubscribe-test@example.com"));
// Create an initially subscribed and verified user const subscriber = await db .insert(pageSubscriber) .values({ pageId: testPageId, email: "resubscribe-test@example.com", token: crypto.randomUUID(), acceptedAt: new Date(), unsubscribedAt: null, expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7), }) .returning() .get();
if (!subscriber.token) { throw new Error("Subscriber token is undefined"); }
resubscribeToken = subscriber.token; });
afterAll(async () => { await db .delete(pageSubscriber) .where(eq(pageSubscriber.email, "resubscribe-test@example.com")); });
test("User can complete full subscribe -> unsubscribe -> resubscribe cycle", async () => { // Step 1: Verify initial subscription state let subscriber = await db.query.pageSubscriber.findFirst({ where: eq(pageSubscriber.email, "resubscribe-test@example.com"), });
if (!subscriber) { throw new Error("Subscriber ID is undefined"); }
expect(subscriber?.acceptedAt).not.toBeNull(); expect(subscriber?.unsubscribedAt).toBeNull();
// Step 2: User unsubscribes await db .update(pageSubscriber) .set({ unsubscribedAt: new Date() }) .where(eq(pageSubscriber.id, subscriber.id));
subscriber = await db.query.pageSubscriber.findFirst({ where: eq(pageSubscriber.id, subscriber.id), });
expect(subscriber?.unsubscribedAt).not.toBeNull();
// Step 3: User is excluded from emails const subscribersAfterUnsub = await db .select() .from(pageSubscriber) .where( and( eq(pageSubscriber.pageId, testPageId), eq(pageSubscriber.email, "resubscribe-test@example.com"), isNotNull(pageSubscriber.acceptedAt), isNull(pageSubscriber.unsubscribedAt), ), ) .all();
expect(subscribersAfterUnsub.length).toBe(0);
if (!subscriber) { throw new Error("Subscriber is undefined"); }
// Step 4: User re-subscribes (simulating the re-subscription flow) const newToken = crypto.randomUUID(); await db .update(pageSubscriber) .set({ unsubscribedAt: null, acceptedAt: null, // Requires re-verification token: newToken, expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7), }) .where(eq(pageSubscriber.id, subscriber.id));
// Step 5: User is still excluded (not yet verified) const subscribersPendingVerify = await db .select() .from(pageSubscriber) .where( and( eq(pageSubscriber.pageId, testPageId), eq(pageSubscriber.email, "resubscribe-test@example.com"), isNotNull(pageSubscriber.acceptedAt), isNull(pageSubscriber.unsubscribedAt), ), ) .all();
expect(subscribersPendingVerify.length).toBe(0);
// Step 6: User verifies their email again await db .update(pageSubscriber) .set({ acceptedAt: new Date() }) .where(eq(pageSubscriber.token, newToken));
// Step 7: User is now included in email list again const subscribersAfterReverify = await db .select() .from(pageSubscriber) .where( and( eq(pageSubscriber.pageId, testPageId), eq(pageSubscriber.email, "resubscribe-test@example.com"), isNotNull(pageSubscriber.acceptedAt), isNull(pageSubscriber.unsubscribedAt), ), ) .all();
expect(subscribersAfterReverify.length).toBe(1); expect(subscribersAfterReverify[0].token).toBe(newToken); expect(subscribersAfterReverify[0].token).not.toBe(resubscribeToken); });});
describe("Invalid token handling", () => { test("Non-existent token returns no subscriber", async () => { const fakeToken = crypto.randomUUID();
const subscriber = await db.query.pageSubscriber.findFirst({ where: eq(pageSubscriber.token, fakeToken), });
expect(subscriber).toBeUndefined(); });
test("Invalid UUID format is handled gracefully", async () => { const invalidToken = "not-a-valid-uuid";
// The database query will still work, just return no results const subscriber = await db.query.pageSubscriber.findFirst({ where: eq(pageSubscriber.token, invalidToken), });
expect(subscriber).toBeUndefined(); });
test("Already unsubscribed token returns subscriber with unsubscribedAt set", async () => { // Create an unsubscribed subscriber await db .delete(pageSubscriber) .where(eq(pageSubscriber.email, "already-unsub@example.com"));
const subscriber = await db .insert(pageSubscriber) .values({ pageId: testPageId, email: "already-unsub@example.com", token: crypto.randomUUID(), acceptedAt: new Date(), unsubscribedAt: new Date(), expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7), }) .returning() .get();
if (!subscriber.token) { throw new Error("Subscriber token is undefined"); }
// Query the subscriber const found = await db.query.pageSubscriber.findFirst({ where: eq(pageSubscriber.token, subscriber.token), });
expect(found).toBeDefined(); expect(found?.unsubscribedAt).not.toBeNull();
// Clean up await db .delete(pageSubscriber) .where(eq(pageSubscriber.email, "already-unsub@example.com")); });});
describe("statusPage.get endpoint validation", () => { test("Returns all required output fields with correct types", async () => { // Use the edgeRouter to call the statusPage.get endpoint const { edgeRouter } = await import("../edge"); const { createInnerTRPCContext } = await import("../trpc");
const ctx = createInnerTRPCContext({ req: undefined, // @ts-expect-error - auth not required for public procedure auth: undefined, });
const caller = edgeRouter.createCaller(ctx); const result = await caller.statusPage.get({ slug: testSlug });
// Validate that result is not null expect(result).toBeDefined(); expect(result).not.toBeNull();
if (!result) { throw new Error("Result should not be null"); }
// Validate core page fields with specific types expect(typeof result.slug).toBe("string"); expect(typeof result.title).toBe("string"); expect(typeof result.description).toBe("string"); expect(result.createdAt).toBeInstanceOf(Date); expect(result.updatedAt).toBeInstanceOf(Date);
// Validate slug matches what we requested expect(result.slug).toBe(testSlug);
// Validate all array fields exist and are arrays expect(Array.isArray(result.monitors)).toBe(true); expect(Array.isArray(result.monitorGroups)).toBe(true); expect(Array.isArray(result.pageComponents)).toBe(true); expect(Array.isArray(result.pageComponentGroups)).toBe(true); expect(Array.isArray(result.trackers)).toBe(true); expect(Array.isArray(result.lastEvents)).toBe(true); expect(Array.isArray(result.openEvents)).toBe(true); expect(Array.isArray(result.statusReports)).toBe(true); expect(Array.isArray(result.incidents)).toBe(true); expect(Array.isArray(result.maintenances)).toBe(true);
// Validate status field is one of the allowed values expect(["success", "degraded", "error", "info"]).toContain(result.status);
// Validate workspacePlan field expect(result.workspacePlan).toBeDefined(); expect(typeof result.workspacePlan).toBe("string");
// Validate whiteLabel field expect(typeof result.whiteLabel).toBe("boolean"); });
test("Returns null for non-existent slug", async () => { const { edgeRouter } = await import("../edge"); const { createInnerTRPCContext } = await import("../trpc");
const ctx = createInnerTRPCContext({ req: undefined, // @ts-expect-error - auth not required for public procedure auth: undefined, });
const caller = edgeRouter.createCaller(ctx); const result = await caller.statusPage.get({ slug: "non-existent-slug-12345", });
expect(result).toBeNull(); });
test("Tracker objects have correct discriminated union types", async () => { const { edgeRouter } = await import("../edge"); const { createInnerTRPCContext } = await import("../trpc");
const ctx = createInnerTRPCContext({ req: undefined, // @ts-expect-error - auth not required for public procedure auth: undefined, });
const caller = edgeRouter.createCaller(ctx); const result = await caller.statusPage.get({ slug: testSlug });
if (!result) { // If no result, skip this test as there are no trackers to validate return; }
// Validate each tracker has the correct structure for (const tracker of result.trackers) { expect(tracker).toHaveProperty("type"); expect(tracker).toHaveProperty("order");
if (tracker.type === "component") { expect(tracker).toHaveProperty("component"); expect(tracker.component).toHaveProperty("id"); expect(tracker.component).toHaveProperty("name"); expect(tracker.component).toHaveProperty("status"); expect(tracker.component).toHaveProperty("type"); expect(["monitor", "static"]).toContain(tracker.component.type); expect(["success", "degraded", "error", "info"]).toContain( tracker.component.status, );
// Monitor-type components should have monitor relation if (tracker.component.type === "monitor") { expect(tracker.component).toHaveProperty("monitor"); expect(tracker.component.monitor).toBeDefined(); } } else if (tracker.type === "group") { expect(tracker).toHaveProperty("groupId"); expect(tracker).toHaveProperty("groupName"); expect(tracker).toHaveProperty("components"); expect(tracker).toHaveProperty("status"); expect(Array.isArray(tracker.components)).toBe(true); expect(["success", "degraded", "error", "info"]).toContain( tracker.status, ); } } });
test("Event objects have required fields", async () => { const { edgeRouter } = await import("../edge"); const { createInnerTRPCContext } = await import("../trpc");
const ctx = createInnerTRPCContext({ req: undefined, // @ts-expect-error - auth not required for public procedure auth: undefined, });
const caller = edgeRouter.createCaller(ctx); const result = await caller.statusPage.get({ slug: testSlug });
if (!result) { return; }
// Validate lastEvents structure for (const event of result.lastEvents) { expect(event).toMatchObject({ id: expect.any(Number), name: expect.any(String), from: expect.any(Date), status: expect.any(String), type: expect.any(String), }); expect(["maintenance", "incident", "report"]).toContain(event.type); expect(["success", "degraded", "error", "info"]).toContain(event.status); }
// Validate openEvents structure for (const event of result.openEvents) { expect(event).toMatchObject({ id: expect.any(Number), name: expect.any(String), from: expect.any(Date), status: expect.any(String), type: expect.any(String), }); expect(["maintenance", "incident", "report"]).toContain(event.type); expect(["success", "degraded", "error", "info"]).toContain(event.status); } });
test("Monitor objects contain status field", async () => { const { edgeRouter } = await import("../edge"); const { createInnerTRPCContext } = await import("../trpc");
const ctx = createInnerTRPCContext({ req: undefined, // @ts-expect-error - auth not required for public procedure auth: undefined, });
const caller = edgeRouter.createCaller(ctx); const result = await caller.statusPage.get({ slug: testSlug });
if (!result || result.monitors.length === 0) { return; }
// Validate each monitor has status field for (const monitor of result.monitors) { expect(monitor).toHaveProperty("status"); expect(["success", "degraded", "error", "info"]).toContain( monitor.status, ); expect(monitor).toHaveProperty("id"); expect(monitor).toHaveProperty("name"); } });});
describe("statusPage.get monitors sorting", () => { const sortingTestSlug = "monitors-sorting-test-page"; let sortingTestPageId: number; let sortingTestWorkspaceId: number; let group1Id: number; let group2Id: number;
beforeAll(async () => { // Clean up any existing test data await db.delete(page).where(eq(page.slug, sortingTestSlug));
// Use workspace id 1 from seed data const existingWorkspace = await db.query.workspace.findFirst({ where: eq(workspace.id, 1), });
if (!existingWorkspace) { throw new Error("Test workspace not found"); }
sortingTestWorkspaceId = existingWorkspace.id;
// Create test page const testPage = await db .insert(page) .values({ workspaceId: sortingTestWorkspaceId, title: "Monitors Sorting Test Page", description: "Test page for monitors sorting", slug: sortingTestSlug, customDomain: "", }) .returning() .get();
sortingTestPageId = testPage.id;
// Import pageComponentGroup schema const { pageComponentGroup } = await import("@openstatus/db/src/schema");
// Create two monitor groups const group1 = await db .insert(pageComponentGroup) .values({ workspaceId: sortingTestWorkspaceId, pageId: sortingTestPageId, name: "Group 1", defaultOpen: true, }) .returning() .get(); group1Id = group1.id;
const group2 = await db .insert(pageComponentGroup) .values({ workspaceId: sortingTestWorkspaceId, pageId: sortingTestPageId, name: "Group 2", defaultOpen: true, }) .returning() .get(); group2Id = group2.id;
// Create monitors with specific order/groupOrder values for testing sorting // Group 1 should appear first (min order = 1) // Group 2 should appear second (min order = 5) // Ungrouped monitors interspersed based on their order values
// Monitor 1: Ungrouped, order = 0 (should be first) const monitor1 = await db .insert(monitor) .values({ workspaceId: sortingTestWorkspaceId, url: "https://monitor1.test", name: "Monitor 1 Ungrouped", periodicity: "30s", active: true, }) .returning() .get();
await db.insert(pageComponent).values({ workspaceId: sortingTestWorkspaceId, pageId: sortingTestPageId, monitorId: monitor1.id, name: monitor1.name, type: "monitor", order: 0, groupId: null, groupOrder: null, });
// Monitor 2: Group 1, order = 1, groupOrder = 1 (group should be second) const monitor2 = await db .insert(monitor) .values({ workspaceId: sortingTestWorkspaceId, url: "https://monitor2.test", name: "Monitor 2 Group 1 First", periodicity: "30s", active: true, }) .returning() .get();
await db.insert(pageComponent).values({ workspaceId: sortingTestWorkspaceId, pageId: sortingTestPageId, monitorId: monitor2.id, name: monitor2.name, type: "monitor", order: 1, groupId: group1Id, groupOrder: 1, });
// Monitor 3: Group 1, order = 3, groupOrder = 2 (should be second in group 1) const monitor3 = await db .insert(monitor) .values({ workspaceId: sortingTestWorkspaceId, url: "https://monitor3.test", name: "Monitor 3 Group 1 Second", periodicity: "30s", active: true, }) .returning() .get();
await db.insert(pageComponent).values({ workspaceId: sortingTestWorkspaceId, pageId: sortingTestPageId, monitorId: monitor3.id, name: monitor3.name, type: "monitor", order: 3, groupId: group1Id, groupOrder: 2, });
// Monitor 4: Ungrouped, order = 4 (should come after Group 1, before Group 2) const monitor4 = await db .insert(monitor) .values({ workspaceId: sortingTestWorkspaceId, url: "https://monitor4.test", name: "Monitor 4 Ungrouped", periodicity: "30s", active: true, }) .returning() .get();
await db.insert(pageComponent).values({ workspaceId: sortingTestWorkspaceId, pageId: sortingTestPageId, monitorId: monitor4.id, name: monitor4.name, type: "monitor", order: 4, groupId: null, groupOrder: null, });
// Monitor 5: Group 2, order = 5, groupOrder = 1 (group should be fourth) const monitor5 = await db .insert(monitor) .values({ workspaceId: sortingTestWorkspaceId, url: "https://monitor5.test", name: "Monitor 5 Group 2 First", periodicity: "30s", active: true, }) .returning() .get();
await db.insert(pageComponent).values({ workspaceId: sortingTestWorkspaceId, pageId: sortingTestPageId, monitorId: monitor5.id, name: monitor5.name, type: "monitor", order: 5, groupId: group2Id, groupOrder: 1, });
// Monitor 6: Group 2, order = 6, groupOrder = 0 (should be first in group 2) const monitor6 = await db .insert(monitor) .values({ workspaceId: sortingTestWorkspaceId, url: "https://monitor6.test", name: "Monitor 6 Group 2 Zero", periodicity: "30s", active: true, }) .returning() .get();
await db.insert(pageComponent).values({ workspaceId: sortingTestWorkspaceId, pageId: sortingTestPageId, monitorId: monitor6.id, name: monitor6.name, type: "monitor", order: 6, groupId: group2Id, groupOrder: 0, }); });
afterAll(async () => { // Clean up test data await db.delete(page).where(eq(page.slug, sortingTestSlug)); });
test("Monitors are sorted correctly by group and order", async () => { const { edgeRouter } = await import("../edge"); const { createInnerTRPCContext } = await import("../trpc");
const ctx = createInnerTRPCContext({ req: undefined, // @ts-expect-error - auth not required for public procedure auth: undefined, });
const caller = edgeRouter.createCaller(ctx); const result = await caller.statusPage.get({ slug: sortingTestSlug });
expect(result).toBeDefined(); expect(result).not.toBeNull();
if (!result) { throw new Error("Result should not be null"); }
expect(result.monitors.length).toBe(6);
// Expected order based on sorting logic: // 1. Monitor 1 (ungrouped, order=0) // 2. Monitor 2 (group1, order=1, groupOrder=1) - group1 min order = 1 // 3. Monitor 3 (group1, order=3, groupOrder=2) // 4. Monitor 4 (ungrouped, order=4) // 5. Monitor 6 (group2, order=6, groupOrder=0) - group2 min order = 5 // 6. Monitor 5 (group2, order=5, groupOrder=1)
expect(result.monitors[0].name).toBe("Monitor 1 Ungrouped"); expect(result.monitors[0].monitorGroupId).toBeNull();
expect(result.monitors[1].name).toBe("Monitor 2 Group 1 First"); expect(result.monitors[1].monitorGroupId).toBe(group1Id);
expect(result.monitors[2].name).toBe("Monitor 3 Group 1 Second"); expect(result.monitors[2].monitorGroupId).toBe(group1Id);
expect(result.monitors[3].name).toBe("Monitor 4 Ungrouped"); expect(result.monitors[3].monitorGroupId).toBeNull();
expect(result.monitors[4].name).toBe("Monitor 6 Group 2 Zero"); expect(result.monitors[4].monitorGroupId).toBe(group2Id);
expect(result.monitors[5].name).toBe("Monitor 5 Group 2 First"); expect(result.monitors[5].monitorGroupId).toBe(group2Id); });
test("Grouped monitors are sorted by groupOrder within their group", async () => { const { edgeRouter } = await import("../edge"); const { createInnerTRPCContext } = await import("../trpc");
const ctx = createInnerTRPCContext({ req: undefined, // @ts-expect-error - auth not required for public procedure auth: undefined, });
const caller = edgeRouter.createCaller(ctx); const result = await caller.statusPage.get({ slug: sortingTestSlug });
if (!result) { throw new Error("Result should not be null"); }
// Find all monitors in group 1 const group1Monitors = result.monitors.filter( (m) => m.monitorGroupId === group1Id, );
expect(group1Monitors.length).toBe(2); expect(group1Monitors[0].groupOrder).toBe(1); expect(group1Monitors[1].groupOrder).toBe(2);
// Find all monitors in group 2 const group2Monitors = result.monitors.filter( (m) => m.monitorGroupId === group2Id, );
expect(group2Monitors.length).toBe(2); expect(group2Monitors[0].groupOrder).toBe(0); expect(group2Monitors[1].groupOrder).toBe(1); });});
describe("statusPage.get gates incidents by barType (calendar manual mode)", () => { const barTypeSlug = "bar-type-incident-gating-test-page"; let barTypePageId: number; let barTypeMonitorId: number; let barTypeIncidentId: number; let barTypeComponentId: number;
async function getPage(barType?: "absolute" | "manual") { const { edgeRouter } = await import("../edge"); const { createInnerTRPCContext } = await import("../trpc"); const ctx = createInnerTRPCContext({ req: undefined, // @ts-expect-error - auth not required for public procedure auth: undefined, }); const caller = edgeRouter.createCaller(ctx); return caller.statusPage.get({ slug: barTypeSlug, barType }); }
function monitorIncidents(result: Awaited<ReturnType<typeof getPage>>) { const component = result?.pageComponents.find( (c) => c.monitorId === barTypeMonitorId, ); return component?.monitor?.monitorIncidents ?? []; }
beforeAll(async () => { await db.delete(page).where(eq(page.slug, barTypeSlug));
// Page is stored as "manual" so we can also assert the default (no explicit // barType arg) resolves from the page configuration. const testPage = await db .insert(page) .values({ workspaceId: 1, title: "Bar Type Incident Gating Page", description: "Verifies incident gating in statusPage.get", slug: barTypeSlug, customDomain: "", configuration: { type: "manual", value: "requests", uptime: true, theme: "default", }, }) .returning() .get(); barTypePageId = testPage.id;
const testMonitor = await db .insert(monitor) .values({ workspaceId: 1, name: "Bar Type Test Monitor", periodicity: "1m", url: "https://example.com", active: true, }) .returning() .get(); barTypeMonitorId = testMonitor.id;
const testIncident = await db .insert(monitorIncidentTable) .values({ monitorId: barTypeMonitorId, workspaceId: 1, title: "Bar Type Test Incident", startedAt: new Date(), }) .returning() .get(); barTypeIncidentId = testIncident.id;
const testComponent = await db .insert(pageComponent) .values({ workspaceId: 1, pageId: barTypePageId, type: "monitor", monitorId: barTypeMonitorId, name: "Bar Type Test Component", order: 0, }) .returning() .get(); barTypeComponentId = testComponent.id; });
afterAll(async () => { await db .delete(pageComponent) .where(eq(pageComponent.id, barTypeComponentId)); await db .delete(monitorIncidentTable) .where(eq(monitorIncidentTable.id, barTypeIncidentId)); await db.delete(monitor).where(eq(monitor.id, barTypeMonitorId)); await db.delete(page).where(eq(page.id, barTypePageId)); });
test("barType 'absolute' keeps monitor incidents", async () => { const result = await getPage("absolute"); const incidents = monitorIncidents(result);
expect(incidents.length).toBe(1); expect(incidents[0].id).toBe(barTypeIncidentId); });
test("barType 'manual' strips monitor incidents", async () => { const result = await getPage("manual");
expect(monitorIncidents(result)).toEqual([]); });
test("defaults to the page configuration when barType is omitted", async () => { // Stored configuration is "manual", so incidents must be stripped without // an explicit barType โ this is the initial-load path the calendar uses. const result = await getPage();
expect(monitorIncidents(result)).toEqual([]); });});
describe("statusPage exposes page component names, not internal monitor names", () => { const publicNameSlug = "public-component-name-test-page"; let publicNamePageId: number; let publicNameMonitorId: number; let publicNameComponentId: number; let noDescriptionMonitorId: number; let noDescriptionComponentId: number; let clearedDescriptionMonitorId: number; let clearedDescriptionComponentId: number; let grpcMonitorId: number; let grpcComponentId: number;
const internalName = "Internal Monitor Name"; const internalDescription = "Internal monitor description"; const legacyExternalName = "Legacy External Name"; const componentName = "Public Component Name"; const componentDescription = "Public component description"; const fallbackDescription = "Monitor description used as fallback";
async function createCaller() { const { edgeRouter } = await import("../edge"); const { createInnerTRPCContext } = await import("../trpc"); const ctx = createInnerTRPCContext({ req: undefined, // @ts-expect-error - auth not required for public procedure auth: undefined, }); return edgeRouter.createCaller(ctx); }
beforeAll(async () => { await db.delete(page).where(eq(page.slug, publicNameSlug));
const testPage = await db .insert(page) .values({ workspaceId: 1, title: "Public Component Name Page", description: "Verifies public naming in statusPage procedures", slug: publicNameSlug, customDomain: "", }) .returning() .get(); publicNamePageId = testPage.id;
// externalName is set on purpose: the legacy `externalName || name` // schema transform must not override the page component name. const testMonitor = await db .insert(monitor) .values({ workspaceId: 1, name: internalName, externalName: legacyExternalName, description: internalDescription, periodicity: "1m", url: "https://example.com", active: true, public: true, }) .returning() .get(); publicNameMonitorId = testMonitor.id;
const testComponent = await db .insert(pageComponent) .values({ workspaceId: 1, pageId: publicNamePageId, type: "monitor", monitorId: publicNameMonitorId, name: componentName, description: componentDescription, order: 0, }) .returning() .get(); publicNameComponentId = testComponent.id;
// mirrors pre-existing components: description was never backfilled (NULL) const noDescriptionMonitor = await db .insert(monitor) .values({ workspaceId: 1, name: "No Description Component Monitor", description: fallbackDescription, periodicity: "1m", url: "https://example.com", active: true, public: true, }) .returning() .get(); noDescriptionMonitorId = noDescriptionMonitor.id;
const noDescriptionComponent = await db .insert(pageComponent) .values({ workspaceId: 1, pageId: publicNamePageId, type: "monitor", monitorId: noDescriptionMonitorId, name: "No Description Component", order: 1, }) .returning() .get(); noDescriptionComponentId = noDescriptionComponent.id;
// mirrors a deliberately cleared field: the dashboard stores "" const clearedDescriptionMonitor = await db .insert(monitor) .values({ workspaceId: 1, name: "Cleared Description Component Monitor", description: "Internal description that must stay hidden", periodicity: "1m", url: "https://example.com", active: true, public: true, }) .returning() .get(); clearedDescriptionMonitorId = clearedDescriptionMonitor.id;
const clearedDescriptionComponent = await db .insert(pageComponent) .values({ workspaceId: 1, pageId: publicNamePageId, type: "monitor", monitorId: clearedDescriptionMonitorId, name: "Cleared Description Component", description: "", order: 2, }) .returning() .get(); clearedDescriptionComponentId = clearedDescriptionComponent.id;
// getMonitor dispatches its Tinybird reads on jobType; a gRPC monitor must // find a matching entry like every other supported type. const grpcMonitor = await db .insert(monitor) .values({ workspaceId: 1, name: "gRPC monitor", jobType: "grpc", periodicity: "1m", url: "api.example.com:443", active: true, public: true, }) .returning() .get(); grpcMonitorId = grpcMonitor.id;
const grpcComponent = await db .insert(pageComponent) .values({ workspaceId: 1, pageId: publicNamePageId, type: "monitor", monitorId: grpcMonitorId, name: "gRPC component", order: 3, }) .returning() .get(); grpcComponentId = grpcComponent.id; });
afterAll(async () => { await db .delete(pageComponent) .where(eq(pageComponent.id, publicNameComponentId)); await db .delete(pageComponent) .where(eq(pageComponent.id, noDescriptionComponentId)); await db .delete(pageComponent) .where(eq(pageComponent.id, clearedDescriptionComponentId)); await db.delete(pageComponent).where(eq(pageComponent.id, grpcComponentId)); await db.delete(monitor).where(eq(monitor.id, grpcMonitorId)); await db.delete(monitor).where(eq(monitor.id, publicNameMonitorId)); await db.delete(monitor).where(eq(monitor.id, noDescriptionMonitorId)); await db.delete(monitor).where(eq(monitor.id, clearedDescriptionMonitorId)); await db.delete(page).where(eq(page.id, publicNamePageId)); });
test("get returns the page component name and description", async () => { const caller = await createCaller(); const result = await caller.statusPage.get({ slug: publicNameSlug });
const monitorItem = result?.monitors.find( (m) => m.id === publicNameMonitorId, );
expect(monitorItem?.name).toBe(componentName); expect(monitorItem?.description).toBe(componentDescription); expect(monitorItem?.name).not.toBe(internalName); expect(monitorItem?.name).not.toBe(legacyExternalName);
// the nested monitor relation must agree with the flat monitors array const component = result?.pageComponents.find( (c) => c.monitorId === publicNameMonitorId, ); expect(component?.monitor?.name).toBe(componentName); expect(component?.monitor?.description).toBe(componentDescription); });
test("getLight returns the page component name and description", async () => { const caller = await createCaller(); const result = await caller.statusPage.getLight({ slug: publicNameSlug });
const monitorItem = result?.monitors.find( (m) => m.id === publicNameMonitorId, );
expect(monitorItem?.name).toBe(componentName); expect(monitorItem?.description).toBe(componentDescription);
const component = result?.pageComponents.find( (c) => c.monitorId === publicNameMonitorId, ); expect(component?.monitor?.name).toBe(componentName); expect(component?.monitor?.description).toBe(componentDescription); });
test("falls back to the monitor description when the component has none", async () => { const caller = await createCaller(); const result = await caller.statusPage.get({ slug: publicNameSlug });
const monitorItem = result?.monitors.find( (m) => m.id === noDescriptionMonitorId, ); expect(monitorItem?.description).toBe(fallbackDescription);
const component = result?.pageComponents.find( (c) => c.monitorId === noDescriptionMonitorId, ); expect(component?.monitor?.description).toBe(fallbackDescription);
const light = await caller.statusPage.getLight({ slug: publicNameSlug }); const lightItem = light?.monitors.find( (m) => m.id === noDescriptionMonitorId, ); expect(lightItem?.description).toBe(fallbackDescription); });
test("keeps a deliberately cleared component description blank", async () => { const caller = await createCaller(); const result = await caller.statusPage.get({ slug: publicNameSlug });
const monitorItem = result?.monitors.find( (m) => m.id === clearedDescriptionMonitorId, ); expect(monitorItem?.description).toBe("");
const component = result?.pageComponents.find( (c) => c.monitorId === clearedDescriptionMonitorId, ); expect(component?.monitor?.description).toBe("");
const light = await caller.statusPage.getLight({ slug: publicNameSlug }); const lightItem = light?.monitors.find( (m) => m.id === clearedDescriptionMonitorId, ); expect(lightItem?.description).toBe(""); });
test("getUptime returns the page component name on the nested monitor", async () => { const caller = await createCaller(); const result = await caller.statusPage.getUptime({ slug: publicNameSlug, pageComponentIds: [String(publicNameComponentId)], });
const component = result?.find( (c) => c.pageComponentId === publicNameComponentId, );
expect(component?.name).toBe(componentName); expect(component?.monitor?.name).toBe(componentName); expect(component?.monitor?.description).toBe(componentDescription); });
test("getMonitor resolves metrics procedures for a gRPC monitor", async () => { const caller = await createCaller();
// Tinybird is noop under test, so a resolved dispatch and a missing one both // end up with empty chart data. What separates them is the TypeError that // indexing proceduresByType with an absent job type throws โ which // withTinybirdFallback catches and files as a Tinybird outage rather than // surfacing. Assert it never happens. const logged: string[] = []; const originalError = console.error; console.error = (...args: unknown[]) => { logged.push(args.map(String).join(" ")); };
let result: Awaited<ReturnType<typeof caller.statusPage.getMonitor>>; try { result = await caller.statusPage.getMonitor({ slug: publicNameSlug, id: grpcMonitorId, }); } finally { console.error = originalError; }
expect(result?.name).toBe("gRPC component"); expect( logged.filter((line) => line.includes("Cannot read properties of undefined"), ), ).toEqual([]); });
test("getMonitor returns the page component name and description", async () => { const caller = await createCaller(); const result = await caller.statusPage.getMonitor({ slug: publicNameSlug, id: publicNameMonitorId, });
expect(result?.name).toBe(componentName); expect(result?.description).toBe(componentDescription); });});