Files
triggerdotdev--trigger.dev/apps/webapp/test/mollifierDrainerWorker.test.ts
T
2026-07-13 13:32:57 +08:00

86 lines
3.5 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
// Importing `~/v3/mollifier/mollifierDrainer.server` (below) transitively
// loads `~/v3/runEngine.server`, whose top-level `singleton(...)` call
// eagerly constructs a RunEngine. That spins up Prisma + Redis workers
// that try to connect to localhost — which in CI (no PG, no Redis)
// produces an unhandled `PrismaClientInitializationError` that fails
// the test run even though the assertions all pass. Mocking the
// runEngine module short-circuits the singleton so no worker starts.
vi.mock("~/v3/runEngine.server", () => ({ engine: {} }));
// Same problem: prisma.server.ts's top-level singleton tries to open a
// PG client. The test never makes a query; an empty stub is enough.
vi.mock("~/db.server", () => ({ prisma: {}, $replica: {} }));
import { MollifierConfigurationError } from "~/v3/mollifier/mollifierDrainer.server";
import { initMollifierDrainerWorker } from "~/v3/mollifierDrainerWorker.server";
// Pins the error-classification policy inside the bootstrap's catch:
// deterministic misconfig errors propagate (so a deploy fails loud
// rather than silently disabling the drainer), and anything else is
// logged-and-swallowed (so a transient Redis blip during boot doesn't
// take the whole webapp down). The corresponding production-path
// integration is the call at `entry.server.tsx`: a sync throw out of
// `initMollifierDrainerWorker` propagates to the module top level
// BEFORE `process.on("uncaughtException", ...)` is registered, so Node
// crashes with a stack trace and exit code 1 — which is exactly what we
// want from the orchestrator's health-check perspective.
describe("initMollifierDrainerWorker error classification", () => {
it("rethrows MollifierConfigurationError so the process can crash on misconfig", () => {
const misconfig = new MollifierConfigurationError(
"TRIGGER_MOLLIFIER_DRAIN_SHUTDOWN_TIMEOUT_MS must be at least 1000ms below GRACEFUL_SHUTDOWN_TIMEOUT"
);
expect(() =>
initMollifierDrainerWorker({
isEnabled: () => true,
getDrainer: () => {
throw misconfig;
},
})
).toThrow(MollifierConfigurationError);
});
it("rethrows when the error carries the marker name even if instanceof fails (dev-realm hot-reload fallback)", () => {
// Simulate the cross-realm case where the consumer's instanceof
// check sees a different class instance from the one the throw
// site used. The bootstrap's `.name === "MollifierConfigurationError"`
// fallback must catch this so dev hot-reload doesn't silently
// suppress misconfig errors.
const cousin = new Error("buffer not initialised");
cousin.name = "MollifierConfigurationError";
expect(() =>
initMollifierDrainerWorker({
isEnabled: () => true,
getDrainer: () => {
throw cousin;
},
})
).toThrow(cousin);
});
it("swallows non-configuration errors so transient init failures don't take the webapp down", () => {
expect(() =>
initMollifierDrainerWorker({
isEnabled: () => true,
getDrainer: () => {
throw new Error("transient redis blip during buffer init");
},
})
).not.toThrow();
});
it("is a no-op when the drainer is disabled for this replica", () => {
let factoryCalled = false;
initMollifierDrainerWorker({
isEnabled: () => false,
getDrainer: () => {
factoryCalled = true;
return null;
},
});
expect(factoryCalled).toBe(false);
});
});