From ecae0c8c8ab002835d110bce7cba619ea9168517 Mon Sep 17 00:00:00 2001 From: Anthony Ting Date: Wed, 3 Sep 2025 14:19:52 -0700 Subject: [PATCH] fix(testing-sdk): only accept strings for sendCallbackSuccess --- .../__tests__/operation-with-data.test.ts | 500 +++++++++++++----- .../common/operations/operation-with-data.ts | 4 +- .../callback-operations.integration.test.ts | 45 +- ...or-callback-operations.integration.test.ts | 93 ++-- 4 files changed, 457 insertions(+), 185 deletions(-) diff --git a/lambda-durable-functions-testing-sdk-js/src/test-runner/common/operations/__tests__/operation-with-data.test.ts b/lambda-durable-functions-testing-sdk-js/src/test-runner/common/operations/__tests__/operation-with-data.test.ts index f914f890..b2de2279 100644 --- a/lambda-durable-functions-testing-sdk-js/src/test-runner/common/operations/__tests__/operation-with-data.test.ts +++ b/lambda-durable-functions-testing-sdk-js/src/test-runner/common/operations/__tests__/operation-with-data.test.ts @@ -19,13 +19,19 @@ describe("OperationWithData", () => { describe("data", () => { it("should return undefined when operation data is not populated", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); expect(operation.getOperationData()).toBeUndefined(); }); it("should return operation data when populated", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "test-operation", @@ -43,7 +49,10 @@ describe("OperationWithData", () => { describe("getContextDetails", () => { it("should throw error when operation type is not CONTEXT", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "test-operation", @@ -62,7 +71,10 @@ describe("OperationWithData", () => { }); it("should return context details when operation type is CONTEXT", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "test-context", @@ -93,7 +105,10 @@ describe("OperationWithData", () => { }); it("should return context details with only result", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "test-context", @@ -118,7 +133,10 @@ describe("OperationWithData", () => { }); it("should return context details with only error", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "test-context", @@ -149,7 +167,10 @@ describe("OperationWithData", () => { }); it("should handle unparseable JSON in Result field", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "test-context", @@ -174,13 +195,19 @@ describe("OperationWithData", () => { }); it("should return undefined when no data is populated", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const contextDetails = operation.getContextDetails(); expect(contextDetails).toBeUndefined(); }); it("should handle missing ContextDetails", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "test-context", @@ -204,7 +231,10 @@ describe("OperationWithData", () => { describe("getStepDetails", () => { it("should throw error when operation type is not STEP", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "test-operation", @@ -223,7 +253,10 @@ describe("OperationWithData", () => { }); it("should return step details when operation type is STEP", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "test-step", @@ -252,13 +285,19 @@ describe("OperationWithData", () => { }); it("should return undefined when no data is populated", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const stepDetails = operation.getStepDetails(); expect(stepDetails).toBeUndefined(); }); it("should return step details with error when populated", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "step-op-error", Type: OperationType.STEP, @@ -292,7 +331,10 @@ describe("OperationWithData", () => { }); it("should return step details with both result and error", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "step-op-partial", Type: OperationType.STEP, @@ -326,7 +368,10 @@ describe("OperationWithData", () => { }); it("should handle unparseable JSON in Result field with error", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "step-op-bad-json", Type: OperationType.STEP, @@ -360,7 +405,10 @@ describe("OperationWithData", () => { }); it("should handle missing StepDetails with default values", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "step-op-no-details", Type: OperationType.STEP, @@ -386,7 +434,10 @@ describe("OperationWithData", () => { describe("getCallbackDetails", () => { it("should throw error when operation type is not CALLBACK", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "test-operation", @@ -405,7 +456,10 @@ describe("OperationWithData", () => { }); it("should throw error when CallbackId is undefined", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "test-callback", @@ -427,7 +481,10 @@ describe("OperationWithData", () => { }); it("should return callback details when properly configured", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "test-callback", @@ -454,32 +511,40 @@ describe("OperationWithData", () => { }); it("should return undefined when no data is populated", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const callbackDetails = operation.getCallbackDetails(); expect(callbackDetails).toBeUndefined(); }); it("should return callback details for WAIT_FOR_CALLBACK context operation with child CALLBACK", () => { const mockChildOperations = [ - { - operation: { - Id: "child-callback", + { + operation: { + Id: "child-callback", Name: "child-callback-op", Type: OperationType.CALLBACK, CallbackDetails: { CallbackId: "callback-456", Result: '{"waitResult": "success"}', - Error: { ErrorMessage: "Wait callback error" } - } + Error: { ErrorMessage: "Wait callback error" }, + }, }, - update: {} + update: {}, }, ]; // Mock the getOperationChildren method - jest.spyOn(mockIndexedOperations, "getOperationChildren").mockReturnValue(mockChildOperations); + jest + .spyOn(mockIndexedOperations, "getOperationChildren") + .mockReturnValue(mockChildOperations); - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "wait-for-callback-op", Name: "wait-for-callback", @@ -503,20 +568,25 @@ describe("OperationWithData", () => { it("should throw error when WAIT_FOR_CALLBACK context has no child CALLBACK operation", () => { const mockChildOperations = [ - { - operation: { - Id: "child-step", + { + operation: { + Id: "child-step", Name: "child-step-op", Type: OperationType.STEP, // Not CALLBACK }, - update: {} + update: {}, }, ]; // Mock the getOperationChildren method - jest.spyOn(mockIndexedOperations, "getOperationChildren").mockReturnValue(mockChildOperations); + jest + .spyOn(mockIndexedOperations, "getOperationChildren") + .mockReturnValue(mockChildOperations); - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "wait-for-callback-op", Name: "wait-for-callback", @@ -537,9 +607,14 @@ describe("OperationWithData", () => { it("should throw error when WAIT_FOR_CALLBACK context has empty child operations", () => { // Mock the getOperationChildren method to return empty array - jest.spyOn(mockIndexedOperations, "getOperationChildren").mockReturnValue([]); + jest + .spyOn(mockIndexedOperations, "getOperationChildren") + .mockReturnValue([]); - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "wait-for-callback-op", Name: "wait-for-callback", @@ -560,23 +635,28 @@ describe("OperationWithData", () => { it("should throw error when WAIT_FOR_CALLBACK child CALLBACK operation has undefined CallbackId", () => { const mockChildOperations = [ - { - operation: { - Id: "child-callback", + { + operation: { + Id: "child-callback", Name: "child-callback-op", Type: OperationType.CALLBACK, CallbackDetails: { CallbackId: undefined, // Missing CallbackId - } + }, }, - update: {} + update: {}, }, ]; // Mock the getOperationChildren method - jest.spyOn(mockIndexedOperations, "getOperationChildren").mockReturnValue(mockChildOperations); + jest + .spyOn(mockIndexedOperations, "getOperationChildren") + .mockReturnValue(mockChildOperations); - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "wait-for-callback-op", Name: "wait-for-callback", @@ -598,7 +678,10 @@ describe("OperationWithData", () => { describe("getWaitDetails", () => { it("should throw error when operation type is not WAIT", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "test-operation", @@ -617,7 +700,10 @@ describe("OperationWithData", () => { }); it("should return wait details when operation type is WAIT", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "test-wait", @@ -641,7 +727,10 @@ describe("OperationWithData", () => { }); it("should return undefined when no data is populated", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const waitDetails = operation.getWaitDetails(); expect(waitDetails).toBeUndefined(); }); @@ -649,7 +738,10 @@ describe("OperationWithData", () => { describe("getter methods", () => { it("should return id when data is populated", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-operation-id", Name: "test-operation-name", @@ -665,12 +757,18 @@ describe("OperationWithData", () => { }); it("should return undefined for id when data is not populated", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); expect(operation.getId()).toBeUndefined(); }); it("should return name when data is populated", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "test-operation-name", @@ -686,12 +784,18 @@ describe("OperationWithData", () => { }); it("should return undefined for name when data is not populated", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); expect(operation.getName()).toBeUndefined(); }); it("should return type when data is populated", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "test-operation", @@ -708,12 +812,18 @@ describe("OperationWithData", () => { }); it("should return undefined for type when data is not populated", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); expect(operation.getType()).toBeUndefined(); }); it("should return status when data is populated", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "test-operation", @@ -729,12 +839,18 @@ describe("OperationWithData", () => { }); it("should return undefined for status when data is not populated", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); expect(operation.getStatus()).toBeUndefined(); }); it("should return start timestamp when data is populated", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const startTime = new Date("2023-01-01T10:00:00Z"); const operationData = { Id: "test-id", @@ -752,12 +868,18 @@ describe("OperationWithData", () => { }); it("should return undefined for start timestamp when data is not populated", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); expect(operation.getStartTimestamp()).toBeUndefined(); }); it("should return end timestamp when data is populated", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const endTime = new Date("2023-01-01T11:00:00Z"); const operationData = { Id: "test-id", @@ -775,12 +897,18 @@ describe("OperationWithData", () => { }); it("should return undefined for end timestamp when data is not populated", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); expect(operation.getEndTimestamp()).toBeUndefined(); }); it("should return parent ID when data is populated", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "test-operation", @@ -797,12 +925,18 @@ describe("OperationWithData", () => { }); it("should return undefined for parent ID when data is not populated", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); expect(operation.getParentId()).toBeUndefined(); }); it("should return subtype when data is populated", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "test-operation", @@ -820,12 +954,18 @@ describe("OperationWithData", () => { }); it("should return undefined for subtype when data is not populated", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); expect(operation.getSubType()).toBeUndefined(); }); it("should return true for isWaitForCallback when operation is CONTEXT with WAIT_FOR_CALLBACK subtype", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "test-operation", @@ -843,7 +983,10 @@ describe("OperationWithData", () => { }); it("should return false for isWaitForCallback when operation is not WAIT_FOR_CALLBACK", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "test-operation", @@ -860,7 +1003,10 @@ describe("OperationWithData", () => { }); it("should return true for isCallback when operation type is CALLBACK", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "test-operation", @@ -877,7 +1023,10 @@ describe("OperationWithData", () => { }); it("should return false for isCallback when operation type is not CALLBACK", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "test-operation", @@ -914,7 +1063,10 @@ describe("OperationWithData", () => { describe("getCallbackId", () => { it("should throw error when operation has not run yet", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); expect(() => operation.sendCallbackSuccess("test-result")).toThrow( "Could not find callback details" @@ -922,7 +1074,10 @@ describe("OperationWithData", () => { }); it("should throw error when operation type is not CALLBACK", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "callback-op", @@ -941,7 +1096,10 @@ describe("OperationWithData", () => { }); it("should throw error when CallbackDetails is missing", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "callback-op", @@ -961,7 +1119,10 @@ describe("OperationWithData", () => { }); it("should throw error when CallbackDetails.CallbackId is undefined", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "callback-op", @@ -985,7 +1146,10 @@ describe("OperationWithData", () => { describe("sendCallbackSuccess", () => { it("should send success callback with correct parameters", async () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "callback-op", @@ -1016,39 +1180,11 @@ describe("OperationWithData", () => { expect(result).toEqual({ success: true }); }); - it("should JSON stringify non-string results", async () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); - const operationData = { - Id: "test-id", - Name: "callback-op", - Status: OperationStatus.SUCCEEDED, - Type: OperationType.CALLBACK, - CallbackDetails: { - CallbackId: "callback-123", - }, - }; - - operation.populateData({ - operation: operationData, - update: {}, - }); - mockClient.send.mockResolvedValue({ success: true }); - - const objectResult = { data: "test", count: 42 }; - await operation.sendCallbackSuccess(objectResult); - - expect(mockClient.send).toHaveBeenCalledWith( - expect.objectContaining({ - input: { - Result: '{"data":"test","count":42}', - CallbackId: "callback-123", - }, - }) - ); - }); - it("should handle client errors", async () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "callback-op", @@ -1074,7 +1210,10 @@ describe("OperationWithData", () => { describe("sendCallbackFailure", () => { it("should send failure callback with correct parameters", async () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "callback-op", @@ -1111,7 +1250,10 @@ describe("OperationWithData", () => { }); it("should handle different error object formats", async () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "callback-op", @@ -1145,7 +1287,10 @@ describe("OperationWithData", () => { }); it("should handle client errors", async () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "callback-op", @@ -1175,7 +1320,10 @@ describe("OperationWithData", () => { describe("sendCallbackHeartbeat", () => { it("should send heartbeat with correct parameters", async () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "callback-op", @@ -1206,7 +1354,10 @@ describe("OperationWithData", () => { }); it("should handle client errors", async () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "test-id", Name: "callback-op", @@ -1232,8 +1383,14 @@ describe("OperationWithData", () => { describe("callback method integration", () => { it("should work with different callback operations", async () => { - const operation1 = new OperationWithData(waitManager, mockIndexedOperations); - const operation2 = new OperationWithData(waitManager, mockIndexedOperations); + const operation1 = new OperationWithData( + waitManager, + mockIndexedOperations + ); + const operation2 = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData1 = { Id: "test-id-1", @@ -1298,7 +1455,10 @@ describe("OperationWithData", () => { }); it("should return data immediately when status matches STARTED", async () => { - const mockOperation = new OperationWithData(waitManager, mockIndexedOperations); + const mockOperation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const expectedResult = { operation: { Status: OperationStatus.STARTED, @@ -1319,7 +1479,10 @@ describe("OperationWithData", () => { }); it("should return data immediately when status matches COMPLETED", async () => { - const mockOperation = new OperationWithData(waitManager, mockIndexedOperations); + const mockOperation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const expectedResult = { operation: { Status: OperationStatus.SUCCEEDED, @@ -1340,7 +1503,10 @@ describe("OperationWithData", () => { }); it("should wait when data exists but status doesn't match", async () => { - const mockOperation = new OperationWithData(waitManager, mockIndexedOperations); + const mockOperation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const initialData = { operation: { Status: OperationStatus.STARTED, // Data exists but has STARTED status @@ -1376,7 +1542,10 @@ describe("OperationWithData", () => { }); it("should wait when data exists but has no status", async () => { - const mockOperation = new OperationWithData(waitManager, mockIndexedOperations); + const mockOperation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const initialData = { operation: { Id: "test-id", @@ -1416,7 +1585,10 @@ describe("OperationWithData", () => { }); it("should delegate to wait manager with default status when data not available", async () => { - const mockOperation = new OperationWithData(waitManager, mockIndexedOperations); + const mockOperation = new OperationWithData( + waitManager, + mockIndexedOperations + ); (waitManager.waitForOperation as jest.Mock).mockResolvedValue( mockOperation ); @@ -1445,7 +1617,10 @@ describe("OperationWithData", () => { }); it("should delegate to wait manager with specified status when data not available", async () => { - const mockOperation = new OperationWithData(waitManager, mockIndexedOperations); + const mockOperation = new OperationWithData( + waitManager, + mockIndexedOperations + ); (waitManager.waitForOperation as jest.Mock).mockResolvedValue( mockOperation ); @@ -1475,7 +1650,10 @@ describe("OperationWithData", () => { }); it("should propagate wait manager errors when data not available", async () => { - const mockOperation = new OperationWithData(waitManager, mockIndexedOperations); + const mockOperation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const waitError = new Error("Wait failed"); (waitManager.waitForOperation as jest.Mock).mockRejectedValue(waitError); @@ -1483,7 +1661,10 @@ describe("OperationWithData", () => { }); it("should support multiple concurrent waiters for the same operation", async () => { - const mockOperation = new OperationWithData(waitManager, mockIndexedOperations); + const mockOperation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const expectedResult = { operation: { Status: OperationStatus.SUCCEEDED, @@ -1526,7 +1707,10 @@ describe("OperationWithData", () => { }); it("should handle mixed scenario with some data available and some waiting", async () => { - const mockOperation = new OperationWithData(waitManager, mockIndexedOperations); + const mockOperation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const expectedResult = { operation: { Status: OperationStatus.SUCCEEDED, @@ -1561,20 +1745,25 @@ describe("OperationWithData", () => { it("should return OperationWithData instances for child operations when data is populated", () => { const mockChildOperations = [ - { + { operation: { Id: "child-1", Name: "child-op-1" }, - update: {} + update: {}, }, - { + { operation: { Id: "child-2", Name: "child-op-2" }, - update: {} + update: {}, }, ]; // Mock the getOperationChildren method - jest.spyOn(mockIndexedOperations, "getOperationChildren").mockReturnValue(mockChildOperations); + jest + .spyOn(mockIndexedOperations, "getOperationChildren") + .mockReturnValue(mockChildOperations); - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "parent-id", Name: "parent-operation", @@ -1588,16 +1777,27 @@ describe("OperationWithData", () => { const childOperations = operation.getChildOperations(); - expect(mockIndexedOperations.getOperationChildren).toHaveBeenCalledWith("parent-id"); + expect(mockIndexedOperations.getOperationChildren).toHaveBeenCalledWith( + "parent-id" + ); expect(childOperations).toHaveLength(2); expect(childOperations![0]).toBeInstanceOf(OperationWithData); expect(childOperations![1]).toBeInstanceOf(OperationWithData); - expect(childOperations![0].getOperationData()).toEqual({ Id: "child-1", Name: "child-op-1" }); - expect(childOperations![1].getOperationData()).toEqual({ Id: "child-2", Name: "child-op-2" }); + expect(childOperations![0].getOperationData()).toEqual({ + Id: "child-1", + Name: "child-op-1", + }); + expect(childOperations![1].getOperationData()).toEqual({ + Id: "child-2", + Name: "child-op-2", + }); }); it("should return undefined when operation data is not populated", () => { - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const childOperations = operation.getChildOperations(); @@ -1606,9 +1806,14 @@ describe("OperationWithData", () => { it("should return empty array when operation has no children", () => { // Mock the getOperationChildren method to return empty array - jest.spyOn(mockIndexedOperations, "getOperationChildren").mockReturnValue([]); + jest + .spyOn(mockIndexedOperations, "getOperationChildren") + .mockReturnValue([]); - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "parent-with-no-children", Name: "parent-operation", @@ -1622,21 +1827,32 @@ describe("OperationWithData", () => { const childOperations = operation.getChildOperations(); - expect(mockIndexedOperations.getOperationChildren).toHaveBeenCalledWith("parent-with-no-children"); + expect(mockIndexedOperations.getOperationChildren).toHaveBeenCalledWith( + "parent-with-no-children" + ); expect(childOperations).toEqual([]); }); it("should work with different operation types", () => { const mockChildOperations = [ - { - operation: { Id: "step-child", Name: "step-child", Type: OperationType.STEP }, - update: {} + { + operation: { + Id: "step-child", + Name: "step-child", + Type: OperationType.STEP, + }, + update: {}, }, ]; - jest.spyOn(mockIndexedOperations, "getOperationChildren").mockReturnValue(mockChildOperations); + jest + .spyOn(mockIndexedOperations, "getOperationChildren") + .mockReturnValue(mockChildOperations); - const operation = new OperationWithData(waitManager, mockIndexedOperations); + const operation = new OperationWithData( + waitManager, + mockIndexedOperations + ); const operationData = { Id: "callback-parent", Name: "callback-operation", @@ -1651,10 +1867,16 @@ describe("OperationWithData", () => { const childOperations = operation.getChildOperations(); - expect(mockIndexedOperations.getOperationChildren).toHaveBeenCalledWith("callback-parent"); + expect(mockIndexedOperations.getOperationChildren).toHaveBeenCalledWith( + "callback-parent" + ); expect(childOperations).toHaveLength(1); expect(childOperations![0]).toBeInstanceOf(OperationWithData); - expect(childOperations![0].getOperationData()).toEqual({ Id: "step-child", Name: "step-child", Type: OperationType.STEP }); + expect(childOperations![0].getOperationData()).toEqual({ + Id: "step-child", + Name: "step-child", + Type: OperationType.STEP, + }); }); }); }); diff --git a/lambda-durable-functions-testing-sdk-js/src/test-runner/common/operations/operation-with-data.ts b/lambda-durable-functions-testing-sdk-js/src/test-runner/common/operations/operation-with-data.ts index 3bdb298b..4ee79f9f 100644 --- a/lambda-durable-functions-testing-sdk-js/src/test-runner/common/operations/operation-with-data.ts +++ b/lambda-durable-functions-testing-sdk-js/src/test-runner/common/operations/operation-with-data.ts @@ -271,7 +271,7 @@ export class OperationWithData return this.checkpointOperationData?.operation.EndTimestamp; } - sendCallbackSuccess(result: string | OperationResultValue) { + sendCallbackSuccess(result: string) { const callbackDetails = this.getCallbackDetails(); if (!callbackDetails) { @@ -282,7 +282,7 @@ export class OperationWithData return client.send( new SendDurableExecutionCallbackSuccessCommand({ - Result: typeof result === "string" ? result : JSON.stringify(result), + Result: result, CallbackId: callbackDetails.callbackId, }) ); diff --git a/lambda-durable-functions-testing-sdk-js/src/test-runner/local/__tests__/integration/callback-operations.integration.test.ts b/lambda-durable-functions-testing-sdk-js/src/test-runner/local/__tests__/integration/callback-operations.integration.test.ts index e7e238bd..7a5a33a6 100644 --- a/lambda-durable-functions-testing-sdk-js/src/test-runner/local/__tests__/integration/callback-operations.integration.test.ts +++ b/lambda-durable-functions-testing-sdk-js/src/test-runner/local/__tests__/integration/callback-operations.integration.test.ts @@ -59,13 +59,13 @@ describe("Callback Operations Integration", () => { expect(callbackData.getCallbackDetails()?.callbackId).toBeDefined(); // Simulate external system completing the callback - await callbackOperation.sendCallbackSuccess('payment_completed_pay_12345'); + await callbackOperation.sendCallbackSuccess("payment_completed_pay_12345"); // Now the execution should complete const result = await executionPromise; expect(result.getResult()).toEqual({ - callbackResult: 'payment_completed_pay_12345', + callbackResult: "payment_completed_pay_12345", callbackId: expect.any(String), completed: true, }); @@ -163,13 +163,18 @@ describe("Callback Operations Integration", () => { // Wait more than half a second await new Promise((resolve) => setTimeout(resolve, 600)); + const callbackResult = JSON.stringify({ + processed: 1000, + status: "completed", + }); + // Finally complete the callback - await callbackOperation.sendCallbackSuccess('task_completed_1000'); + await callbackOperation.sendCallbackSuccess(callbackResult); const result = await executionPromise; expect(result.getResult()).toEqual({ - longTaskResult: 'task_completed_1000', + longTaskResult: callbackResult, }); }); @@ -274,18 +279,26 @@ describe("Callback Operations Integration", () => { ]); // Complete callbacks in different order - await callback2.sendCallbackSuccess('result_2_second'); - await callback1.sendCallbackSuccess('result_1_first'); - await callback3.sendCallbackSuccess('result_3_third'); + const callbackResult2 = JSON.stringify({ + id: 2, + data: "second", + }); + await callback2.sendCallbackSuccess(callbackResult2); + const callbackResult1 = JSON.stringify({ + id: 1, + data: "first", + }); + await callback1.sendCallbackSuccess(callbackResult1); + const callbackResult3 = JSON.stringify({ + id: 3, + data: "third", + }); + await callback3.sendCallbackSuccess(callbackResult3); const result = await executionPromise; expect(result.getResult()).toEqual({ - results: [ - 'result_1_first', - 'result_2_second', - 'result_3_third', - ], + results: [callbackResult1, callbackResult2, callbackResult3], allCompleted: true, }); @@ -334,13 +347,17 @@ describe("Callback Operations Integration", () => { await callbackOperation.waitForData(WaitingOperationStatus.STARTED); // Complete the callback - await callbackOperation.sendCallbackSuccess('processed_true_timestamp'); + const callbackResult = JSON.stringify({ + processed: true, + timestamp: Date.now(), + }); + await callbackOperation.sendCallbackSuccess(callbackResult); const result = await executionPromise; expect(result.getResult()).toEqual({ stepResult: { userId: 123, name: "John Doe" }, - callbackResult: 'processed_true_timestamp', + callbackResult: callbackResult, completed: true, }); diff --git a/lambda-durable-functions-testing-sdk-js/src/test-runner/local/__tests__/integration/wait-for-callback-operations.integration.test.ts b/lambda-durable-functions-testing-sdk-js/src/test-runner/local/__tests__/integration/wait-for-callback-operations.integration.test.ts index e4c676fb..09474c59 100644 --- a/lambda-durable-functions-testing-sdk-js/src/test-runner/local/__tests__/integration/wait-for-callback-operations.integration.test.ts +++ b/lambda-durable-functions-testing-sdk-js/src/test-runner/local/__tests__/integration/wait-for-callback-operations.integration.test.ts @@ -48,14 +48,17 @@ describe("WaitForCallback Operations Integration", () => { // Wait for the operation to be available await callbackOperation.waitForData(WaitingOperationStatus.STARTED); + const callbackResult = JSON.stringify({ + data: "callback_completed", + }); // Simulate external system completing the callback - await callbackOperation.sendCallbackSuccess("callback_completed"); + await callbackOperation.sendCallbackSuccess(callbackResult); // Now the execution should complete const result = await executionPromise; expect(result.getResult()).toEqual({ - callbackResult: 'callback_completed', + callbackResult: callbackResult, submitterReceived: true, completed: true, }); @@ -94,14 +97,17 @@ describe("WaitForCallback Operations Integration", () => { // Wait for the operation to be available await callbackOperation.waitForData(WaitingOperationStatus.STARTED); // Simulate external system completing the callback - await callbackOperation.sendCallbackSuccess("callback_completed"); + const callbackResult = JSON.stringify({ + data: "callback_completed", + }); + await callbackOperation.sendCallbackSuccess(callbackResult); // Now the execution should complete const result = await executionPromise; const callbackDetails = callbackOperation.getCallbackDetails(); expect(result.getResult()).toEqual({ - callbackResult: 'callback_completed', + callbackResult, completed: true, callbackId: callbackDetails?.callbackId, }); @@ -335,13 +341,15 @@ describe("WaitForCallback Operations Integration", () => { 1 ); await callbackOperation2.waitForData(WaitingOperationStatus.STARTED); - await callbackOperation2.sendCallbackSuccess({ data: "success-data" }); + + const callbackResult = JSON.stringify({ data: "success-data" }); + await callbackOperation2.sendCallbackSuccess(callbackResult); const result2 = await executionPromise2; expect(result2.getResult()).toEqual({ success: true, - result: '{"data":"success-data"}', + result: callbackResult, scenario: "submitter-succeeds-callback-succeeds", }); @@ -472,15 +480,16 @@ describe("WaitForCallback Operations Integration", () => { const callbackDetails = callbackOperation.getCallbackDetails(); expect(callbackDetails?.callbackId).toBeDefined(); - // Complete the callback successfully - await callbackOperation.sendCallbackSuccess({ + const callbackResult = JSON.stringify({ data: "heartbeat-completed", }); + // Complete the callback successfully + await callbackOperation.sendCallbackSuccess(callbackResult); const result = await executionPromise; expect(result.getResult()).toEqual({ - callbackResult: '{"data":"heartbeat-completed"}', + callbackResult: callbackResult, heartbeatEnabled: true, completed: true, }); @@ -551,17 +560,20 @@ describe("WaitForCallback Operations Integration", () => { await callbackOperation.sendCallbackHeartbeat(); // Finally complete the callback - await callbackOperation.sendCallbackSuccess({ processed: 1000 }); + const callbackResult = JSON.stringify({ + processed: 1000, + }); + await callbackOperation.sendCallbackSuccess(callbackResult); const result = await executionPromise; const resultData = result.getResult() as { - callbackResult: { processed: number }; + callbackResult: string; submitterDuration: number; callbackId: string; }; - expect(resultData.callbackResult).toEqual('{"processed":1000}'); + expect(resultData.callbackResult).toEqual(callbackResult); expect(resultData.submitterDuration).toBeGreaterThan(50); // Should take at least 100ms expect(resultData.callbackId).toBeDefined(); @@ -692,12 +704,22 @@ describe("WaitForCallback Operations Integration", () => { ]); // Complete callbacks in different order to test concurrency - await callback2Op.sendCallbackSuccess({ + const callback2Result = JSON.stringify({ id: 2, data: "second-completed", }); - await callback1Op.sendCallbackSuccess({ id: 1, data: "first-completed" }); - await callback3Op.sendCallbackSuccess({ id: 3, data: "third-completed" }); + const callback1Result = JSON.stringify({ + id: 1, + data: "first-completed", + }); + const callback3Result = JSON.stringify({ + id: 3, + data: "third-completed", + }); + + await callback2Op.sendCallbackSuccess(callback2Result); + await callback1Op.sendCallbackSuccess(callback1Result); + await callback3Op.sendCallbackSuccess(callback3Result); const result = await executionPromise; @@ -788,7 +810,8 @@ describe("WaitForCallback Operations Integration", () => { await callbackOperation.waitForData(WaitingOperationStatus.STARTED); // Complete the callback - await callbackOperation.sendCallbackSuccess({ processed: true }); + const callbackResult = JSON.stringify({ processed: true }); + await callbackOperation.sendCallbackSuccess(callbackResult); const result = await executionPromise; @@ -879,20 +902,22 @@ describe("WaitForCallback Operations Integration", () => { // Wait for parent callback to start await parentCallbackOp.waitForData(WaitingOperationStatus.STARTED); - await parentCallbackOp.sendCallbackSuccess({ + const parentCallbackResult = JSON.stringify({ parentData: "parent-completed", }); + await parentCallbackOp.sendCallbackSuccess(parentCallbackResult); // Wait for child callback to start await childCallbackOp.waitForData(WaitingOperationStatus.STARTED); - await childCallbackOp.sendCallbackSuccess({ childData: 42 }); + const childCallbackResult = JSON.stringify({ childData: 42 }); + await childCallbackOp.sendCallbackSuccess(childCallbackResult); const result = await executionPromise; expect(result.getResult()).toEqual({ - parentResult: '{"parentData":"parent-completed"}', + parentResult: parentCallbackResult, childContextResult: { - childResult: '{"childData":42}', + childResult: childCallbackResult, childProcessed: true, }, callbackIds: { @@ -973,11 +998,13 @@ describe("WaitForCallback Operations Integration", () => { // Wait for first callback and complete it await firstCallbackOp.waitForData(WaitingOperationStatus.STARTED); - await firstCallbackOp.sendCallbackSuccess({ step: 1 }); + const firstCallbackResult = JSON.stringify({ step: 1 }); + await firstCallbackOp.sendCallbackSuccess(firstCallbackResult); // Wait for second callback and complete it await secondCallbackOp.waitForData(WaitingOperationStatus.STARTED); - await secondCallbackOp.sendCallbackSuccess({ step: 2 }); + const secondCallbackResult = JSON.stringify({ step: 2 }); + await secondCallbackOp.sendCallbackSuccess(secondCallbackResult); const result = await executionPromise; @@ -1107,22 +1134,27 @@ describe("WaitForCallback Operations Integration", () => { // Complete callbacks in sequence await outerCallbackOp.waitForData(WaitingOperationStatus.STARTED); - await outerCallbackOp.sendCallbackSuccess({ level: "outer-completed" }); + const outerCallbackResult = JSON.stringify({ level: "outer-completed" }); + await outerCallbackOp.sendCallbackSuccess(outerCallbackResult); await innerCallbackOp.waitForData(WaitingOperationStatus.STARTED); - await innerCallbackOp.sendCallbackSuccess({ level: "inner-completed" }); + const innerCallbackResult = JSON.stringify({ level: "inner-completed" }); + await innerCallbackOp.sendCallbackSuccess(innerCallbackResult); await nestedCallbackOp.waitForData(WaitingOperationStatus.STARTED); - await nestedCallbackOp.sendCallbackSuccess({ level: "nested-completed" }); + const nestedCallbackResult = JSON.stringify({ + level: "nested-completed", + }); + await nestedCallbackOp.sendCallbackSuccess(nestedCallbackResult); const result = await executionPromise; expect(result.getResult()).toEqual({ - outerCallback: '{"level":"outer-completed"}', + outerCallback: outerCallbackResult, nestedResults: { - innerCallback: '{"level":"inner-completed"}', + innerCallback: innerCallbackResult, deepNested: { - nestedCallback: '{"level":"nested-completed"}', + nestedCallback: nestedCallbackResult, deepLevel: "inner-child", }, level: "outer-child", @@ -1203,14 +1235,15 @@ describe("WaitForCallback Operations Integration", () => { expect(callbackDetails?.callbackId).toBeDefined(); // Complete the callback successfully - await callbackOperation.sendCallbackSuccess({ + const customTimeoutResult = JSON.stringify({ data: "custom-timeout-completed", }); + await callbackOperation.sendCallbackSuccess(customTimeoutResult); const result = await executionPromise; expect(result.getResult()).toEqual({ - callbackResult: '{"data":"custom-timeout-completed"}', + callbackResult: customTimeoutResult, timeoutConfigured: true, callbackId: expect.any(String), });