Skip to content
Merged
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
16 changes: 16 additions & 0 deletions .changeset/soft-tigers-beg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"wrangler": minor
---

feature: add a `ctx` field to the `getBindingsProxy` result

Add a new `ctx` filed to the `getBindingsProxy` result that people can use to mock the production
`ExecutionContext` object.

Example:

```ts
const { ctx } = await getBindingsProxy();
// ...
ctx.waitUntil(myPromise);
```
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
Fetcher,
R2Bucket,
} from "@cloudflare/workers-types";
import { Request, Response } from "undici";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import {
getBindingsProxy as originalGetBindingsProxy,
Expand Down Expand Up @@ -42,7 +41,7 @@ function getBindingsProxy<T>(
});
}

describe("getBindingsProxy", () => {
describe("getBindingsProxy - bindings", () => {
let devWorkers: UnstableDevWorker[];

beforeAll(async () => {
Expand Down Expand Up @@ -228,25 +227,6 @@ describe("getBindingsProxy", () => {
await dispose();
}
});

describe("caches", () => {
(["default", "named"] as const).forEach((cacheType) =>
it(`correctly obtains a no-op ${cacheType} cache`, async () => {
const { caches, dispose } = await getBindingsProxy<Bindings>({
configPath: wranglerTomlFilePath,
});
try {
const cache =
cacheType === "default"
? caches.default
: await caches.open("my-cache");
testNoOpCache(cache);
} finally {
await dispose();
}
})
);
});
});

/**
Expand Down Expand Up @@ -283,17 +263,3 @@ async function testDoBinding(
const doRespText = await doResp.text();
expect(doRespText).toBe(expectedResponse);
}

async function testNoOpCache(
cache: Awaited<ReturnType<typeof getBindingsProxy>>["caches"]["default"]
) {
let match = await cache.match("http://0.0.0.0/test");
expect(match).toBeUndefined();

const req = new Request("http://0.0.0.0/test");
await cache.put(req, new Response("test"));
const resp = await cache.match(req);
expect(resp).toBeUndefined();
const deleted = await cache.delete(req);
expect(deleted).toBe(false);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Request, Response } from "undici";
import { describe, expect, it } from "vitest";
import { getBindingsProxy } from "./shared";

describe("getBindingsProxy - caches", () => {
(["default", "named"] as const).forEach((cacheType) =>
it(`correctly obtains a no-op ${cacheType} cache`, async () => {
const { caches, dispose } = await getBindingsProxy();
try {
const cache =
cacheType === "default"
? caches.default
: await caches.open("my-cache");
testNoOpCache(cache);
} finally {
await dispose();
}
})
);
});

async function testNoOpCache(
cache: Awaited<ReturnType<typeof getBindingsProxy>>["caches"]["default"]
) {
let match = await cache.match("http://0.0.0.0/test");
expect(match).toBeUndefined();

const req = new Request("http://0.0.0.0/test");
await cache.put(req, new Response("test"));
const resp = await cache.match(req);
expect(resp).toBeUndefined();
const deleted = await cache.delete(req);
expect(deleted).toBe(false);
}
55 changes: 55 additions & 0 deletions fixtures/get-bindings-proxy/tests/get-bindings-proxy.ctx.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { describe, expect, it } from "vitest";
import { getBindingsProxy } from "./shared";

describe("getBindingsProxy - ctx", () => {
it("should provide a no-op waitUntil method", async () => {
const { ctx, dispose } = await getBindingsProxy();
try {
let value = 4;
ctx.waitUntil(
new Promise((resolve) => {
value++;
resolve(value);
})
);
expect(value).toBe(5);
} finally {
await dispose();
}
});

it("should provide a no-op passThroughOnException method", async () => {
const { ctx, dispose } = await getBindingsProxy();
try {
expect(ctx.passThroughOnException()).toBe(undefined);
} finally {
await dispose();
}
});

it("should match the production runtime ctx object", async () => {
const { ctx, dispose } = await getBindingsProxy();
try {
expect(ctx.constructor.name).toBe("ExecutionContext");
expect(typeof ctx.waitUntil).toBe("function");
expect(typeof ctx.passThroughOnException).toBe("function");

ctx.waitUntil = ((str: string) => `- ${str} -`) as any;
expect(ctx.waitUntil("waitUntil can be overridden" as any)).toBe(
"- waitUntil can be overridden -"
);

ctx.passThroughOnException = ((str: string) => `_ ${str} _`) as any;
expect(
(ctx.passThroughOnException as any)(
"passThroughOnException can be overridden"
)
).toBe("_ passThroughOnException can be overridden _");

(ctx as any).text = "the ExecutionContext can be extended";
expect((ctx as any).text).toBe("the ExecutionContext can be extended");
} finally {
await dispose();
}
});
});
13 changes: 13 additions & 0 deletions fixtures/get-bindings-proxy/tests/shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { getBindingsProxy as originalGetBindingsProxy } from "wrangler";
import type { GetBindingsProxyOptions } from "wrangler";

// Here we wrap the actual original getBindingsProxy function and disable its persistance, this is to make sure
// that we don't implement any persistance during these tests (which would add unnecessary extra complexity)
export function getBindingsProxy<T>(
options: Omit<GetBindingsProxyOptions, "persist"> = {}
): ReturnType<typeof originalGetBindingsProxy<T>> {
return originalGetBindingsProxy({
...options,
persist: false,
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class ExecutionContext {
// eslint-disable-next-line @typescript-eslint/no-explicit-any, unused-imports/no-unused-vars
waitUntil(promise: Promise<any>): void {}
passThroughOnException(): void {}
}
6 changes: 6 additions & 0 deletions packages/wrangler/src/api/integrations/bindings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getBoundRegisteredWorkers } from "../../../dev-registry";
import { getVarsForDev } from "../../../dev/dev-vars";
import { buildMiniflareBindingOptions } from "../../../dev/miniflare";
import { CacheStorage } from "./caches";
import { ExecutionContext } from "./executionContext";
import { getServiceBindings } from "./services";
import type { Config } from "../../../config";
import type { MiniflareOptions } from "miniflare";
Expand Down Expand Up @@ -40,6 +41,10 @@ export type BindingsProxy<Bindings = Record<string, unknown>> = {
* Object containing the various proxies
*/
bindings: Bindings;
/**
* Mock of the context object that Workers received in their request handler, all the object's methods are no-op
*/
ctx: ExecutionContext;
/**
* Caches object emulating the Workers Cache runtime API
*/
Expand Down Expand Up @@ -88,6 +93,7 @@ export async function getBindingsProxy<Bindings = Record<string, unknown>>(
...vars,
...bindings,
},
ctx: new ExecutionContext(),
caches: new CacheStorage(),
dispose: () => mf.dispose(),
};
Expand Down