chore: fixed tests
This commit is contained in:
@@ -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");
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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})`);
|
||||
}
|
||||
@@ -87,6 +87,7 @@ const createDevLogStream = () => {
|
||||
"workflow",
|
||||
"stripe_event",
|
||||
"extras",
|
||||
"type",
|
||||
];
|
||||
const additionalFields = Object.keys(log)
|
||||
.filter((key) => !excludeFields.includes(key))
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
// Root file for search
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user