Skip to content

Commit 73785fe

Browse files
authored
[copilot-finds] Bug: newOrchestrationState drops failure details when error message or type is empty string (#198)
1 parent 826f90c commit 73785fe

2 files changed

Lines changed: 228 additions & 7 deletions

File tree

packages/durabletask-js/src/orchestration/index.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,12 @@ export function newOrchestrationState(
1818
const state = res.getOrchestrationstate();
1919
let failureDetails;
2020

21-
const failureDetailsErrorMessage = state?.getFailuredetails()?.getErrormessage();
22-
const failureDetailsErrorType = state?.getFailuredetails()?.getErrortype();
23-
24-
if (state && failureDetailsErrorMessage && failureDetailsErrorType) {
21+
const protoFailureDetails = state?.getFailuredetails();
22+
if (protoFailureDetails) {
2523
failureDetails = new FailureDetails(
26-
failureDetailsErrorMessage,
27-
failureDetailsErrorType,
28-
state.getFailuredetails()?.getStacktrace()?.toString(),
24+
protoFailureDetails.getErrormessage(),
25+
protoFailureDetails.getErrortype(),
26+
protoFailureDetails.getStacktrace()?.getValue(),
2927
);
3028
}
3129

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import * as pb from "../src/proto/orchestrator_service_pb";
5+
import { StringValue } from "google-protobuf/google/protobuf/wrappers_pb";
6+
import { Timestamp } from "google-protobuf/google/protobuf/timestamp_pb";
7+
import { newOrchestrationState } from "../src/orchestration";
8+
9+
/**
10+
* Helper to create a GetInstanceResponse with the given orchestration state.
11+
*/
12+
function createResponse(
13+
name: string,
14+
status: pb.OrchestrationStatus,
15+
options?: {
16+
failureDetails?: pb.TaskFailureDetails;
17+
input?: string;
18+
output?: string;
19+
customStatus?: string;
20+
},
21+
): pb.GetInstanceResponse {
22+
const state = new pb.OrchestrationState();
23+
state.setName(name);
24+
state.setOrchestrationstatus(status);
25+
26+
const now = new Timestamp();
27+
now.fromDate(new Date());
28+
state.setCreatedtimestamp(now);
29+
state.setLastupdatedtimestamp(now);
30+
31+
if (options?.failureDetails) {
32+
state.setFailuredetails(options.failureDetails);
33+
}
34+
35+
if (options?.input !== undefined) {
36+
const inputValue = new StringValue();
37+
inputValue.setValue(options.input);
38+
state.setInput(inputValue);
39+
}
40+
41+
if (options?.output !== undefined) {
42+
const outputValue = new StringValue();
43+
outputValue.setValue(options.output);
44+
state.setOutput(outputValue);
45+
}
46+
47+
if (options?.customStatus !== undefined) {
48+
const customStatusValue = new StringValue();
49+
customStatusValue.setValue(options.customStatus);
50+
state.setCustomstatus(customStatusValue);
51+
}
52+
53+
const res = new pb.GetInstanceResponse();
54+
res.setExists(true);
55+
res.setOrchestrationstate(state);
56+
return res;
57+
}
58+
59+
describe("newOrchestrationState", () => {
60+
it("should return undefined when response is undefined", () => {
61+
const result = newOrchestrationState("test-id", undefined);
62+
expect(result).toBeUndefined();
63+
});
64+
65+
it("should return undefined when instance does not exist", () => {
66+
const res = new pb.GetInstanceResponse();
67+
res.setExists(false);
68+
const result = newOrchestrationState("test-id", res);
69+
expect(result).toBeUndefined();
70+
});
71+
72+
it("should return state for a completed orchestration", () => {
73+
const res = createResponse(
74+
"TestOrchestrator",
75+
pb.OrchestrationStatus.ORCHESTRATION_STATUS_COMPLETED,
76+
{ output: '"hello"' },
77+
);
78+
79+
const result = newOrchestrationState("test-id", res);
80+
81+
expect(result).toBeDefined();
82+
expect(result!.instanceId).toBe("test-id");
83+
expect(result!.name).toBe("TestOrchestrator");
84+
expect(result!.serializedOutput).toBe('"hello"');
85+
expect(result!.failureDetails).toBeUndefined();
86+
});
87+
88+
it("should preserve failure details with non-empty error message", () => {
89+
const failureDetails = new pb.TaskFailureDetails();
90+
failureDetails.setErrormessage("Something went wrong");
91+
failureDetails.setErrortype("Error");
92+
const stackValue = new StringValue();
93+
stackValue.setValue("Error: Something went wrong\n at test.ts:1");
94+
failureDetails.setStacktrace(stackValue);
95+
96+
const res = createResponse(
97+
"TestOrchestrator",
98+
pb.OrchestrationStatus.ORCHESTRATION_STATUS_FAILED,
99+
{ failureDetails },
100+
);
101+
102+
const result = newOrchestrationState("test-id", res);
103+
104+
expect(result).toBeDefined();
105+
expect(result!.failureDetails).toBeDefined();
106+
expect(result!.failureDetails!.message).toBe("Something went wrong");
107+
expect(result!.failureDetails!.errorType).toBe("Error");
108+
expect(result!.failureDetails!.stackTrace).toBe(
109+
"Error: Something went wrong\n at test.ts:1",
110+
);
111+
});
112+
113+
it("should preserve failure details when error message is an empty string", () => {
114+
const failureDetails = new pb.TaskFailureDetails();
115+
failureDetails.setErrormessage("");
116+
failureDetails.setErrortype("Error");
117+
118+
const res = createResponse(
119+
"TestOrchestrator",
120+
pb.OrchestrationStatus.ORCHESTRATION_STATUS_FAILED,
121+
{ failureDetails },
122+
);
123+
124+
const result = newOrchestrationState("test-id", res);
125+
126+
expect(result).toBeDefined();
127+
expect(result!.failureDetails).toBeDefined();
128+
expect(result!.failureDetails!.message).toBe("");
129+
expect(result!.failureDetails!.errorType).toBe("Error");
130+
});
131+
132+
it("should preserve failure details when error type is an empty string", () => {
133+
const failureDetails = new pb.TaskFailureDetails();
134+
failureDetails.setErrormessage("Some error");
135+
failureDetails.setErrortype("");
136+
137+
const res = createResponse(
138+
"TestOrchestrator",
139+
pb.OrchestrationStatus.ORCHESTRATION_STATUS_FAILED,
140+
{ failureDetails },
141+
);
142+
143+
const result = newOrchestrationState("test-id", res);
144+
145+
expect(result).toBeDefined();
146+
expect(result!.failureDetails).toBeDefined();
147+
expect(result!.failureDetails!.message).toBe("Some error");
148+
expect(result!.failureDetails!.errorType).toBe("");
149+
});
150+
151+
it("should preserve failure details when both error message and type are empty strings", () => {
152+
const failureDetails = new pb.TaskFailureDetails();
153+
failureDetails.setErrormessage("");
154+
failureDetails.setErrortype("");
155+
156+
const res = createResponse(
157+
"TestOrchestrator",
158+
pb.OrchestrationStatus.ORCHESTRATION_STATUS_FAILED,
159+
{ failureDetails },
160+
);
161+
162+
const result = newOrchestrationState("test-id", res);
163+
164+
expect(result).toBeDefined();
165+
expect(result!.failureDetails).toBeDefined();
166+
expect(result!.failureDetails!.message).toBe("");
167+
expect(result!.failureDetails!.errorType).toBe("");
168+
});
169+
170+
it("should not set failure details when no failure details exist in response", () => {
171+
const res = createResponse(
172+
"TestOrchestrator",
173+
pb.OrchestrationStatus.ORCHESTRATION_STATUS_COMPLETED,
174+
);
175+
176+
const result = newOrchestrationState("test-id", res);
177+
178+
expect(result).toBeDefined();
179+
expect(result!.failureDetails).toBeUndefined();
180+
});
181+
182+
it("should extract stack trace using getValue() instead of toString()", () => {
183+
const failureDetails = new pb.TaskFailureDetails();
184+
failureDetails.setErrormessage("test error");
185+
failureDetails.setErrortype("TypeError");
186+
const stackValue = new StringValue();
187+
stackValue.setValue("TypeError: test error\n at Object.<anonymous> (test.ts:1:1)");
188+
failureDetails.setStacktrace(stackValue);
189+
190+
const res = createResponse(
191+
"TestOrchestrator",
192+
pb.OrchestrationStatus.ORCHESTRATION_STATUS_FAILED,
193+
{ failureDetails },
194+
);
195+
196+
const result = newOrchestrationState("test-id", res);
197+
198+
expect(result).toBeDefined();
199+
expect(result!.failureDetails).toBeDefined();
200+
expect(result!.failureDetails!.stackTrace).toBe(
201+
"TypeError: test error\n at Object.<anonymous> (test.ts:1:1)",
202+
);
203+
});
204+
205+
it("should handle failure details without stack trace", () => {
206+
const failureDetails = new pb.TaskFailureDetails();
207+
failureDetails.setErrormessage("error without stack");
208+
failureDetails.setErrortype("Error");
209+
210+
const res = createResponse(
211+
"TestOrchestrator",
212+
pb.OrchestrationStatus.ORCHESTRATION_STATUS_FAILED,
213+
{ failureDetails },
214+
);
215+
216+
const result = newOrchestrationState("test-id", res);
217+
218+
expect(result).toBeDefined();
219+
expect(result!.failureDetails).toBeDefined();
220+
expect(result!.failureDetails!.message).toBe("error without stack");
221+
expect(result!.failureDetails!.stackTrace).toBeUndefined();
222+
});
223+
});

0 commit comments

Comments
 (0)