实现M002 V3 P0.1 Parent Group路由修订
This commit is contained in:
@@ -7,8 +7,8 @@ import org.junit.jupiter.api.Test;
|
||||
class ReservationAiRouteDefinitionTest {
|
||||
|
||||
@Test
|
||||
void shouldKeepFortyTwoM002V3P0RouteDefinitions() {
|
||||
assertThat(ReservationAiRouteDefinition.values()).hasSize(42);
|
||||
void shouldKeepFortyM002V3P01RouteDefinitions() {
|
||||
assertThat(ReservationAiRouteDefinition.values()).hasSize(40);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -25,5 +25,15 @@ class ReservationAiRouteDefinitionTest {
|
||||
"Payment Evidence",
|
||||
"payment_evidence"))
|
||||
.contains(ReservationAiRouteDefinition.PAYMENT_EVIDENCE_REVIEW);
|
||||
assertThat(ReservationAiRouteDefinition.findByTriplet(
|
||||
"normal_task",
|
||||
"Cancel Allotment",
|
||||
"cancel_allotment_control_block"))
|
||||
.contains(ReservationAiRouteDefinition.CANCEL_ALLOTMENT_CONTROL_BLOCK_NORMAL);
|
||||
assertThat(ReservationAiRouteDefinition.findByTriplet(
|
||||
"normal_task",
|
||||
"Cancel Booking",
|
||||
"linked_parent_release_after_child_split"))
|
||||
.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +149,35 @@ class SuperAgentTaskResultControllerTest {
|
||||
org.assertj.core.api.Assertions.assertThat(taskCardCount).isGreaterThanOrEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectLegacyAiTaskResultsParentSplitCancelBooking() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage("mail-v2-parent-split-legacy-001");
|
||||
String body = minimalBody(source.inboxId().toString(), "Cancel Booking", "normal_task",
|
||||
"linked_parent_release_after_child_split", """
|
||||
"case_keys": {"group_code": "PARENT-V2-001"},
|
||||
"extracted_fields": {
|
||||
"cancel_object_type": "group_block",
|
||||
"parent_release_or_cancel_candidate": true,
|
||||
"release_reason": "parent_to_child_allocation_split",
|
||||
"parent_group_code": "PARENT-V2-001",
|
||||
"child_group_codes": ["CHILD-V2-001"],
|
||||
"allocation_split_from_parent": true
|
||||
}
|
||||
""");
|
||||
|
||||
mockMvc.perform(signedPost(body, "nonce-v2-parent-split-legacy-001"))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.error_code").value("ADAPTER_CONTRACT_ERROR"))
|
||||
.andExpect(content().string(containsString("Cancel Booking + linked_parent_release_after_child_split")));
|
||||
|
||||
Long taskCount = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM workflow_reservation_task
|
||||
WHERE source_message_id = ?
|
||||
""", Long.class, source.inboxId());
|
||||
assertThat(taskCount).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveTaskResultSourceMessageByExternalMessageId() throws Exception {
|
||||
String externalMessageId = "mail-external-source-id-001";
|
||||
|
||||
@@ -269,6 +269,7 @@ class SuperAgentTaskResultP0FixtureRegressionTest {
|
||||
void shouldCreateParentSplitTasksFromP0FixtureInEventOrder() throws Exception {
|
||||
ObjectNode root = fixture("parent_split_two_children.json").deepCopy();
|
||||
useParentSplitBusinessKeys(root, "PARENT-2608-OK", "CHILD-2608-OK-A", "CHILD-2608-OK-B");
|
||||
convertParentSplitRootToP01(root);
|
||||
String externalId = "p0-parent-split-001";
|
||||
SourceMessageCaptureResult source = captureSourceMessage(externalId);
|
||||
|
||||
@@ -279,7 +280,7 @@ class SuperAgentTaskResultP0FixtureRegressionTest {
|
||||
.andExpect(jsonPath("$.items[0].execution_order").value(1))
|
||||
.andExpect(jsonPath("$.items[1].route_code").value("R02_NEW_GROUP_BLOCK_NORMAL"))
|
||||
.andExpect(jsonPath("$.items[1].execution_order").value(1))
|
||||
.andExpect(jsonPath("$.items[2].route_code").value("R07_LINKED_PARENT_RELEASE_AFTER_CHILD_SPLIT_NORMAL"))
|
||||
.andExpect(jsonPath("$.items[2].route_code").value("R08_CANCEL_ALLOTMENT_CONTROL_BLOCK_NORMAL"))
|
||||
.andReturn();
|
||||
|
||||
String parentOrderId = com.jayway.jsonpath.JsonPath.read(
|
||||
@@ -297,16 +298,41 @@ class SuperAgentTaskResultP0FixtureRegressionTest {
|
||||
WHERE source_message_id = ?
|
||||
AND route_code IN (
|
||||
'R02_NEW_GROUP_BLOCK_NORMAL',
|
||||
'R07_LINKED_PARENT_RELEASE_AFTER_CHILD_SPLIT_NORMAL'
|
||||
'R08_CANCEL_ALLOTMENT_CONTROL_BLOCK_NORMAL'
|
||||
)
|
||||
""", Long.class, source.inboxId());
|
||||
assertThat(transitionCount).isEqualTo(3L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailClosedWhenCurrentProducerUsesLegacyParentCancelBooking() throws Exception {
|
||||
ObjectNode root = fixture("parent_split_two_children.json").deepCopy();
|
||||
useParentSplitBusinessKeys(root, "PARENT-2608-LEG", "CHILD-2608-LEG-A", "CHILD-2608-LEG-B");
|
||||
String externalId = "p0-parent-split-legacy-001";
|
||||
SourceMessageCaptureResult source = captureSourceMessage(externalId);
|
||||
|
||||
mockMvc.perform(signedPost(withSourceMessageId(root, externalId), "nonce-p0-parent-split-legacy-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.accepted_count").value(3))
|
||||
.andExpect(jsonPath("$.items[0].route_code").value("R02_NEW_GROUP_BLOCK_NORMAL"))
|
||||
.andExpect(jsonPath("$.items[1].route_code").value("R02_NEW_GROUP_BLOCK_NORMAL"))
|
||||
.andExpect(jsonPath("$.items[2].adapter_error_code")
|
||||
.value("LINKED_PARENT_RELEASE_LEGACY_CANCEL_BOOKING_UNSUPPORTED"))
|
||||
.andExpect(jsonPath("$.items[2].task_id").doesNotExist());
|
||||
|
||||
Long taskCount = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM workflow_reservation_task
|
||||
WHERE source_message_id = ?
|
||||
""", Long.class, source.inboxId());
|
||||
assertThat(taskCount).isEqualTo(2L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailClosedWhenParentSplitReferencesDoNotMatchChildEvents() throws Exception {
|
||||
ObjectNode root = fixture("parent_split_two_children.json").deepCopy();
|
||||
useParentSplitBusinessKeys(root, "PARENT-2608-MIS", "CHILD-2608-MIS-A", "CHILD-2608-MIS-B");
|
||||
convertParentSplitRootToP01(root);
|
||||
ObjectNode parentEvent = (ObjectNode) root.path("message_events").get(2);
|
||||
((ArrayNode) parentEvent.path("related_source_event_indices"))
|
||||
.set(1, objectMapper.getNodeFactory().textNode("E_UNKNOWN_CHILD"));
|
||||
@@ -330,9 +356,97 @@ class SuperAgentTaskResultP0FixtureRegressionTest {
|
||||
assertThat(taskCount).isEqualTo(2L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailClosedWhenParentSplitReferenceOrderDoesNotMatchChildCodes() throws Exception {
|
||||
ObjectNode root = fixture("parent_split_two_children.json").deepCopy();
|
||||
useParentSplitBusinessKeys(root, "PARENT-2608-ORD", "CHILD-2608-ORD-A", "CHILD-2608-ORD-B");
|
||||
convertParentSplitRootToP01(root);
|
||||
ObjectNode parentEvent = (ObjectNode) root.path("message_events").get(2);
|
||||
ArrayNode relatedIndices = (ArrayNode) parentEvent.path("related_source_event_indices");
|
||||
relatedIndices.removeAll();
|
||||
relatedIndices.add("E_CHILD_2");
|
||||
relatedIndices.add("E_CHILD_1");
|
||||
String externalId = "p0-parent-split-order-mismatch-001";
|
||||
SourceMessageCaptureResult source = captureSourceMessage(externalId);
|
||||
|
||||
mockMvc.perform(signedPost(withSourceMessageId(root, externalId), "nonce-p0-parent-split-order-mismatch-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.accepted_count").value(3))
|
||||
.andExpect(jsonPath("$.items[0].route_code").value("R02_NEW_GROUP_BLOCK_NORMAL"))
|
||||
.andExpect(jsonPath("$.items[1].route_code").value("R02_NEW_GROUP_BLOCK_NORMAL"))
|
||||
.andExpect(jsonPath("$.items[2].adapter_error_code")
|
||||
.value("LINKED_PARENT_RELEASE_CONTRACT_INCOMPLETE"))
|
||||
.andExpect(jsonPath("$.items[2].task_id").doesNotExist());
|
||||
|
||||
Long taskCount = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM workflow_reservation_task
|
||||
WHERE source_message_id = ?
|
||||
""", Long.class, source.inboxId());
|
||||
assertThat(taskCount).isEqualTo(2L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailClosedWhenParentSplitRequiredP01FieldsAreMissing() throws Exception {
|
||||
ObjectNode root = fixture("parent_split_two_children.json").deepCopy();
|
||||
useParentSplitBusinessKeys(root, "PARENT-2608-FIX", "CHILD-2608-FIX-A", "CHILD-2608-FIX-B");
|
||||
convertParentSplitRootToP01(root);
|
||||
ObjectNode parentExtractedFields = (ObjectNode) root.path("message_events").get(2).path("extracted_fields");
|
||||
parentExtractedFields.remove("parent_release_or_cancel_candidate");
|
||||
String externalId = "p0-parent-split-fixed-field-missing-001";
|
||||
SourceMessageCaptureResult source = captureSourceMessage(externalId);
|
||||
|
||||
mockMvc.perform(signedPost(withSourceMessageId(root, externalId), "nonce-p0-parent-split-fixed-field-missing-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.accepted_count").value(3))
|
||||
.andExpect(jsonPath("$.items[0].route_code").value("R02_NEW_GROUP_BLOCK_NORMAL"))
|
||||
.andExpect(jsonPath("$.items[1].route_code").value("R02_NEW_GROUP_BLOCK_NORMAL"))
|
||||
.andExpect(jsonPath("$.items[2].adapter_error_code")
|
||||
.value("LINKED_PARENT_RELEASE_CONTRACT_INCOMPLETE"))
|
||||
.andExpect(jsonPath("$.items[2].task_id").doesNotExist());
|
||||
|
||||
Long taskCount = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM workflow_reservation_task
|
||||
WHERE source_message_id = ?
|
||||
""", Long.class, source.inboxId());
|
||||
assertThat(taskCount).isEqualTo(2L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailClosedWhenParentSplitHasDuplicateParentCandidate() throws Exception {
|
||||
ObjectNode root = fixture("parent_split_two_children.json").deepCopy();
|
||||
useParentSplitBusinessKeys(root, "PARENT-2608-DUP", "CHILD-2608-DUP-A", "CHILD-2608-DUP-B");
|
||||
convertParentSplitRootToP01(root);
|
||||
ArrayNode events = (ArrayNode) root.path("message_events");
|
||||
ObjectNode duplicateParent = events.get(2).deepCopy();
|
||||
duplicateParent.put("source_event_index", "E_PARENT_CANCEL_DUP");
|
||||
events.add(duplicateParent);
|
||||
String externalId = "p0-parent-split-duplicate-parent-001";
|
||||
SourceMessageCaptureResult source = captureSourceMessage(externalId);
|
||||
|
||||
mockMvc.perform(signedPost(withSourceMessageId(root, externalId), "nonce-p0-parent-split-duplicate-parent-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.accepted_count").value(4))
|
||||
.andExpect(jsonPath("$.items[0].route_code").value("R02_NEW_GROUP_BLOCK_NORMAL"))
|
||||
.andExpect(jsonPath("$.items[1].route_code").value("R02_NEW_GROUP_BLOCK_NORMAL"))
|
||||
.andExpect(jsonPath("$.items[2].route_code").value("R08_CANCEL_ALLOTMENT_CONTROL_BLOCK_NORMAL"))
|
||||
.andExpect(jsonPath("$.items[3].adapter_error_code")
|
||||
.value("LINKED_PARENT_RELEASE_DUPLICATE_PARENT"))
|
||||
.andExpect(jsonPath("$.items[3].task_id").doesNotExist());
|
||||
|
||||
Long taskCount = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM workflow_reservation_task
|
||||
WHERE source_message_id = ?
|
||||
""", Long.class, source.inboxId());
|
||||
assertThat(taskCount).isEqualTo(3L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateRowDerivedMainParentAndTraceTasksFromP0Fixture() throws Exception {
|
||||
ObjectNode root = fixture("row_multiple_derived.json").deepCopy();
|
||||
convertParentSplitRootToP01(root);
|
||||
String externalId = "p0-row-derived-001";
|
||||
SourceMessageCaptureResult source = captureSourceMessage(externalId);
|
||||
|
||||
@@ -340,7 +454,7 @@ class SuperAgentTaskResultP0FixtureRegressionTest {
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.accepted_count").value(3))
|
||||
.andExpect(jsonPath("$.items[0].route_code").value("R02_NEW_GROUP_BLOCK_NORMAL"))
|
||||
.andExpect(jsonPath("$.items[1].route_code").value("R07_LINKED_PARENT_RELEASE_AFTER_CHILD_SPLIT_NORMAL"))
|
||||
.andExpect(jsonPath("$.items[1].route_code").value("R08_CANCEL_ALLOTMENT_CONTROL_BLOCK_NORMAL"))
|
||||
.andExpect(jsonPath("$.items[2].route_code").value("R13_EXTRA_BED_NORMAL"));
|
||||
|
||||
Long taskCount = jdbcTemplate.queryForObject("""
|
||||
@@ -675,6 +789,27 @@ class SuperAgentTaskResultP0FixtureRegressionTest {
|
||||
childGroupCodes.add(childGroupCodeTwo);
|
||||
}
|
||||
|
||||
private void convertParentSplitRootToP01(ObjectNode root) {
|
||||
ArrayNode events = (ArrayNode) root.path("message_events");
|
||||
for (JsonNode item : events) {
|
||||
ObjectNode event = (ObjectNode) item;
|
||||
if (!"linked_parent_release_after_child_split".equals(event.path("relationship_type").asText(null))) {
|
||||
continue;
|
||||
}
|
||||
ObjectNode caseKeys = (ObjectNode) event.path("case_keys");
|
||||
String parentGroupCode = caseKeys.path("group_code").asText(null);
|
||||
event.put("event_type", "Cancel Allotment");
|
||||
caseKeys.put("block_code", parentGroupCode);
|
||||
|
||||
ObjectNode extractedFields = (ObjectNode) event.path("extracted_fields");
|
||||
extractedFields.remove("cancel_object_type");
|
||||
extractedFields.put("cancel_scope", "entire_allotment_control_block");
|
||||
extractedFields.put("parent_release_or_cancel_candidate", true);
|
||||
extractedFields.put("release_reason", "parent_to_child_allocation_split");
|
||||
extractedFields.put("allocation_split_from_parent", true);
|
||||
}
|
||||
}
|
||||
|
||||
private void useManualReviewGroupCode(ObjectNode event, String groupCode) {
|
||||
useEventGroupCode(event, groupCode);
|
||||
((ObjectNode) event.path("manual_review").path("known_fields")).put("group_code", groupCode);
|
||||
|
||||
Reference in New Issue
Block a user