chore: fixed tests

This commit is contained in:
John Yeo
2026-02-23 16:32:42 +00:00
parent 9d320b25c9
commit a4944e4e82
9 changed files with 18 additions and 153 deletions

View File

@@ -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");
});
};

View File

@@ -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<HonoEnv>();
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");

View File

@@ -1,87 +0,0 @@
/**
* Registers a SIGUSR2 handler that takes a heap snapshot
* and sends it to Discord via webhook.
*
* Usage: kill -USR2 <pid>
* 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})`);
}

View File

@@ -87,6 +87,7 @@ const createDevLogStream = () => {
"workflow",
"stripe_event",
"extras",
"type",
];
const additionalFields = Object.keys(log)
.filter((key) => !excludeFields.includes(key))

View File

@@ -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),
},
});
}

View File

@@ -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) {

View File

@@ -0,0 +1 @@
// Root file for search

View File

@@ -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

View File

@@ -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