From d99e6204d30edc26f84397cefdf565c47d98355e Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 08:55:48 +0000 Subject: [PATCH] [copilot-finds] Bug: Add missing input validation to signalEntity() and getEntity() client methods The signalEntity() and getEntity() methods on TaskHubGrpcClient lack the input validation guards that all comparable public API methods have (e.g. raiseOrchestrationEvent, terminateOrchestration, suspendOrchestration). Without validation, passing null or undefined for the required 'id' parameter produces a confusing TypeError ('Cannot read properties of null') instead of a clear error message. Similarly, signalEntity() does not validate its required 'operationName' parameter. Add validation to both methods matching the established pattern: - signalEntity: validate 'id' and 'operationName' - getEntity: validate 'id' Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- packages/durabletask-js/src/client/client.ts | 12 ++++ .../durabletask-js/test/entity-client.spec.ts | 59 +++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/packages/durabletask-js/src/client/client.ts b/packages/durabletask-js/src/client/client.ts index d0be02e7..8055fbd7 100644 --- a/packages/durabletask-js/src/client/client.ts +++ b/packages/durabletask-js/src/client/client.ts @@ -994,6 +994,14 @@ export class TaskHubGrpcClient { input?: unknown, options?: SignalEntityOptions, ): Promise { + if (!id) { + throw new Error("signalEntity: 'id' is required."); + } + + if (!operationName) { + throw new Error("signalEntity: 'operationName' is required and cannot be empty."); + } + const req = new pb.SignalEntityRequest(); req.setInstanceid(id.toString()); req.setRequestid(randomUUID()); @@ -1047,6 +1055,10 @@ export class TaskHubGrpcClient { id: EntityInstanceId, includeState: boolean = true, ): Promise | undefined> { + if (!id) { + throw new Error("getEntity: 'id' is required."); + } + const req = new pb.GetEntityRequest(); req.setInstanceid(id.toString()); req.setIncludestate(includeState); diff --git a/packages/durabletask-js/test/entity-client.spec.ts b/packages/durabletask-js/test/entity-client.spec.ts index e77b46af..b6e063d6 100644 --- a/packages/durabletask-js/test/entity-client.spec.ts +++ b/packages/durabletask-js/test/entity-client.spec.ts @@ -351,3 +351,62 @@ describe("EntityInstanceId.fromString", () => { expect(() => EntityInstanceId.fromString("@onlyname")).toThrow(); }); }); + +describe("Entity Client Input Validation", () => { + // Validation guards throw before any gRPC call, so no connection is needed. + const { TaskHubGrpcClient } = require("../src"); + let client: InstanceType; + + beforeEach(() => { + client = new TaskHubGrpcClient({ hostAddress: "localhost:4001" }); + }); + + describe("signalEntity", () => { + it("should throw when id is null", async () => { + await expect(client.signalEntity(null as any, "op")).rejects.toThrow( + "signalEntity: 'id' is required.", + ); + }); + + it("should throw when id is undefined", async () => { + await expect(client.signalEntity(undefined as any, "op")).rejects.toThrow( + "signalEntity: 'id' is required.", + ); + }); + + it("should throw when operationName is empty string", async () => { + const entityId = new EntityInstanceId("counter", "my-counter"); + await expect(client.signalEntity(entityId, "")).rejects.toThrow( + "signalEntity: 'operationName' is required and cannot be empty.", + ); + }); + + it("should throw when operationName is null", async () => { + const entityId = new EntityInstanceId("counter", "my-counter"); + await expect(client.signalEntity(entityId, null as any)).rejects.toThrow( + "signalEntity: 'operationName' is required and cannot be empty.", + ); + }); + + it("should throw when operationName is undefined", async () => { + const entityId = new EntityInstanceId("counter", "my-counter"); + await expect(client.signalEntity(entityId, undefined as any)).rejects.toThrow( + "signalEntity: 'operationName' is required and cannot be empty.", + ); + }); + }); + + describe("getEntity", () => { + it("should throw when id is null", async () => { + await expect(client.getEntity(null as any)).rejects.toThrow( + "getEntity: 'id' is required.", + ); + }); + + it("should throw when id is undefined", async () => { + await expect(client.getEntity(undefined as any)).rejects.toThrow( + "getEntity: 'id' is required.", + ); + }); + }); +});