Skip to content

Commit 4937b98

Browse files
fix: restore boolean return type for isApproved and add metadata.status fallback
- ResumePlanResponse.isApproved(): Boolean -> boolean (binary compat fix) - Add metadata.status check in executePlan status extraction - Status precedence: data.status > metadata.status > top-level > default - Add 3 tests: nested failure, metadata fallback, null approved handling
1 parent 58886e3 commit 4937b98

3 files changed

Lines changed: 55 additions & 3 deletions

File tree

src/main/java/com/getaxonflow/sdk/AxonFlow.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -832,6 +832,7 @@ private PlanResponse parseExecutePlanResponse(Response response, String planId)
832832
String result = (String) agentResponse.get("result");
833833

834834
// Read status from response data (e.g., "awaiting_approval" for confirm mode)
835+
// Precedence: data.status > metadata.status > top-level status > "completed"
835836
String status = "completed";
836837
Object dataObj2 = agentResponse.get("data");
837838
if (dataObj2 instanceof Map) {
@@ -842,6 +843,17 @@ private PlanResponse parseExecutePlanResponse(Response response, String planId)
842843
status = (String) dataStatus;
843844
}
844845
}
846+
if ("completed".equals(status)) {
847+
Object metaObj = agentResponse.get("metadata");
848+
if (metaObj instanceof Map) {
849+
@SuppressWarnings("unchecked")
850+
Map<String, Object> metaMap = (Map<String, Object>) metaObj;
851+
Object metaStatus = metaMap.get("status");
852+
if (metaStatus instanceof String && !((String) metaStatus).isEmpty()) {
853+
status = (String) metaStatus;
854+
}
855+
}
856+
}
845857
if ("completed".equals(status)) {
846858
Object topStatus = agentResponse.get("status");
847859
if (topStatus instanceof String && !((String) topStatus).isEmpty()) {

src/main/java/com/getaxonflow/sdk/types/ResumePlanResponse.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,10 @@ public String getWorkflowId() {
9999
/**
100100
* Returns whether the plan was approved to continue.
101101
*
102-
* @return true if the plan was approved, null if not applicable
102+
* @return true if the plan was approved, false if not approved or not applicable
103103
*/
104-
public Boolean isApproved() {
105-
return approved;
104+
public boolean isApproved() {
105+
return Boolean.TRUE.equals(approved);
106106
}
107107

108108
/**

src/test/java/com/getaxonflow/sdk/AxonFlowTest.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,46 @@ void getPlanStatusShouldReturnStatus() {
418418
assertThat(response.getStatus()).isEqualTo("pending");
419419
}
420420

421+
@Test
422+
@DisplayName("executePlan should throw when nested data.success is false")
423+
void executePlanShouldThrowOnNestedDataFailure() {
424+
stubFor(post(urlEqualTo("/api/request"))
425+
.willReturn(aResponse()
426+
.withStatus(200)
427+
.withHeader("Content-Type", "application/json")
428+
.withBody("{\"success\":true,\"data\":{\"success\":false,\"error\":\"Step 2 timed out\"}}")));
429+
430+
assertThatThrownBy(() -> axonflow.executePlan("plan_fail"))
431+
.isInstanceOf(PlanExecutionException.class)
432+
.hasMessageContaining("Step 2 timed out");
433+
}
434+
435+
@Test
436+
@DisplayName("executePlan should use metadata.status when data.status is absent")
437+
void executePlanShouldFallbackToMetadataStatus() {
438+
// No data.status, but metadata.status is present — should use metadata.status
439+
stubFor(post(urlEqualTo("/api/request"))
440+
.willReturn(aResponse()
441+
.withStatus(200)
442+
.withHeader("Content-Type", "application/json")
443+
.withBody("{\"success\":true,\"result\":\"done\",\"metadata\":{\"status\":\"awaiting_approval\"}}")));
444+
445+
PlanResponse response = axonflow.executePlan("plan_meta");
446+
447+
assertThat(response.getStatus()).isEqualTo("awaiting_approval");
448+
}
449+
450+
@Test
451+
@DisplayName("isApproved should return false when approved field is null")
452+
void isApprovedShouldReturnFalseWhenNull() {
453+
// Construct a ResumePlanResponse with null approved field
454+
ResumePlanResponse response = new ResumePlanResponse(
455+
"plan_123", "wf_456", "in_progress", null, "Pending review", 2, "Step 2", 5);
456+
457+
// Must return false (not throw NPE)
458+
assertThat(response.isApproved()).isFalse();
459+
}
460+
421461
// ========================================================================
422462
// Orchestrator Health Check
423463
// ========================================================================

0 commit comments

Comments
 (0)