实现 M002 V4 入站解析基线
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -989,6 +989,138 @@ class SuperAgentTaskResultControllerTest {
|
||||
assertThat(transitionCount).isEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateBusinessTasksFromV4OrderContextsAndMessageEventsInOrder() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage("mail-v4-business-root-001");
|
||||
String body = v4BusinessRootBody("mail-v4-business-root-001");
|
||||
|
||||
MvcResult result = mockMvc.perform(signedPost(body, "nonce-v4-business-root-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.source_message_id").value("mail-v4-business-root-001"))
|
||||
.andExpect(jsonPath("$.accepted_count").value(2))
|
||||
.andExpect(jsonPath("$.items[0].source_event_index").value(1))
|
||||
.andExpect(jsonPath("$.items[0].array_index").value(1))
|
||||
.andExpect(jsonPath("$.items[0].route_code").value("R02_NEW_GROUP_BLOCK_NORMAL"))
|
||||
.andExpect(jsonPath("$.items[0].system_task_type").value("NEW_BOOKING"))
|
||||
.andExpect(jsonPath("$.items[0].task_card_type").value("NEW_BOOKING"))
|
||||
.andExpect(jsonPath("$.items[0].execution_order").value(1))
|
||||
.andExpect(jsonPath("$.items[1].source_event_index").value(2))
|
||||
.andExpect(jsonPath("$.items[1].array_index").value(2))
|
||||
.andExpect(jsonPath("$.items[1].route_code").value("R10_PAYMENT_EVIDENCE_NORMAL"))
|
||||
.andExpect(jsonPath("$.items[1].system_task_type").value("UPDATE_BOOKING"))
|
||||
.andExpect(jsonPath("$.items[1].task_card_type").value("PAYMENT_EVIDENCE"))
|
||||
.andExpect(jsonPath("$.items[1].execution_order").value(2))
|
||||
.andReturn();
|
||||
|
||||
String firstOrderId = com.jayway.jsonpath.JsonPath.read(
|
||||
result.getResponse().getContentAsString(),
|
||||
"$.items[0].order_id");
|
||||
String secondOrderId = com.jayway.jsonpath.JsonPath.read(
|
||||
result.getResponse().getContentAsString(),
|
||||
"$.items[1].order_id");
|
||||
assertThat(secondOrderId).isEqualTo(firstOrderId);
|
||||
|
||||
Long transitionCount = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM workflow_reservation_ai_transition
|
||||
WHERE source_message_id = ?
|
||||
AND catalog_code = 'M002V4'
|
||||
AND skill_id = 'booking-desk-event-v4'
|
||||
AND route_code IN ('R02_NEW_GROUP_BLOCK_NORMAL', 'R10_PAYMENT_EVIDENCE_NORMAL')
|
||||
AND ai_payload_json LIKE '%"v4_message_event"%'
|
||||
""", Long.class, source.inboxId());
|
||||
assertThat(transitionCount).isEqualTo(2L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateReadOnlyTaskForV4S10WithoutLegacyResultType() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage("mail-v4-s10-entry-result-001");
|
||||
String body = v4S10Body("mail-v4-s10-entry-result-001");
|
||||
|
||||
mockMvc.perform(signedPost(body, "nonce-v4-s10-entry-result-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.source_message_id").value("mail-v4-s10-entry-result-001"))
|
||||
.andExpect(jsonPath("$.accepted_count").value(1))
|
||||
.andExpect(jsonPath("$.items[0].route_code").value("S10"))
|
||||
.andExpect(jsonPath("$.items[0].system_process_category").value("SOURCE_MESSAGE_NOTIFICATION"))
|
||||
.andExpect(jsonPath("$.items[0].system_task_type").value("SOURCE_MESSAGE_ONLY"))
|
||||
.andExpect(jsonPath("$.items[0].task_status").value("COMPLETED"));
|
||||
|
||||
Long transitionCount = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM workflow_reservation_ai_transition
|
||||
WHERE source_message_id = ?
|
||||
AND result_type = 'source_message_review_notification'
|
||||
AND ai_task_type = 'S10'
|
||||
AND route_code = 'S10'
|
||||
""", Long.class, source.inboxId());
|
||||
assertThat(transitionCount).isEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectMalformedV4PackageWithClearAdapterContractError() throws Exception {
|
||||
String body = v4MalformedMissingOrderContextsBody("mail-v4-malformed-missing-order-contexts-001");
|
||||
|
||||
mockMvc.perform(signedPost(body, "nonce-v4-malformed-missing-order-contexts-001"))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.error_code").value("ADAPTER_CONTRACT_ERROR"))
|
||||
.andExpect(jsonPath("$.message").value("V4 根结构字段不完整。"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPersistAdapterContractErrorWhenV4PaymentReferencesUnknownAttachment() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage("mail-v4-payment-unknown-attachment-001");
|
||||
String body = v4PaymentBody("mail-v4-payment-unknown-attachment-001", "[\"att-missing\"]");
|
||||
|
||||
mockMvc.perform(signedPost(body, "nonce-v4-payment-unknown-attachment-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.accepted_count").value(1))
|
||||
.andExpect(jsonPath("$.items[0].adapter_error_code").value("PAYMENT_ATTACHMENT_ID_NOT_FOUND"))
|
||||
.andExpect(jsonPath("$.items[0].task_id").doesNotExist());
|
||||
|
||||
Long taskCount = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM workflow_reservation_task
|
||||
WHERE source_message_id = ?
|
||||
""", Long.class, source.inboxId());
|
||||
Long adapterErrorCount = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM workflow_reservation_ai_transition
|
||||
WHERE source_message_id = ?
|
||||
AND system_process_category = 'ADAPTER_CONTRACT_ERROR'
|
||||
AND adapter_error_code = 'PAYMENT_ATTACHMENT_ID_NOT_FOUND'
|
||||
""", Long.class, source.inboxId());
|
||||
assertThat(taskCount).isZero();
|
||||
assertThat(adapterErrorCount).isEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPersistAdapterContractErrorWhenV4UpdateContainsRateCode() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage("mail-v4-update-rate-code-001");
|
||||
String body = v4UpdateWithRateCodeBody("mail-v4-update-rate-code-001");
|
||||
|
||||
mockMvc.perform(signedPost(body, "nonce-v4-update-rate-code-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.accepted_count").value(1))
|
||||
.andExpect(jsonPath("$.items[0].adapter_error_code").value("UPDATE_RATE_CODE_NOT_ALLOWED"))
|
||||
.andExpect(jsonPath("$.items[0].task_id").doesNotExist());
|
||||
|
||||
Long taskCount = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM workflow_reservation_task
|
||||
WHERE source_message_id = ?
|
||||
""", Long.class, source.inboxId());
|
||||
Long adapterErrorCount = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM workflow_reservation_ai_transition
|
||||
WHERE source_message_id = ?
|
||||
AND system_process_category = 'ADAPTER_CONTRACT_ERROR'
|
||||
AND adapter_error_code = 'UPDATE_RATE_CODE_NOT_ALLOWED'
|
||||
""", Long.class, source.inboxId());
|
||||
assertThat(taskCount).isZero();
|
||||
assertThat(adapterErrorCount).isEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAcceptV2TaskResultWhenSourceMessageStoredAsOutlookChannelWithoutSourceChannel() throws Exception {
|
||||
String externalMessageId = "mail-v2-task-result-outlook-channel-001";
|
||||
@@ -3183,6 +3315,204 @@ class SuperAgentTaskResultControllerTest {
|
||||
""".formatted(externalSourceMessageId);
|
||||
}
|
||||
|
||||
private String v4BusinessRootBody(String externalSourceMessageId) {
|
||||
return """
|
||||
{
|
||||
"route_code": null,
|
||||
"source_message": {
|
||||
"source_message_id": "%s",
|
||||
"conversation_id": "thread-v4-001",
|
||||
"subject": "Group booking and payment",
|
||||
"sender": "agent@example.test",
|
||||
"sent_at": "2026-07-18T02:10:00Z",
|
||||
"body": "Please create group GRP-V4-001 and note payment attached.",
|
||||
"body_content_type": "text/plain",
|
||||
"attachments": [
|
||||
{
|
||||
"id": "att-pay-1",
|
||||
"name": "payment-slip.jpg",
|
||||
"content_type": "image/jpeg",
|
||||
"url": "https://oss.example.test/payment-slip.jpg",
|
||||
"size": 251524
|
||||
}
|
||||
]
|
||||
},
|
||||
"order_contexts": [
|
||||
{
|
||||
"order_ref": "order-1",
|
||||
"basic_information": {
|
||||
"account_code": "QBD_TRAVEL",
|
||||
"manual_review": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"message_events": [
|
||||
{
|
||||
"order_ref": "order-1",
|
||||
"event_type": "NEW_BOOKING",
|
||||
"target_order": {
|
||||
"booking_type": "GROUP",
|
||||
"locator_type": "GROUP_CODE",
|
||||
"locator_value": "GRP-V4-001"
|
||||
},
|
||||
"arrival_date": "2026-07-26",
|
||||
"departure_date": "2026-07-29",
|
||||
"rate_code": "BAR",
|
||||
"booking_scenario": "STANDARD",
|
||||
"room_items": [
|
||||
{
|
||||
"room_type_code": "TWN",
|
||||
"room_count": 2
|
||||
}
|
||||
],
|
||||
"manual_review": null
|
||||
},
|
||||
{
|
||||
"order_ref": "order-1",
|
||||
"event_type": "PAYMENT",
|
||||
"target_order": {
|
||||
"booking_type": "GROUP",
|
||||
"locator_type": "GROUP_CODE",
|
||||
"locator_value": "GRP-V4-001"
|
||||
},
|
||||
"attachment_ids": ["att-pay-1"],
|
||||
"manual_review": null
|
||||
}
|
||||
]
|
||||
}
|
||||
""".formatted(externalSourceMessageId);
|
||||
}
|
||||
|
||||
private String v4S10Body(String externalSourceMessageId) {
|
||||
return """
|
||||
{
|
||||
"route_code": "S10",
|
||||
"source_message": {
|
||||
"source_message_id": "%s",
|
||||
"conversation_id": "thread-v4-s10-001",
|
||||
"subject": "Thanks",
|
||||
"sender": "guest@example.test",
|
||||
"sent_at": "2026-07-18T02:10:00Z",
|
||||
"body": "Noted with thanks.",
|
||||
"body_content_type": "text/plain",
|
||||
"attachments": []
|
||||
},
|
||||
"order_contexts": [],
|
||||
"message_events": []
|
||||
}
|
||||
""".formatted(externalSourceMessageId);
|
||||
}
|
||||
|
||||
private String v4MalformedMissingOrderContextsBody(String externalSourceMessageId) {
|
||||
return """
|
||||
{
|
||||
"route_code": null,
|
||||
"source_message": {
|
||||
"source_message_id": "%s",
|
||||
"conversation_id": "thread-v4-malformed-001",
|
||||
"subject": "Malformed V4 package",
|
||||
"sender": "agent@example.test",
|
||||
"sent_at": "2026-07-18T02:10:00Z",
|
||||
"body": "Missing order contexts.",
|
||||
"body_content_type": "text/plain",
|
||||
"attachments": []
|
||||
},
|
||||
"message_events": []
|
||||
}
|
||||
""".formatted(externalSourceMessageId);
|
||||
}
|
||||
|
||||
private String v4PaymentBody(String externalSourceMessageId, String attachmentIdsJson) {
|
||||
return """
|
||||
{
|
||||
"route_code": null,
|
||||
"source_message": {
|
||||
"source_message_id": "%s",
|
||||
"conversation_id": "thread-v4-payment-001",
|
||||
"subject": "Payment evidence",
|
||||
"sender": "agent@example.test",
|
||||
"sent_at": "2026-07-18T02:10:00Z",
|
||||
"body": "Payment attached.",
|
||||
"body_content_type": "text/plain",
|
||||
"attachments": [
|
||||
{
|
||||
"id": "att-pay-1",
|
||||
"name": "payment-slip.jpg",
|
||||
"content_type": "image/jpeg",
|
||||
"url": "https://oss.example.test/payment-slip.jpg",
|
||||
"size": 251524
|
||||
}
|
||||
]
|
||||
},
|
||||
"order_contexts": [
|
||||
{
|
||||
"order_ref": "order-1",
|
||||
"basic_information": {
|
||||
"account_code": "QBD_TRAVEL",
|
||||
"manual_review": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"message_events": [
|
||||
{
|
||||
"order_ref": "order-1",
|
||||
"event_type": "PAYMENT",
|
||||
"target_order": {
|
||||
"booking_type": "GROUP",
|
||||
"locator_type": "GROUP_CODE",
|
||||
"locator_value": "GRP-V4-PAY-001"
|
||||
},
|
||||
"attachment_ids": %s,
|
||||
"manual_review": null
|
||||
}
|
||||
]
|
||||
}
|
||||
""".formatted(externalSourceMessageId, attachmentIdsJson);
|
||||
}
|
||||
|
||||
private String v4UpdateWithRateCodeBody(String externalSourceMessageId) {
|
||||
return """
|
||||
{
|
||||
"route_code": null,
|
||||
"source_message": {
|
||||
"source_message_id": "%s",
|
||||
"conversation_id": "thread-v4-update-rate-001",
|
||||
"subject": "Update booking",
|
||||
"sender": "agent@example.test",
|
||||
"sent_at": "2026-07-18T02:10:00Z",
|
||||
"body": "Please update stay and rate code.",
|
||||
"body_content_type": "text/plain",
|
||||
"attachments": []
|
||||
},
|
||||
"order_contexts": [
|
||||
{
|
||||
"order_ref": "order-1",
|
||||
"basic_information": {
|
||||
"account_code": "QBD_TRAVEL",
|
||||
"manual_review": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"message_events": [
|
||||
{
|
||||
"order_ref": "order-1",
|
||||
"event_type": "UPDATE_BOOKING",
|
||||
"target_order": {
|
||||
"booking_type": "GROUP",
|
||||
"locator_type": "GROUP_CODE",
|
||||
"locator_value": "GRP-V4-UPD-001"
|
||||
},
|
||||
"after": {
|
||||
"arrival_date": "2026-08-01",
|
||||
"rate_code": "BAR"
|
||||
},
|
||||
"manual_review": null
|
||||
}
|
||||
]
|
||||
}
|
||||
""".formatted(externalSourceMessageId);
|
||||
}
|
||||
|
||||
private String[] createReadyTaskWithTwoOperaOperations(
|
||||
String externalMessageId,
|
||||
String nonce,
|
||||
|
||||
Reference in New Issue
Block a user