Skip to content

Commit 7f291e1

Browse files
authored
[copilot-finds] Bug: Add missing input validation to signalEntity() and getEntity() client methods (#325)
1 parent bb8c283 commit 7f291e1

2 files changed

Lines changed: 71 additions & 0 deletions

File tree

packages/durabletask-js/src/client/client.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,14 @@ export class TaskHubGrpcClient {
998998
input?: unknown,
999999
options?: SignalEntityOptions,
10001000
): Promise<void> {
1001+
if (!id) {
1002+
throw new Error("signalEntity: 'id' is required.");
1003+
}
1004+
1005+
if (!operationName) {
1006+
throw new Error("signalEntity: 'operationName' is required and cannot be empty.");
1007+
}
1008+
10011009
const req = new pb.SignalEntityRequest();
10021010
req.setInstanceid(id.toString());
10031011
req.setRequestid(randomUUID());
@@ -1051,6 +1059,10 @@ export class TaskHubGrpcClient {
10511059
id: EntityInstanceId,
10521060
includeState: boolean = true,
10531061
): Promise<EntityMetadata<T> | undefined> {
1062+
if (!id) {
1063+
throw new Error("getEntity: 'id' is required.");
1064+
}
1065+
10541066
const req = new pb.GetEntityRequest();
10551067
req.setInstanceid(id.toString());
10561068
req.setIncludestate(includeState);

packages/durabletask-js/test/entity-client.spec.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,65 @@ describe("EntityInstanceId.fromString", () => {
396396
});
397397
});
398398

399+
describe("Entity Client Input Validation", () => {
400+
// Validation guards throw before any gRPC call, so no connection is needed.
401+
const { TaskHubGrpcClient } = require("../src");
402+
let client: InstanceType<typeof TaskHubGrpcClient>;
403+
404+
beforeEach(() => {
405+
client = new TaskHubGrpcClient({ hostAddress: "localhost:4001" });
406+
});
407+
408+
describe("signalEntity", () => {
409+
it("should throw when id is null", async () => {
410+
await expect(client.signalEntity(null as any, "op")).rejects.toThrow(
411+
"signalEntity: 'id' is required.",
412+
);
413+
});
414+
415+
it("should throw when id is undefined", async () => {
416+
await expect(client.signalEntity(undefined as any, "op")).rejects.toThrow(
417+
"signalEntity: 'id' is required.",
418+
);
419+
});
420+
421+
it("should throw when operationName is empty string", async () => {
422+
const entityId = new EntityInstanceId("counter", "my-counter");
423+
await expect(client.signalEntity(entityId, "")).rejects.toThrow(
424+
"signalEntity: 'operationName' is required and cannot be empty.",
425+
);
426+
});
427+
428+
it("should throw when operationName is null", async () => {
429+
const entityId = new EntityInstanceId("counter", "my-counter");
430+
await expect(client.signalEntity(entityId, null as any)).rejects.toThrow(
431+
"signalEntity: 'operationName' is required and cannot be empty.",
432+
);
433+
});
434+
435+
it("should throw when operationName is undefined", async () => {
436+
const entityId = new EntityInstanceId("counter", "my-counter");
437+
await expect(client.signalEntity(entityId, undefined as any)).rejects.toThrow(
438+
"signalEntity: 'operationName' is required and cannot be empty.",
439+
);
440+
});
441+
});
442+
443+
describe("getEntity", () => {
444+
it("should throw when id is null", async () => {
445+
await expect(client.getEntity(null as any)).rejects.toThrow(
446+
"getEntity: 'id' is required.",
447+
);
448+
});
449+
450+
it("should throw when id is undefined", async () => {
451+
await expect(client.getEntity(undefined as any)).rejects.toThrow(
452+
"getEntity: 'id' is required.",
453+
);
454+
});
455+
});
456+
});
457+
399458
describe("getEntities query normalization", () => {
400459
const { TaskHubGrpcClient } = require("../src");
401460

0 commit comments

Comments
 (0)