Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/vitest-pool-workers-reset-ratelimit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@cloudflare/vitest-pool-workers": patch
"miniflare": patch
---

`reset()` from `cloudflare:test` now resets ratelimit binding state between tests. Previously, `RATE_LIMITERS` bindings retained their in-memory bucket counts across test boundaries, causing later tests in the same file to see stale rate-limit exhaustion state.
Comment thread
matingathani marked this conversation as resolved.

1 change: 1 addition & 0 deletions fixtures/vitest-pool-workers-examples/reset/src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ interface __BaseEnv_Env {
R2_BUCKET: R2Bucket;
DATABASE: D1Database;
COUNTER: DurableObjectNamespace<import("./index").Counter>;
RATE_LIMITER: RateLimit;
Comment on lines 5 to +8

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Generated env.d.ts appears manually edited rather than regenerated

The env.d.ts file at fixtures/vitest-pool-workers-examples/reset/src/env.d.ts has a generated-file header with hash 524c60174bc1732153e84c1b8699023e (line 2), but the new RATE_LIMITER: RateLimit binding on line 8 was added without updating the hash. Since wrangler types supports ratelimit bindings (see packages/wrangler/src/type-generation/index.ts:2329), running it after updating wrangler.jsonc would regenerate this file with a correct hash. The stale hash suggests manual editing. AGENTS.md says 'Never modify generated files directly — modify the generator or config, then regenerate.' This is in a test fixture so the practical impact is minimal, but it could cause confusion if the file is later regenerated and produces unexpected diffs.

(Refers to lines 2-8)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

}
declare namespace Cloudflare {
interface GlobalProps {
Expand Down
21 changes: 21 additions & 0 deletions fixtures/vitest-pool-workers-examples/reset/test/reset.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,24 @@ it("sees reset Durable Object storage after reset", async ({ expect }) => {
const response = await stub.fetch("https://example.com");
expect(await response.text()).toBe("1");
});

it("exhausts ratelimit then resets between tests", async ({ expect }) => {
// First call succeeds
const first = await env.RATE_LIMITER.limit({ key: "test-key" });
expect(first.success).toBe(true);

// Exhaust the full limit of 100
for (let i = 1; i < 100; i++) {
await env.RATE_LIMITER.limit({ key: "test-key" });
}

// 101st call should fail
const over = await env.RATE_LIMITER.limit({ key: "test-key" });
expect(over.success).toBe(false);
});

it("sees reset ratelimit state after reset", async ({ expect }) => {
// After reset(), buckets should be cleared — first call succeeds again
const result = await env.RATE_LIMITER.limit({ key: "test-key" });
expect(result.success).toBe(true);
});
7 changes: 7 additions & 0 deletions fixtures/vitest-pool-workers-examples/reset/wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,11 @@
"database_id": "00000000-0000-0000-0000-000000000000",
},
],
"ratelimits": [
{
"name": "RATE_LIMITER",
"namespace_id": "1",
"simple": { "limit": 100, "period": 60 },
},
],
}
23 changes: 22 additions & 1 deletion packages/miniflare/src/workers/ratelimit/ratelimit.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ class Ratelimit {
this.epoch = 0;
}

// Resets the in-memory bucket state so tests start from a clean slate.
reset(): void {
this.buckets.clear();
this.epoch = 0;
}

// method that counts and checks against the limit in in-memory buckets
async limit(options: unknown): Promise<RatelimitResult> {
// validate options input
Expand Down Expand Up @@ -86,7 +92,22 @@ class Ratelimit {
}
}

// Module-level set tracking all instances created during this Worker lifetime.
// The ratelimit extension module is marked `internal: true` in workerd, making
// direct import impossible from outside. We expose a single reset function via
// globalThis so vitest-pool-workers' reset() can invoke it without a global
// variable that leaks to user code (the function is set once on module load).
const instances = new Set<Ratelimit>();
(globalThis as { __cfRatelimitReset__?: () => void }).__cfRatelimitReset__ =
() => {
for (const instance of instances) {
instance.reset();
}
};

// create a new Ratelimit
export default function (env: RatelimitConfig) {
return new Ratelimit(env);
const instance = new Ratelimit(env);
instances.add(instance);
return instance;
}
8 changes: 8 additions & 0 deletions packages/vitest-pool-workers/src/worker/reset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ import workerdUnsafe from "workerd:unsafe";

export async function reset(): Promise<void> {
await workerdUnsafe.deleteAllDurableObjects();

// Reset ratelimit binding state. The miniflare ratelimit extension module is
// marked `internal: true` in workerd, so it cannot be imported directly.
// Instead the module registers a reset function on globalThis when loaded,
// which is a no-op when no RATE_LIMITERS bindings are configured.
const resetRatelimits = (globalThis as { __cfRatelimitReset__?: () => void })
.__cfRatelimitReset__;
resetRatelimits?.();
}

export async function abortAllDurableObjects(): Promise<void> {
Expand Down
Loading