Skip to content

Commit a3bef26

Browse files
authored
fix(testing-sdk): fix result formatter not returning results for timed out statuses (#331)
*Issue #, if available:* *Description of changes:* Before if the execution timed out, the execution would not do anything and getResult would just return undefined. Now, it will properly throw an error, and getError will also return the error correctly. By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
1 parent 93e050d commit a3bef26

File tree

2 files changed

+137
-70
lines changed

2 files changed

+137
-70
lines changed

packages/aws-durable-execution-sdk-js-testing/src/test-runner/local/__tests__/result-formatter.test.ts

Lines changed: 134 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import { ResultFormatter } from "../result-formatter";
22
import { LocalOperationStorage } from "../operations/local-operation-storage";
33
import { OperationWaitManager } from "../operations/operation-wait-manager";
44
import {
5-
OperationStatus,
65
OperationType,
76
Event,
87
EventType,
8+
ExecutionStatus,
99
} from "@aws-sdk/client-lambda";
1010
import { OperationWithData } from "../../common/operations/operation-with-data";
1111
import { IndexedOperations } from "../../common/indexed-operations";
@@ -25,13 +25,13 @@ describe("ResultFormatter", () => {
2525
beforeEach(() => {
2626
mockOperationIndex = new IndexedOperations([]);
2727
mockOperationStorage = new LocalOperationStorage(
28-
new OperationWaitManager(),
28+
new OperationWaitManager(mockOperationIndex),
2929
mockOperationIndex,
3030
mockApiClient,
3131
jest.fn(),
3232
) as jest.Mocked<LocalOperationStorage>;
3333
resultFormatter = new ResultFormatter<{ success: boolean }>();
34-
mockWaitManager = new OperationWaitManager();
34+
mockWaitManager = new OperationWaitManager(mockOperationIndex);
3535
mockApiClient = {} as DurableApiClient;
3636
});
3737

@@ -47,7 +47,7 @@ describe("ResultFormatter", () => {
4747
Id: "op1",
4848
Name: "operation1",
4949
Type: OperationType.STEP,
50-
Status: OperationStatus.SUCCEEDED,
50+
Status: ExecutionStatus.SUCCEEDED,
5151
StartTimestamp: new Date(),
5252
},
5353
events: [],
@@ -58,7 +58,7 @@ describe("ResultFormatter", () => {
5858
mockOperationStorage.getOperations.mockReturnValue(mockOperations);
5959

6060
const lambdaResponse: TestExecutionResult = {
61-
status: OperationStatus.SUCCEEDED,
61+
status: ExecutionStatus.SUCCEEDED,
6262
result: JSON.stringify({ success: true }),
6363
};
6464

@@ -86,7 +86,7 @@ describe("ResultFormatter", () => {
8686
Id: "op1",
8787
Name: "succeededOp",
8888
Type: OperationType.STEP,
89-
Status: OperationStatus.SUCCEEDED,
89+
Status: ExecutionStatus.SUCCEEDED,
9090
StartTimestamp: new Date(),
9191
},
9292
events: [],
@@ -102,7 +102,7 @@ describe("ResultFormatter", () => {
102102
Id: "op2",
103103
Name: "failedOp",
104104
Type: OperationType.STEP,
105-
Status: OperationStatus.FAILED,
105+
Status: ExecutionStatus.FAILED,
106106
StartTimestamp: new Date(),
107107
},
108108
events: [],
@@ -113,7 +113,7 @@ describe("ResultFormatter", () => {
113113
mockOperationStorage.getOperations.mockReturnValue(mockOperations);
114114

115115
const lambdaResponse: TestExecutionResult = {
116-
status: OperationStatus.SUCCEEDED,
116+
status: ExecutionStatus.SUCCEEDED,
117117
result: JSON.stringify({ success: true }),
118118
};
119119

@@ -125,24 +125,24 @@ describe("ResultFormatter", () => {
125125

126126
// Test filtering by SUCCEEDED status
127127
const succeededOps = testResult.getOperations({
128-
status: OperationStatus.SUCCEEDED,
128+
status: ExecutionStatus.SUCCEEDED,
129129
});
130130
expect(succeededOps).toHaveLength(1);
131-
expect(succeededOps[0].getStatus()).toBe(OperationStatus.SUCCEEDED);
131+
expect(succeededOps[0].getStatus()).toBe(ExecutionStatus.SUCCEEDED);
132132

133133
// Test filtering by FAILED status
134134
const failedOps = testResult.getOperations({
135-
status: OperationStatus.FAILED,
135+
status: ExecutionStatus.FAILED,
136136
});
137137
expect(failedOps).toHaveLength(1);
138-
expect(failedOps[0].getStatus()).toBe(OperationStatus.FAILED);
138+
expect(failedOps[0].getStatus()).toBe(ExecutionStatus.FAILED);
139139
});
140140

141141
it("should pass invocations from history to test result", () => {
142142
mockOperationStorage.getOperations.mockReturnValue([]);
143143

144144
const lambdaResponse: TestExecutionResult = {
145-
status: OperationStatus.SUCCEEDED,
145+
status: ExecutionStatus.SUCCEEDED,
146146
result: JSON.stringify({ success: true }),
147147
};
148148

@@ -250,7 +250,7 @@ describe("ResultFormatter", () => {
250250
mockOperationStorage.getOperations.mockReturnValue([]);
251251

252252
const lambdaResponse: TestExecutionResult = {
253-
status: OperationStatus.SUCCEEDED,
253+
status: ExecutionStatus.SUCCEEDED,
254254
result: "",
255255
};
256256

@@ -267,7 +267,7 @@ describe("ResultFormatter", () => {
267267
mockOperationStorage.getOperations.mockReturnValue([]);
268268

269269
const lambdaResponse: TestExecutionResult = {
270-
status: OperationStatus.SUCCEEDED,
270+
status: ExecutionStatus.SUCCEEDED,
271271
result: "raw-string-value",
272272
};
273273

@@ -284,7 +284,7 @@ describe("ResultFormatter", () => {
284284
mockOperationStorage.getOperations.mockReturnValue([]);
285285

286286
const lambdaResponse: TestExecutionResult = {
287-
status: OperationStatus.SUCCEEDED,
287+
status: ExecutionStatus.SUCCEEDED,
288288
result: "{ invalid json",
289289
};
290290

@@ -303,7 +303,7 @@ describe("ResultFormatter", () => {
303303
operation: {
304304
Id: "op1",
305305
Name: "operation1",
306-
Status: OperationStatus.SUCCEEDED,
306+
Status: ExecutionStatus.SUCCEEDED,
307307
Type: OperationType.STEP,
308308
StartTimestamp: new Date(),
309309
},
@@ -314,7 +314,7 @@ describe("ResultFormatter", () => {
314314
operation: {
315315
Id: "op2",
316316
Name: "operation2",
317-
Status: OperationStatus.SUCCEEDED,
317+
Status: ExecutionStatus.SUCCEEDED,
318318
Type: OperationType.WAIT,
319319
StartTimestamp: new Date(),
320320
},
@@ -334,7 +334,7 @@ describe("ResultFormatter", () => {
334334
mockOperationStorage.getOperations.mockReturnValue(mockOperations);
335335

336336
const lambdaResponse: TestExecutionResult = {
337-
status: OperationStatus.SUCCEEDED,
337+
status: ExecutionStatus.SUCCEEDED,
338338
result: JSON.stringify({ success: true }),
339339
};
340340

@@ -362,7 +362,7 @@ describe("ResultFormatter", () => {
362362
};
363363

364364
const lambdaResponse: TestExecutionResult = {
365-
status: OperationStatus.SUCCEEDED,
365+
status: ExecutionStatus.SUCCEEDED,
366366
result: JSON.stringify(complexData),
367367
};
368368

@@ -375,9 +375,9 @@ describe("ResultFormatter", () => {
375375
expect(testResult.getResult()).toEqual(complexData);
376376
});
377377

378-
it("should throw Error with 'Execution failed' when ErrorMessage is empty string", () => {
378+
it("should throw Error when error object is not found for failed execution", () => {
379379
const lambdaResponse: TestExecutionResult = {
380-
status: OperationStatus.FAILED,
380+
status: ExecutionStatus.FAILED,
381381
result: JSON.stringify({ error: "" }),
382382
};
383383

@@ -387,12 +387,14 @@ describe("ResultFormatter", () => {
387387
mockOperationStorage,
388388
);
389389

390-
expect(() => testResult.getResult()).toThrow("Execution failed");
390+
expect(() => testResult.getResult()).toThrow(
391+
"Could not find error result",
392+
);
391393
});
392394

393-
it("should clean stack trace by removing ResultFormatter references", () => {
395+
it("should not clean stack trace if error had no stack trace", () => {
394396
const lambdaResponse: TestExecutionResult = {
395-
status: OperationStatus.FAILED,
397+
status: ExecutionStatus.FAILED,
396398
result: JSON.stringify({ error: "Test error" }),
397399
};
398400

@@ -408,18 +410,17 @@ describe("ResultFormatter", () => {
408410
} catch (error) {
409411
thrownError = error as Error;
410412
}
411-
// The stack should be cleaned to remove ResultFormatter references
412413
expect(thrownError!.stack).toBeDefined();
413-
expect(thrownError!.stack).not.toContain("ResultFormatter");
414+
expect(thrownError!.stack).toContain("ResultFormatter");
414415
});
415416
});
416417

417418
describe("getError behavior", () => {
418-
it("should return ErrorObject for failed invocation", () => {
419+
it("should not return ErrorObject for failed invocation if error object is missing", () => {
419420
const mockError = "Handler execution failed";
420421

421422
const lambdaResponse: TestExecutionResult = {
422-
status: OperationStatus.FAILED,
423+
status: ExecutionStatus.FAILED,
423424
result: JSON.stringify({ error: mockError }),
424425
};
425426

@@ -429,14 +430,14 @@ describe("ResultFormatter", () => {
429430
mockOperationStorage,
430431
);
431432

432-
expect(testResult.getError()).toEqual({
433-
errorMessage: mockError,
434-
});
433+
expect(() => testResult.getError()).toThrow(
434+
"Could not find error result",
435+
);
435436
});
436437

437438
it("should throw error when called on successful execution", () => {
438439
const lambdaResponse: TestExecutionResult = {
439-
status: OperationStatus.SUCCEEDED,
440+
status: ExecutionStatus.SUCCEEDED,
440441
result: JSON.stringify({ success: true }),
441442
};
442443

@@ -453,7 +454,7 @@ describe("ResultFormatter", () => {
453454

454455
it("should throw error when parsing fails", () => {
455456
const lambdaResponse: TestExecutionResult = {
456-
status: OperationStatus.FAILED,
457+
status: ExecutionStatus.FAILED,
457458
result: "Raw error message - not JSON",
458459
};
459460

@@ -470,7 +471,7 @@ describe("ResultFormatter", () => {
470471

471472
it("should throw error when error field is not a string", () => {
472473
const lambdaResponse: TestExecutionResult = {
473-
status: OperationStatus.FAILED,
474+
status: ExecutionStatus.FAILED,
474475
result: JSON.stringify({ error: { nested: "object" } }),
475476
};
476477

@@ -487,7 +488,7 @@ describe("ResultFormatter", () => {
487488

488489
it("should return ErrorObject when error object is specified", () => {
489490
const lambdaResponse: TestExecutionResult = {
490-
status: OperationStatus.FAILED,
491+
status: ExecutionStatus.FAILED,
491492
result: JSON.stringify({ someOtherField: "value" }),
492493
error: {
493494
ErrorData: "my-error-data",
@@ -530,7 +531,7 @@ describe("ResultFormatter", () => {
530531
];
531532

532533
const lambdaResponse: TestExecutionResult = {
533-
status: OperationStatus.SUCCEEDED,
534+
status: ExecutionStatus.SUCCEEDED,
534535
result: JSON.stringify({ success: true }),
535536
};
536537

@@ -547,7 +548,7 @@ describe("ResultFormatter", () => {
547548
mockOperationStorage.getOperations.mockReturnValue([]);
548549

549550
const lambdaResponse: TestExecutionResult = {
550-
status: OperationStatus.SUCCEEDED,
551+
status: ExecutionStatus.SUCCEEDED,
551552
result: JSON.stringify({ success: true }),
552553
};
553554

@@ -566,7 +567,7 @@ describe("ResultFormatter", () => {
566567
mockOperationStorage.getOperations.mockReturnValue([]);
567568

568569
const lambdaResponse: TestExecutionResult = {
569-
status: OperationStatus.SUCCEEDED,
570+
status: ExecutionStatus.SUCCEEDED,
570571
result: JSON.stringify({ success: true }),
571572
};
572573

@@ -576,7 +577,100 @@ describe("ResultFormatter", () => {
576577
mockOperationStorage,
577578
);
578579

579-
expect(testResult.getStatus()).toBe(OperationStatus.SUCCEEDED);
580+
expect(testResult.getStatus()).toBe(ExecutionStatus.SUCCEEDED);
581+
});
582+
});
583+
584+
describe("getResult with stackTrace", () => {
585+
it("should set error stack trace when errorFromResult.stackTrace is not undefined", () => {
586+
const lambdaResponse: TestExecutionResult = {
587+
status: ExecutionStatus.FAILED,
588+
result: JSON.stringify({ someField: "value" }),
589+
error: {
590+
ErrorMessage: "Test error message",
591+
ErrorData: "test-data",
592+
ErrorType: "TestError",
593+
StackTrace: ["at line 1", "at line 2", "at line 3"],
594+
},
595+
};
596+
597+
const testResult = resultFormatter.formatTestResult(
598+
lambdaResponse,
599+
[],
600+
mockOperationStorage,
601+
);
602+
603+
let thrownError: Error;
604+
try {
605+
testResult.getResult();
606+
} catch (error) {
607+
thrownError = error as Error;
608+
}
609+
610+
expect(thrownError!.message).toBe("Test error message");
611+
expect(thrownError!.stack).toBe("at line 1\nat line 2\nat line 3");
612+
});
613+
});
614+
615+
describe("getResult error throwing for non-succeeded statuses", () => {
616+
it.each([
617+
ExecutionStatus.FAILED,
618+
ExecutionStatus.RUNNING,
619+
ExecutionStatus.STOPPED,
620+
ExecutionStatus.TIMED_OUT,
621+
])("should throw an error when status is %s", (status) => {
622+
const lambdaResponse: TestExecutionResult = {
623+
status,
624+
result: JSON.stringify({ someField: "value" }),
625+
error: {
626+
ErrorMessage: "Execution failed",
627+
ErrorData: "error-data",
628+
ErrorType: "ExecutionError",
629+
StackTrace: ["stack trace line"],
630+
},
631+
};
632+
633+
const testResult = resultFormatter.formatTestResult(
634+
lambdaResponse,
635+
[],
636+
mockOperationStorage,
637+
);
638+
639+
expect(() => testResult.getResult()).toThrow("Execution failed");
640+
});
641+
});
642+
643+
describe("getError for non-succeeded statuses", () => {
644+
it.each([
645+
ExecutionStatus.FAILED,
646+
ExecutionStatus.RUNNING,
647+
ExecutionStatus.STOPPED,
648+
ExecutionStatus.TIMED_OUT,
649+
])("should return error for status %s", (status) => {
650+
const lambdaResponse: TestExecutionResult = {
651+
status,
652+
result: JSON.stringify({ someField: "value" }),
653+
error: {
654+
ErrorMessage: "Test error message",
655+
ErrorData: "test-error-data",
656+
ErrorType: "TestErrorType",
657+
StackTrace: ["stack line 1", "stack line 2"],
658+
},
659+
};
660+
661+
const testResult = resultFormatter.formatTestResult(
662+
lambdaResponse,
663+
[],
664+
mockOperationStorage,
665+
);
666+
667+
const error = testResult.getError();
668+
expect(error).toEqual({
669+
errorMessage: "Test error message",
670+
errorData: "test-error-data",
671+
errorType: "TestErrorType",
672+
stackTrace: ["stack line 1", "stack line 2"],
673+
});
580674
});
581675
});
582676
});

0 commit comments

Comments
 (0)