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 ยท 1696 lines
TypeScript
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697import { and, db, eq, isNotNull, isNull } from "@openstatus/db";import { monitorIncidentTable, monitor, page, pageComponent, pageSubscriber, workspace,} from "@openstatus/db/src/schema";import { expect } from "@std/expect";import { afterAll, beforeAll, describe, test } from "@std/testing/bdd";
/** * 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); });});