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
5 changes: 5 additions & 0 deletions .changeset/rare-peaches-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": patch
---

Enforce 64-character limit for Workflow binding names locally to match production validation
113 changes: 113 additions & 0 deletions packages/wrangler/src/__tests__/workflows.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { clearDialogs } from "./helpers/mock-dialogs";
import { msw } from "./helpers/msw";
import { runInTempDir } from "./helpers/run-in-tmp";
import { runWrangler } from "./helpers/run-wrangler";
import { writeWorkerSource } from "./helpers/write-worker-source";
import { writeWranglerConfig } from "./helpers/write-wrangler-config";
import type { Instance, Workflow } from "../workflows/types";

Expand Down Expand Up @@ -433,4 +434,116 @@ describe("wrangler workflows", () => {
);
});
});

describe("workflow binding validation", () => {
it("should validate workflow binding with valid name", async () => {
writeWorkerSource({ format: "ts" });
writeWranglerConfig({
main: "index.ts",
workflows: [
{
binding: "MY_WORKFLOW",
name: "valid-workflow-name",
class_name: "MyWorkflow",
script_name: "external-script",
},
],
});

await runWrangler("deploy --dry-run");
expect(std.err).toBe("");
});

it("should reject workflow binding with name exceeding 64 characters", async () => {
const longName = "a".repeat(65); // 65 characters
writeWranglerConfig({
workflows: [
{
binding: "MY_WORKFLOW",
name: longName,
class_name: "MyWorkflow",
},
],
});

await expect(runWrangler("deploy --dry-run")).rejects.toThrow();
expect(std.err).toContain("must be 64 characters or less");
expect(std.err).toContain("but got 65 characters");
});

it("should accept workflow binding with name exactly 64 characters", async () => {
const maxLengthName = "a".repeat(64); // exactly 64 characters
writeWorkerSource({ format: "ts" });
writeWranglerConfig({
main: "index.ts",
workflows: [
{
binding: "MY_WORKFLOW",
name: maxLengthName,
class_name: "MyWorkflow",
script_name: "external-script",
},
],
});

await runWrangler("deploy --dry-run");
expect(std.err).toBe("");
});

it("should validate required fields for workflow binding", async () => {
writeWranglerConfig({
workflows: [
{
binding: "MY_WORKFLOW",
} as any, // eslint-disable-line @typescript-eslint/no-explicit-any
],
});

await expect(runWrangler("deploy --dry-run")).rejects.toThrow();
expect(std.err).toContain('should have a string "name" field');
expect(std.err).toContain('should have a string "class_name" field');
});

it("should validate optional fields for workflow binding", async () => {
writeWorkerSource({ format: "ts" });
writeWranglerConfig({
main: "index.ts",
workflows: [
{
binding: "MY_WORKFLOW",
name: "my-workflow",
class_name: "MyWorkflow",
script_name: "external-script",
},
],
});

await runWrangler("deploy --dry-run");
expect(std.err).toBe("");
});

it("should reject workflow binding with invalid field types", async () => {
writeWranglerConfig({
workflows: [
{
binding: 123, // should be string
name: "my-workflow",
class_name: "MyWorkflow",
} as any, // eslint-disable-line @typescript-eslint/no-explicit-any
],
});

await expect(runWrangler("deploy --dry-run")).rejects.toThrow();
expect(std.err).toContain('should have a string "binding" field');
});

it("should reject workflow binding that is not an object", async () => {
writeWranglerConfig({
workflows: ["invalid-workflow-config"] as any, // eslint-disable-line @typescript-eslint/no-explicit-any
});

await expect(runWrangler("deploy --dry-run")).rejects.toThrow();
expect(std.err).toContain('"workflows" bindings should be objects');
});
});
});
71 changes: 68 additions & 3 deletions packages/wrangler/src/config/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2173,10 +2173,75 @@ const validateDurableObjectBinding: ValidatorFn = (
/**
* Check that the given field is a valid "workflow" binding object.
*/
const validateWorkflowBinding: ValidatorFn = (_diagnostics, _field, _value) => {
// TODO
const validateWorkflowBinding: ValidatorFn = (diagnostics, field, value) => {
if (typeof value !== "object" || value === null) {
diagnostics.errors.push(
`"workflows" bindings should be objects, but got ${JSON.stringify(value)}`
);
return false;
}

return true;
let isValid = true;

if (!isRequiredProperty(value, "binding", "string")) {
diagnostics.errors.push(
`"${field}" bindings should have a string "binding" field but got ${JSON.stringify(
value
)}.`
);
isValid = false;
}

if (!isRequiredProperty(value, "name", "string")) {
diagnostics.errors.push(
`"${field}" bindings should have a string "name" field but got ${JSON.stringify(
value
)}.`
);
isValid = false;
} else if (value.name.length > 64) {
diagnostics.errors.push(
`"${field}" binding "name" field must be 64 characters or less, but got ${value.name.length} characters.`
);
isValid = false;
}

if (!isRequiredProperty(value, "class_name", "string")) {
diagnostics.errors.push(
`"${field}" bindings should have a string "class_name" field but got ${JSON.stringify(
value
)}.`
);
isValid = false;
}

if (!isOptionalProperty(value, "script_name", "string")) {
diagnostics.errors.push(
`"${field}" bindings should, optionally, have a string "script_name" field but got ${JSON.stringify(
value
)}.`
);
isValid = false;
}

if (!isOptionalProperty(value, "experimental_remote", "boolean")) {
diagnostics.errors.push(
`"${field}" bindings should, optionally, have a boolean "experimental_remote" field but got ${JSON.stringify(
value
)}.`
);
isValid = false;
}

validateAdditionalProperties(diagnostics, field, Object.keys(value), [
"binding",
"name",
"class_name",
"script_name",
"experimental_remote",
]);

return isValid;
};

const validateCflogfwdrObject: (env: string) => ValidatorFn =
Expand Down
Loading