88 lines
3.2 KiB
TypeScript
88 lines
3.2 KiB
TypeScript
import { redisTest } from "@internal/testcontainers";
|
|
import { MollifierBuffer } from "@trigger.dev/redis-worker";
|
|
import { describe, expect, vi } from "vitest";
|
|
import { createRealTripEvaluator } from "~/v3/mollifier/mollifierTripEvaluator.server";
|
|
|
|
vi.setConfig({ testTimeout: 30_000 });
|
|
|
|
// Use a real MollifierBuffer backed by a Redis testcontainer — repo policy
|
|
// is no mocks for Redis. Per-test envIds keep keys disjoint without explicit
|
|
// cleanup. We close() the buffer in a finally to release the client.
|
|
const inputs = { envId: "env_a", orgId: "org_1", taskId: "t1" } as const;
|
|
|
|
describe("createRealTripEvaluator", () => {
|
|
redisTest(
|
|
"returns divert=false when the sliding window stays under threshold",
|
|
async ({ redisOptions }) => {
|
|
const buffer = new MollifierBuffer({ redisOptions });
|
|
try {
|
|
const evaluator = createRealTripEvaluator({
|
|
getBuffer: () => buffer,
|
|
options: () => ({ windowMs: 1000, threshold: 100, holdMs: 500 }),
|
|
});
|
|
|
|
const decision = await evaluator({ ...inputs, envId: "env_under" });
|
|
expect(decision).toEqual({ divert: false });
|
|
} finally {
|
|
await buffer.close();
|
|
}
|
|
}
|
|
);
|
|
|
|
redisTest(
|
|
"returns divert=true with reason per_env_rate once the window trips",
|
|
async ({ redisOptions }) => {
|
|
const buffer = new MollifierBuffer({ redisOptions });
|
|
try {
|
|
// threshold=2 → the 3rd call within windowMs is the first that trips.
|
|
const options = { windowMs: 5000, threshold: 2, holdMs: 5000 } as const;
|
|
const evaluator = createRealTripEvaluator({
|
|
getBuffer: () => buffer,
|
|
options: () => options,
|
|
});
|
|
|
|
const envId = "env_trip";
|
|
await evaluator({ ...inputs, envId });
|
|
await evaluator({ ...inputs, envId });
|
|
const decision = await evaluator({ ...inputs, envId });
|
|
|
|
expect(decision.divert).toBe(true);
|
|
if (decision.divert) {
|
|
expect(decision.reason).toBe("per_env_rate");
|
|
expect(decision.threshold).toBe(options.threshold);
|
|
expect(decision.windowMs).toBe(options.windowMs);
|
|
expect(decision.holdMs).toBe(options.holdMs);
|
|
expect(decision.count).toBeGreaterThan(options.threshold);
|
|
}
|
|
} finally {
|
|
await buffer.close();
|
|
}
|
|
}
|
|
);
|
|
|
|
redisTest("returns divert=false when getBuffer returns null (fail-open)", async () => {
|
|
const evaluator = createRealTripEvaluator({
|
|
getBuffer: () => null,
|
|
options: () => ({ windowMs: 200, threshold: 100, holdMs: 500 }),
|
|
});
|
|
|
|
const decision = await evaluator(inputs);
|
|
expect(decision).toEqual({ divert: false });
|
|
});
|
|
|
|
redisTest("returns divert=false when buffer throws (fail-open)", async ({ redisOptions }) => {
|
|
const buffer = new MollifierBuffer({ redisOptions });
|
|
// Closing the client up front means evaluateTrip will throw on the first
|
|
// Redis command — a real failure mode, not a stub.
|
|
await buffer.close();
|
|
|
|
const evaluator = createRealTripEvaluator({
|
|
getBuffer: () => buffer,
|
|
options: () => ({ windowMs: 200, threshold: 100, holdMs: 500 }),
|
|
});
|
|
|
|
const decision = await evaluator(inputs);
|
|
expect(decision).toEqual({ divert: false });
|
|
});
|
|
});
|