From a4944e4e82c35b427f2bd0be6f84583efd9ec856 Mon Sep 17 00:00:00 2001 From: John Yeo Date: Mon, 23 Feb 2026 16:32:42 +0000 Subject: [PATCH] chore: fixed tests --- server/src/init.ts | 2 - .../src/internal/debug/heapSnapshotRoute.ts | 50 +---------- server/src/utils/heapSnapshotHandler.ts | 87 ------------------- server/src/utils/logging/initLogger.ts | 1 + server/src/utils/memoryMonitor.ts | 16 ++-- server/src/workers.ts | 2 - .../stripe-webhooks/stripe-webhooks.test.ts | 1 + ...cription-deleted-invoice-discounts.test.ts | 6 +- .../subscription-deleted-invoice.test.ts | 6 +- 9 files changed, 18 insertions(+), 153 deletions(-) delete mode 100644 server/src/utils/heapSnapshotHandler.ts create mode 100644 server/tests/integration/billing/stripe-webhooks/stripe-webhooks.test.ts diff --git a/server/src/init.ts b/server/src/init.ts index 4862179af..41c0451eb 100644 --- a/server/src/init.ts +++ b/server/src/init.ts @@ -21,7 +21,6 @@ import { redirectToHono } from "./initHono.js"; import { auth } from "./utils/auth.js"; import { generateId } from "./utils/genUtils.js"; import { checkEnvVars } from "./utils/initUtils.js"; -import { registerHeapSnapshotHandler } from "./utils/heapSnapshotHandler.js"; import { startMemoryMonitor } from "./utils/memoryMonitor.js"; checkEnvVars(); @@ -143,7 +142,6 @@ const init = async () => { server.listen(PORT, "0.0.0.0", () => { console.log(`Server running on port ${PORT}`); startMemoryMonitor("server", 60_000); - registerHeapSnapshotHandler("server"); }); }; diff --git a/server/src/internal/debug/heapSnapshotRoute.ts b/server/src/internal/debug/heapSnapshotRoute.ts index a8115e25e..3f2823970 100644 --- a/server/src/internal/debug/heapSnapshotRoute.ts +++ b/server/src/internal/debug/heapSnapshotRoute.ts @@ -1,9 +1,6 @@ -import { writeFileSync, readFileSync, unlinkSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; import { Hono } from "hono"; -import { secretKeyMiddleware } from "@/honoMiddlewares/secretKeyMiddleware.js"; import { orgConfigMiddleware } from "@/honoMiddlewares/orgConfigMiddleware.js"; +import { secretKeyMiddleware } from "@/honoMiddlewares/secretKeyMiddleware.js"; import type { HonoEnv } from "@/honoUtils/HonoEnv.js"; const ALLOWED_ORG_IDS = new Set([ @@ -16,51 +13,6 @@ export const heapSnapshotRouter = new Hono(); heapSnapshotRouter.use("*", secretKeyMiddleware); heapSnapshotRouter.use("*", orgConfigMiddleware); -heapSnapshotRouter.get("/heap-snapshot", async (c) => { - const ctx = c.get("ctx"); - - if (!ALLOWED_ORG_IDS.has(ctx.org?.id)) { - return c.json({ error: "Forbidden" }, 403); - } - - // Bun-specific heap snapshot - if (typeof Bun !== "undefined" && typeof Bun.generateHeapSnapshot === "function") { - const snapshot = Bun.generateHeapSnapshot(); - return c.json({ - ok: true, - pid: process.pid, - timestamp: new Date().toISOString(), - snapshot, - }); - } - - // Node.js fallback using v8 - try { - const v8 = await import("node:v8"); - const snapshotPath = join(tmpdir(), `heap-${process.pid}-${Date.now()}.heapsnapshot`); - - v8.writeHeapSnapshot(snapshotPath); - - const data = readFileSync(snapshotPath); - unlinkSync(snapshotPath); - - return new Response(data, { - headers: { - "Content-Type": "application/json", - "Content-Disposition": `attachment; filename="heap-${process.pid}-${Date.now()}.heapsnapshot"`, - }, - }); - } catch (err) { - return c.json( - { - error: "Failed to generate heap snapshot", - detail: err instanceof Error ? err.message : String(err), - }, - 500, - ); - } -}); - heapSnapshotRouter.get("/memory", async (c) => { const ctx = c.get("ctx"); diff --git a/server/src/utils/heapSnapshotHandler.ts b/server/src/utils/heapSnapshotHandler.ts deleted file mode 100644 index 5752c4bc9..000000000 --- a/server/src/utils/heapSnapshotHandler.ts +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Registers a SIGUSR2 handler that takes a heap snapshot - * and sends it to Discord via webhook. - * - * Usage: kill -USR2 - * Or trigger from the cluster primary via IPC. - */ - -import { writeFileSync, readFileSync, unlinkSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; -import { logger } from "../external/logtail/logtailUtils.js"; - -const DISCORD_WEBHOOK_URL = process.env.DISCORD_FEEDBACK_WEBHOOK; - -async function sendToDiscord(filePath: string, label: string) { - if (!DISCORD_WEBHOOK_URL) { - logger.warn("DISCORD_FEEDBACK_WEBHOOK not configured, cannot send heap snapshot"); - return; - } - - const fileData = readFileSync(filePath); - const filename = `heap-${label}-pid${process.pid}-${Date.now()}.heapsnapshot`; - - const mem = process.memoryUsage(); - const toMB = (b: number) => (b / 1024 / 1024).toFixed(1); - - const summary = [ - `**Heap Snapshot — ${label} (pid ${process.pid})**`, - `RSS: ${toMB(mem.rss)}MB | Heap: ${toMB(mem.heapUsed)}/${toMB(mem.heapTotal)}MB`, - `External: ${toMB(mem.external)}MB | ArrayBuffers: ${toMB(mem.arrayBuffers)}MB`, - `File size: ${(fileData.length / 1024 / 1024).toFixed(1)}MB`, - "Open in Chrome DevTools → Memory → Load", - ].join("\n"); - - const formData = new FormData(); - formData.append("content", summary); - formData.append( - "files[0]", - new Blob([fileData], { type: "application/json" }), - filename, - ); - - try { - const res = await fetch(DISCORD_WEBHOOK_URL, { - method: "POST", - body: formData, - }); - - if (!res.ok) { - logger.error(`Failed to send heap snapshot to Discord: ${res.status} ${await res.text()}`); - } else { - logger.info(`Heap snapshot sent to Discord: ${filename}`); - } - } catch (err) { - logger.error(`Failed to send heap snapshot to Discord: ${err}`); - } -} - -export function registerHeapSnapshotHandler(label: string) { - process.on("SIGUSR2", async () => { - logger.info(`SIGUSR2 received — generating heap snapshot for ${label} (pid ${process.pid})`); - - const snapshotPath = join(tmpdir(), `heap-${label}-${process.pid}-${Date.now()}.heapsnapshot`); - - try { - if (typeof Bun !== "undefined" && typeof Bun.generateHeapSnapshot === "function") { - const snapshot = Bun.generateHeapSnapshot(); - writeFileSync(snapshotPath, JSON.stringify(snapshot)); - } else { - const v8 = await import("node:v8"); - v8.writeHeapSnapshot(snapshotPath); - } - - await sendToDiscord(snapshotPath, label); - - // Clean up - try { - unlinkSync(snapshotPath); - } catch {} - } catch (err) { - logger.error(`Failed to generate heap snapshot: ${err}`); - } - }); - - logger.info(`[${label}] Heap snapshot handler registered (send SIGUSR2 to pid ${process.pid})`); -} diff --git a/server/src/utils/logging/initLogger.ts b/server/src/utils/logging/initLogger.ts index 320f557bc..8c3b16b86 100644 --- a/server/src/utils/logging/initLogger.ts +++ b/server/src/utils/logging/initLogger.ts @@ -87,6 +87,7 @@ const createDevLogStream = () => { "workflow", "stripe_event", "extras", + "type", ]; const additionalFields = Object.keys(log) .filter((key) => !excludeFields.includes(key)) diff --git a/server/src/utils/memoryMonitor.ts b/server/src/utils/memoryMonitor.ts index 9c0978207..ef589ebfc 100644 --- a/server/src/utils/memoryMonitor.ts +++ b/server/src/utils/memoryMonitor.ts @@ -20,13 +20,15 @@ function logMemoryUsage(label: string) { logger.info("memory_log", { type: "memory_log", - label, - pid: process.pid, - rssMB: toMB(mem.rss), - heapUsedMB: toMB(mem.heapUsed), - heapTotalMB: toMB(mem.heapTotal), - externalMB: toMB(mem.external), - arrayBuffersMB: toMB(mem.arrayBuffers), + data: { + label, + pid: process.pid, + rssMB: toMB(mem.rss), + heapUsedMB: toMB(mem.heapUsed), + heapTotalMB: toMB(mem.heapTotal), + externalMB: toMB(mem.external), + arrayBuffersMB: toMB(mem.arrayBuffers), + }, }); } diff --git a/server/src/workers.ts b/server/src/workers.ts index af0a2a091..a08d7e212 100644 --- a/server/src/workers.ts +++ b/server/src/workers.ts @@ -19,7 +19,6 @@ const NUM_PROCESSES = process.env.NODE_ENV === "development" ? 1 : 4; // Track if we're shutting down let isShuttingDown = false; -import { registerHeapSnapshotHandler } from "./utils/heapSnapshotHandler.js"; import { startMemoryMonitor } from "./utils/memoryMonitor.js"; if (cluster.isPrimary) { @@ -98,7 +97,6 @@ if (cluster.isPrimary) { // Worker process console.log(`[Worker ${process.pid}] Starting queue consumer...`); startMemoryMonitor("worker", 60_000); - registerHeapSnapshotHandler("worker"); // Auto-detect which queue implementation to use if (process.env.SQS_QUEUE_URL) { diff --git a/server/tests/integration/billing/stripe-webhooks/stripe-webhooks.test.ts b/server/tests/integration/billing/stripe-webhooks/stripe-webhooks.test.ts new file mode 100644 index 000000000..06d46d16c --- /dev/null +++ b/server/tests/integration/billing/stripe-webhooks/stripe-webhooks.test.ts @@ -0,0 +1 @@ +// Root file for search diff --git a/server/tests/integration/billing/stripe-webhooks/subscription-deleted/subscription-deleted-invoice-discounts.test.ts b/server/tests/integration/billing/stripe-webhooks/subscription-deleted/subscription-deleted-invoice-discounts.test.ts index b92f561bb..c56a9c2c1 100644 --- a/server/tests/integration/billing/stripe-webhooks/subscription-deleted/subscription-deleted-invoice-discounts.test.ts +++ b/server/tests/integration/billing/stripe-webhooks/subscription-deleted/subscription-deleted-invoice-discounts.test.ts @@ -139,7 +139,7 @@ test.concurrent(`${chalk.yellowBright("sub.deleted discount: customer-level disc stripeCli: ctx.stripeCli, testClockId: testClockId!, advanceTo: addMonths(new Date(), 1).getTime(), - waitForSeconds: 15, + waitForSeconds: 30, }); // Verify product is removed from entity @@ -262,7 +262,7 @@ test.concurrent(`${chalk.yellowBright("sub.deleted discount: subscription-level stripeCli: ctx.stripeCli, testClockId: testClockId!, advanceTo: addMonths(new Date(), 1).getTime(), - waitForSeconds: 15, + waitForSeconds: 30, }); // Verify product is removed from entity @@ -538,7 +538,7 @@ test.concurrent(`${chalk.yellowBright("sub.deleted discount: consumable price on stripeCli: ctx.stripeCli, testClockId: testClockId!, advanceTo: addMonths(new Date(), 1).getTime(), - waitForSeconds: 15, + waitForSeconds: 30, }); // Verify product is removed from entity diff --git a/server/tests/integration/billing/stripe-webhooks/subscription-deleted/subscription-deleted-invoice.test.ts b/server/tests/integration/billing/stripe-webhooks/subscription-deleted/subscription-deleted-invoice.test.ts index b6832e4a1..a0b202bec 100644 --- a/server/tests/integration/billing/stripe-webhooks/subscription-deleted/subscription-deleted-invoice.test.ts +++ b/server/tests/integration/billing/stripe-webhooks/subscription-deleted/subscription-deleted-invoice.test.ts @@ -321,7 +321,7 @@ test.concurrent(`${chalk.yellowBright("sub.deleted invoice: multi-interval → a stripeCli: ctx.stripeCli, testClockId: testClockId!, advanceTo: addMonths(new Date(), 1).getTime(), - waitForSeconds: 15, + waitForSeconds: 30, }); // Get invoice count after 1 month advance @@ -425,7 +425,7 @@ test.concurrent(`${chalk.yellowBright("sub.deleted invoice: entity consumable stripeCli: ctx.stripeCli, testClockId: testClockId!, advanceTo: addMonths(new Date(), 1).getTime(), - waitForSeconds: 15, + waitForSeconds: 30, }); // Track 500 messages on entity in the new cycle (100 included, 400 overage) @@ -808,7 +808,7 @@ test.concurrent(`${chalk.yellowBright("sub.deleted invoice: entity consumable stripeCli: ctx.stripeCli, testClockId: testClockId!, advanceTo: addMonths(new Date(), 1).getTime(), - waitForSeconds: 15, + waitForSeconds: 30, }); // Verify product is removed from entity