70bf21e064
Deploy (to testing) and Test Playground Preview Worker / Deploy Playground Preview Worker (testing) (push) Has been skipped
Deploy Workers Shared Staging / Deploy Workers Shared Staging (push) Failing after 0s
Prerelease / build (push) Has been skipped
Handle Changesets / Handle Changesets (push) Has been cancelled
Semgrep OSS scan / semgrep-oss (push) Has been cancelled
334 lines
9.6 KiB
TypeScript
334 lines
9.6 KiB
TypeScript
import { readFile, writeFile } from "node:fs/promises";
|
|
import { resolve } from "node:path";
|
|
import { setTimeout } from "node:timers/promises";
|
|
import dedent from "ts-dedent";
|
|
import { beforeAll, describe, it } from "vitest";
|
|
import { CLOUDFLARE_ACCOUNT_ID } from "../helpers/account-id";
|
|
import { WranglerE2ETestHelper } from "../helpers/e2e-wrangler-test";
|
|
import { fetchText } from "../helpers/fetch-text";
|
|
import { normalizeOutput } from "../helpers/normalize";
|
|
import { makeRoot, seed } from "../helpers/setup";
|
|
import { waitFor, waitForLong } from "../helpers/wait-for";
|
|
|
|
describe.skipIf(!CLOUDFLARE_ACCOUNT_ID)(
|
|
"wrangler dev - remote bindings",
|
|
() => {
|
|
const remoteWorkerName = "preserve-e2e-wrangler-remote-worker";
|
|
const alternativeRemoteWorkerName =
|
|
"preserve-e2e-wrangler-remote-worker-alt";
|
|
const helper = new WranglerE2ETestHelper();
|
|
|
|
beforeAll(async () => {
|
|
await helper.seed(resolve(__dirname, "./workers"));
|
|
await Promise.all([
|
|
helper.ensureWorkerDeployed({
|
|
entryPoint: "remote-worker.js",
|
|
workerName: remoteWorkerName,
|
|
}),
|
|
helper.ensureWorkerDeployed({
|
|
entryPoint: "alt-remote-worker.js",
|
|
workerName: alternativeRemoteWorkerName,
|
|
}),
|
|
]);
|
|
}, 35_000);
|
|
|
|
it("handles both remote and local service bindings at the same time", async ({
|
|
expect,
|
|
}) => {
|
|
await spawnLocalWorker(helper);
|
|
await helper.seed({
|
|
"wrangler.json": JSON.stringify({
|
|
name: "remote-bindings-mixed-bindings-test",
|
|
main: "local-and-remote-service-bindings.js",
|
|
compatibility_date: "2025-05-07",
|
|
services: [
|
|
{ binding: "LOCAL_WORKER", service: "local-worker", remote: false },
|
|
{
|
|
binding: "REMOTE_WORKER",
|
|
service: remoteWorkerName,
|
|
remote: true,
|
|
},
|
|
],
|
|
}),
|
|
});
|
|
|
|
const worker = helper.runLongLived("wrangler dev");
|
|
|
|
const { url } = await worker.waitForReady();
|
|
|
|
await expect(fetchText(url)).resolves.toMatchInlineSnapshot(`
|
|
"LOCAL<WORKER>: Hello from a local worker!
|
|
REMOTE<WORKER>: Hello from a remote worker
|
|
"
|
|
`);
|
|
});
|
|
|
|
it("allows code changes during development", async ({ expect }) => {
|
|
await spawnLocalWorker(helper);
|
|
await helper.seed({
|
|
"wrangler.json": JSON.stringify({
|
|
name: "remote-bindings-mixed-bindings-test",
|
|
main: "simple-service-binding.js",
|
|
compatibility_date: "2025-05-07",
|
|
services: [
|
|
{
|
|
binding: "REMOTE_WORKER",
|
|
service: remoteWorkerName,
|
|
remote: true,
|
|
},
|
|
],
|
|
}),
|
|
});
|
|
|
|
const worker = helper.runLongLived("wrangler dev");
|
|
|
|
const { url } = await worker.waitForReady();
|
|
|
|
await expect(fetchText(url)).resolves.toMatchInlineSnapshot(
|
|
`"REMOTE<WORKER>: Hello from a remote worker"`
|
|
);
|
|
|
|
const indexContent = await readFile(
|
|
`${helper.tmpPath}/simple-service-binding.js`,
|
|
"utf8"
|
|
);
|
|
await writeFile(
|
|
`${helper.tmpPath}/simple-service-binding.js`,
|
|
indexContent.replace(
|
|
"REMOTE<WORKER>:",
|
|
"The remote worker responded with:"
|
|
),
|
|
"utf8"
|
|
);
|
|
|
|
await setTimeout(500);
|
|
|
|
await expect(fetchText(url)).resolves.toMatchInlineSnapshot(
|
|
`"The remote worker responded with: Hello from a remote worker"`
|
|
);
|
|
|
|
await writeFile(
|
|
`${helper.tmpPath}/simple-service-binding.js`,
|
|
indexContent,
|
|
"utf8"
|
|
);
|
|
|
|
await setTimeout(500);
|
|
|
|
await expect(fetchText(url)).resolves.toMatchInlineSnapshot(
|
|
`"REMOTE<WORKER>: Hello from a remote worker"`
|
|
);
|
|
});
|
|
|
|
it("handles workers AI alongside a local service binding", async ({
|
|
expect,
|
|
}) => {
|
|
await spawnLocalWorker(helper);
|
|
await helper.seed({
|
|
"wrangler.json": JSON.stringify({
|
|
name: "remote-bindings-mixed-bindings-test",
|
|
main: "local-service-binding-and-remote-ai.js",
|
|
compatibility_date: "2025-05-07",
|
|
ai: {
|
|
binding: "AI",
|
|
},
|
|
services: [
|
|
{ binding: "LOCAL_WORKER", service: "local-worker", remote: false },
|
|
],
|
|
}),
|
|
});
|
|
|
|
const worker = helper.runLongLived("wrangler dev");
|
|
|
|
const { url } = await worker.waitForReady();
|
|
|
|
await expect(fetchText(url)).resolves.toMatchInlineSnapshot(`
|
|
"LOCAL<WORKER>: Hello from a local worker!
|
|
REMOTE<AI>: This is a response from Workers AI.
|
|
"
|
|
`);
|
|
});
|
|
|
|
it("should only include logs from the user Wrangler session (i.e. a single list of attached bindings, and only one ready message)", async ({
|
|
expect,
|
|
}) => {
|
|
await spawnLocalWorker(helper);
|
|
await helper.seed({
|
|
"wrangler.json": JSON.stringify({
|
|
name: "remote-bindings-mixed-bindings-test",
|
|
main: "ai.js",
|
|
compatibility_date: "2025-05-07",
|
|
ai: {
|
|
binding: "AI",
|
|
},
|
|
}),
|
|
});
|
|
|
|
const worker = helper.runLongLived("wrangler dev");
|
|
|
|
const { url } = await worker.waitForReady();
|
|
|
|
await expect(fetchText(url)).resolves.toContain(
|
|
"This is a response from Workers AI."
|
|
);
|
|
|
|
// This should only include logs from the user Wrangler session (i.e. a single list of attached bindings, and only one ready message)
|
|
await waitFor(() =>
|
|
expect(normalizeOutput(worker.currentOutput)).toEqual(dedent`
|
|
Your Worker has access to the following bindings:
|
|
Binding Resource Mode
|
|
env.AI AI remote
|
|
⎔ Establishing remote connection...
|
|
▲ [WARNING] AI bindings always access remote resources, and so may incur usage charges even in local dev. To suppress this warning, set \`remote: true\` for the binding definition in your configuration file.
|
|
⎔ Starting local server...
|
|
[wrangler:info] Ready on http://<HOST>:<PORT>
|
|
[wrangler:info] GET / 200 OK (TIMINGS)
|
|
`)
|
|
);
|
|
});
|
|
|
|
describe("shows helpful error logs", () => {
|
|
it("when a remote service binding is not properly configured", async ({
|
|
expect,
|
|
}) => {
|
|
await helper.seed({
|
|
"wrangler.json": JSON.stringify({
|
|
name: "remote-bindings-mixed-bindings-test",
|
|
main: "simple-service-binding.js",
|
|
compatibility_date: "2025-05-07",
|
|
services: [
|
|
{
|
|
binding: "REMOTE_WORKER",
|
|
service: "non-existent-service-binding",
|
|
remote: true,
|
|
},
|
|
],
|
|
}),
|
|
});
|
|
|
|
const worker = helper.runLongLived("wrangler dev");
|
|
|
|
// The error appears only after the remote proxy session validates
|
|
// the binding against the Cloudflare API, so use the longer timeout.
|
|
await waitForLong(() =>
|
|
expect(worker.currentOutput).toContain(
|
|
"Service binding 'REMOTE_WORKER' references Worker 'non-existent-service-binding' which was not found."
|
|
)
|
|
);
|
|
});
|
|
|
|
it("when a remote KV binding is not properly configured", async ({
|
|
expect,
|
|
}) => {
|
|
await helper.seed({
|
|
"wrangler.json": JSON.stringify({
|
|
name: "remote-bindings-mixed-bindings-test",
|
|
main: "kv.js",
|
|
compatibility_date: "2025-05-07",
|
|
kv_namespaces: [
|
|
{
|
|
binding: "KV_BINDING",
|
|
id: "non-existent-kv",
|
|
remote: true,
|
|
},
|
|
],
|
|
}),
|
|
});
|
|
|
|
const worker = helper.runLongLived("wrangler dev");
|
|
|
|
// The error appears only after the remote proxy session validates
|
|
// the binding against the Cloudflare API, so use the longer timeout.
|
|
await waitForLong(() =>
|
|
expect(worker.currentOutput).toContain(
|
|
"KV namespace 'non-existent-kv' is not valid."
|
|
)
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("multi-worker", () => {
|
|
it("handles both remote and local service bindings at the same time in all workers", async ({
|
|
expect,
|
|
}) => {
|
|
await helper.seed({
|
|
"wrangler.json": JSON.stringify({
|
|
name: "remote-bindings-mixed-bindings-multi-worker-test",
|
|
main: "local-and-remote-service-bindings.js",
|
|
compatibility_date: "2025-05-07",
|
|
services: [
|
|
{
|
|
binding: "LOCAL_WORKER",
|
|
service: "local-test-worker",
|
|
remote: false,
|
|
},
|
|
{
|
|
binding: "REMOTE_WORKER",
|
|
service: remoteWorkerName,
|
|
remote: true,
|
|
},
|
|
],
|
|
}),
|
|
});
|
|
const localTest = makeRoot();
|
|
await seed(localTest, {
|
|
"wrangler.json": JSON.stringify({
|
|
name: "local-test-worker",
|
|
main: "index.js",
|
|
compatibility_date: "2025-05-07",
|
|
services: [
|
|
{
|
|
// Note: we use the same binding name but bound to a difference service
|
|
binding: "REMOTE_WORKER",
|
|
service: alternativeRemoteWorkerName,
|
|
remote: true,
|
|
},
|
|
],
|
|
}),
|
|
"index.js": dedent`
|
|
export default {
|
|
async fetch(request, env) {
|
|
const remoteWorkerText = await (await env.REMOTE_WORKER.fetch(request)).text();
|
|
return new Response(\`[local-test-worker]REMOTE<WORKER>: \${remoteWorkerText}\`);
|
|
}
|
|
}`,
|
|
});
|
|
|
|
const worker = helper.runLongLived(
|
|
`wrangler dev -c wrangler.json -c ${localTest}/wrangler.json`
|
|
);
|
|
|
|
// Multi-worker with remote bindings on both workers requires two
|
|
// serialised remote proxy sessions (API calls) before "Ready on"
|
|
// can appear, so give it more time than the default 15s.
|
|
const { url } = await worker.waitForReady(30_000);
|
|
|
|
await expect(fetchText(url)).resolves.toMatchInlineSnapshot(`
|
|
"LOCAL<WORKER>: [local-test-worker]REMOTE<WORKER>: Hello from an alternative remote worker
|
|
REMOTE<WORKER>: Hello from a remote worker
|
|
"
|
|
`);
|
|
});
|
|
});
|
|
}
|
|
);
|
|
|
|
async function spawnLocalWorker(helper: WranglerE2ETestHelper): Promise<void> {
|
|
const local = makeRoot();
|
|
await seed(local, {
|
|
"wrangler.json": JSON.stringify({
|
|
name: "local-worker",
|
|
main: "index.js",
|
|
compatibility_date: "2025-05-07",
|
|
}),
|
|
"index.js": dedent`
|
|
export default {
|
|
fetch(request) {
|
|
return new Response("Hello from a local worker!");
|
|
}
|
|
}`,
|
|
});
|
|
const localWorker = helper.runLongLived("wrangler dev", { cwd: local });
|
|
await localWorker.waitForReady();
|
|
}
|