import path from "path"; import { D1Database, R2Bucket } from "@cloudflare/workers-types"; import { toMatchImageSnapshot } from "jest-image-snapshot"; import { beforeEach, describe, it, MockInstance, vi } from "vitest"; import { getPlatformProxy } from "./shared"; import type { Fetcher, Hyperdrive, ImagesBinding, KVNamespace, Workflow, } from "@cloudflare/workers-types"; type Env = { MY_VAR: string; MY_VAR_A: string; MY_JSON_VAR: Object; MY_DEV_VAR: string; MY_KV: KVNamespace; MY_KV_PROD: KVNamespace; MY_BUCKET: R2Bucket; MY_D1: D1Database; MY_HYPERDRIVE: Hyperdrive; ASSETS: Fetcher; IMAGES: ImagesBinding; MY_WORKFLOW_INTERNAL: Workflow; MY_WORKFLOW_EXTERNAL: Workflow; }; const wranglerConfigFilePath = path.join(__dirname, "..", "wrangler.jsonc"); describe("getPlatformProxy - env", () => { let warn = {} as MockInstance; beforeEach(() => { // Hide stdout messages from the test logs vi.spyOn(console, "log").mockImplementation(() => {}); vi.spyOn(console, "error").mockImplementation(() => {}); warn = vi.spyOn(console, "warn").mockImplementation(() => {}); warn.mockClear(); }); describe("var bindings", () => { it("correctly obtains var bindings from both wrangler config and .dev.vars", async ({ expect, }) => { const { env, dispose } = await getPlatformProxy({ configPath: wranglerConfigFilePath, }); try { const { MY_VAR, MY_JSON_VAR, MY_DEV_VAR } = env; expect(MY_VAR).toEqual("my-var-value"); expect(MY_JSON_VAR).toEqual({ test: true, }); expect(MY_DEV_VAR).toEqual("my-dev-var-value"); } finally { await dispose(); } }); it("correctly makes vars from .dev.vars override the ones in wrangler config", async ({ expect, }) => { const { env, dispose } = await getPlatformProxy({ configPath: wranglerConfigFilePath, }); try { const { MY_VAR_A } = env; expect(MY_VAR_A).not.toEqual("my-var-a"); // if this fails, the value was read from wrangler config – not .dev.vars expect(MY_VAR_A).toEqual("my-dev-var-a"); } finally { await dispose(); } }); it("correctly makes vars from .dev.vars not override bindings of the same name from wrangler config", async ({ expect, }) => { const { env, dispose } = await getPlatformProxy({ configPath: wranglerConfigFilePath, }); try { const { MY_KV } = env; expect(MY_KV).not.toEqual("my-dev-kv"); ["get", "delete", "list", "put", "getWithMetadata"].every( (methodName) => expect( typeof (MY_KV as unknown as Record)[methodName] ).toBe("function") ); } finally { await dispose(); } }); it("correctly reads a toml from a custom path alongside with its .dev.vars", async ({ expect, }) => { const { env, dispose } = await getPlatformProxy({ configPath: path.join( __dirname, "..", "custom-toml", "path", "test.toml" ), }); try { const { MY_VAR, MY_JSON_VAR, MY_DEV_VAR } = env; expect(MY_VAR).toEqual("my-var-value-from-a-custom-toml"); expect(MY_JSON_VAR).toEqual({ test: true, customToml: true, }); expect(MY_DEV_VAR).toEqual("my-dev-var-value-from-a-custom-location"); } finally { await dispose(); } }); }); it("correctly reads a json config file", async ({ expect }) => { const { env, dispose } = await getPlatformProxy({ configPath: path.join(__dirname, "..", "wrangler.json"), }); try { const { MY_VAR, MY_JSON_VAR } = env; expect(MY_VAR).toEqual("my-var-value-from-a-json-config-file"); expect(MY_JSON_VAR).toEqual({ test: true, fromJson: true, }); } finally { await dispose(); } }); it("correctly obtains functioning ASSETS bindings", async ({ expect }) => { const { env, dispose } = await getPlatformProxy({ configPath: wranglerConfigFilePath, }); const res = await env.ASSETS.fetch("https://0.0.0.0/test.txt"); const text = await res.text(); expect(text).toEqual("this is a test text file!\n"); await dispose(); }); it("correctly obtains functioning KV bindings", async ({ expect }) => { const { env, dispose } = await getPlatformProxy({ configPath: wranglerConfigFilePath, }); const { MY_KV } = env; let numOfKeys = (await MY_KV.list()).keys.length; expect(numOfKeys).toBe(0); await MY_KV.put("my-key", "my-value"); numOfKeys = (await MY_KV.list()).keys.length; expect(numOfKeys).toBe(1); const value = await MY_KV.get("my-key"); expect(value).toBe("my-value"); await dispose(); }); it("correctly obtains functioning R2 bindings", async ({ expect }) => { const { env, dispose } = await getPlatformProxy({ configPath: wranglerConfigFilePath, }); try { const { MY_BUCKET } = env; let numOfObjects = (await MY_BUCKET.list()).objects.length; expect(numOfObjects).toBe(0); await MY_BUCKET.put("my-object", "my-value"); numOfObjects = (await MY_BUCKET.list()).objects.length; expect(numOfObjects).toBe(1); const value = await MY_BUCKET.get("my-object"); expect(await value?.text()).toBe("my-value"); } finally { await dispose(); } }); it("correctly obtains functioning D1 bindings", async ({ expect }) => { const { env, dispose } = await getPlatformProxy({ configPath: wranglerConfigFilePath, }); try { const { MY_D1 } = env; await MY_D1.exec( `CREATE TABLE IF NOT EXISTS users ( id integer PRIMARY KEY AUTOINCREMENT, name text NOT NULL )` ); const stmt = MY_D1.prepare("insert into users (name) values (?1)"); await MY_D1.batch([ stmt.bind("userA"), stmt.bind("userB"), stmt.bind("userC"), ]); const { results } = await MY_D1.prepare( "SELECT name FROM users LIMIT 5" ).all(); expect(results).toEqual([ { name: "userA" }, { name: "userB" }, { name: "userC" }, ]); } finally { await dispose(); } }); it("correctly obtains functioning Image bindings", async ({ expect }) => { expect.extend({ toMatchImageSnapshot }); const { env, dispose } = await getPlatformProxy({ configPath: wranglerConfigFilePath, }); try { const { IMAGES } = env; const streams = ( await fetch("https://playground.devprod.cloudflare.dev/flares.png") ).body!.tee(); // @ts-expect-error The stream types aren't matching up properly? expect(await IMAGES.info(streams[0])).toMatchInlineSnapshot(` { "fileSize": 96549, "format": "image/png", "height": 1145, "width": 2048, } `); // @ts-expect-error The stream types aren't matching up properly? const response = await env.IMAGES.input(streams[1]) .transform({ rotate: 90 }) .transform({ width: 128, height: 100 }) .transform({ blur: 20 }) .output({ format: "image/png" }); expect( Buffer.from(await response.response().arrayBuffer()) ).toMatchImageSnapshot(); } finally { await dispose(); } }); // Important: the hyperdrive values are passthrough ones since the workerd specific hyperdrive values only make sense inside // workerd itself and would simply not work in a node.js process it("correctly obtains passthrough Hyperdrive bindings", async ({ expect, }) => { const { env, dispose } = await getPlatformProxy({ configPath: wranglerConfigFilePath, }); try { const { MY_HYPERDRIVE } = env; expect(MY_HYPERDRIVE.connectionString).toEqual( "postgres://user:pass@127.0.0.1:1234/db" ); expect(MY_HYPERDRIVE.database).toEqual("db"); expect(MY_HYPERDRIVE.host).toEqual("127.0.0.1"); expect(MY_HYPERDRIVE.user).toEqual("user"); expect(MY_HYPERDRIVE.password).toEqual("pass"); expect(MY_HYPERDRIVE.port).toEqual(1234); } finally { await dispose(); } }); describe("DO warnings", () => { it("warns about internal DOs and doesn't crash", async ({ expect }) => { await getPlatformProxy({ configPath: path.join(__dirname, "..", "wrangler_internal_do.jsonc"), }); expect(warn.mock.calls[0][0].replaceAll(/[\r\n]+/g, "\n")) .toMatchInlineSnapshot(` "▲ [WARNING]  You have defined bindings to the following internal Durable Objects: - {"class_name":"MyDurableObject","name":"MY_DURABLE_OBJECT"} These will not work in local development, but they should work in production. If you want to develop these locally, you can define your DO in a separate Worker, with a separate configuration file. For detailed instructions, refer to the Durable Objects section here: https://developers.cloudflare.com/workers/wrangler/api#supported-bindings " `); }); it("doesn't warn about external DOs and doesn't crash", async ({ expect, }) => { await getPlatformProxy({ configPath: path.join(__dirname, "..", "wrangler_external_do.jsonc"), }); expect(warn).not.toHaveBeenCalled(); }); it("warns only about internal Workflows, not cross-script ones, and doesn't crash", async ({ expect, }) => { await getPlatformProxy({ configPath: path.join(__dirname, "..", "wrangler_workflow.jsonc"), }); // Only MY_WORKFLOW_INTERNAL (no script_name) is unsupported in // getPlatformProxy(); MY_WORKFLOW_EXTERNAL (script_name set) is // passed through to miniflare, which routes it via the dev-registry // proxy to the named worker. expect(warn).toHaveBeenCalledTimes(1); expect(warn.mock.calls[0][0].replaceAll(/[\r\n]+/g, "\n")) .toMatchInlineSnapshot(` "▲ [WARNING] You have defined bindings to the following Workflows without a script_name: - {"binding":"MY_WORKFLOW_INTERNAL","name":"my-workflow-internal","class_name":"MyWorkflowInternal"} These are not available in local development, so you will not be able to bind to them when testing locally, but they should work in production. " `); }); }); describe("with a target environment", () => { it("should provide bindings targeting a specified environment and also inherit top-level ones", async ({ expect, }) => { const { env, dispose } = await getPlatformProxy({ configPath: wranglerConfigFilePath, environment: "production", }); try { expect(env.MY_VAR).not.toBe("my-var-value"); expect(env.MY_VAR).toBe("my-PRODUCTION-var-value"); expect(env.MY_JSON_VAR).toEqual({ test: true, production: true }); expect(env.MY_KV).toBeTruthy(); expect(env.MY_KV_PROD).toBeTruthy(); } finally { await dispose(); } }); it("should not provide bindings targeting an environment when none was specified", async ({ expect, }) => { const { env, dispose } = await getPlatformProxy({ configPath: wranglerConfigFilePath, }); try { expect(env.MY_VAR).not.toBe("my-PRODUCTION-var-value"); expect(env.MY_VAR).toBe("my-var-value"); expect(env.MY_JSON_VAR).toEqual({ test: true }); expect(env.MY_KV).toBeTruthy(); expect(env.MY_KV_PROD).toBeFalsy(); } finally { await dispose(); } }); it("should provide secrets targeting a specified environment", async ({ expect, }) => { const { env, dispose } = await getPlatformProxy({ configPath: wranglerConfigFilePath, environment: "production", }); try { const { MY_DEV_VAR } = env; expect(MY_DEV_VAR).not.toEqual("my-dev-var-value"); expect(MY_DEV_VAR).toEqual("my-PRODUCTION-dev-var-value"); } finally { await dispose(); } }); it("should error if a non-existent environment is provided", async ({ expect, }) => { await expect( getPlatformProxy({ configPath: wranglerConfigFilePath, environment: "non-existent-environment", }) ).rejects.toThrow( /No environment found in configuration with name "non-existent-environment"/ ); }); }); });