修复 V4 确认白名单和嵌套目录校验
This commit is contained in:
@@ -160,8 +160,7 @@ class ReservationV4CommandControllerTest {
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.basic_information_card.card_status").value("CONFIRMED"))
|
||||
.andExpect(jsonPath("$.basic_information_card.confirmed_by").value("v4-command-admin"))
|
||||
.andExpect(jsonPath("$.basic_information_card.confirmed_payload.group_code")
|
||||
.value("GRP-V4-COMMAND-001"))
|
||||
.andExpect(jsonPath("$.basic_information_card.confirmed_payload.group_code").doesNotExist())
|
||||
.andExpect(jsonPath("$.basic_information_card.confirmed_payload.basic_information.market_code")
|
||||
.value("LEISURE"))
|
||||
.andExpect(jsonPath("$.basic_information_card.confirmed_payload.basic_information.source_code")
|
||||
@@ -178,7 +177,8 @@ class ReservationV4CommandControllerTest {
|
||||
{
|
||||
"version": 0,
|
||||
"confirmed_payload": {
|
||||
"room_items": [{"room_type_code": "TWN", "room_count": 2}]
|
||||
"room_items": [{"room_type_code": "TWN", "room_count": 2}],
|
||||
"injected_debug_field": "SHOULD-NOT-PERSIST"
|
||||
}
|
||||
}
|
||||
"""))
|
||||
@@ -187,6 +187,7 @@ class ReservationV4CommandControllerTest {
|
||||
.andExpect(jsonPath("$.business_cards[0].card_status").value("CONFIRMED"))
|
||||
.andExpect(jsonPath("$.business_cards[0].confirmed_payload.room_items[0].room_type_code")
|
||||
.value("TWN"))
|
||||
.andExpect(jsonPath("$.business_cards[0].confirmed_payload.injected_debug_field").doesNotExist())
|
||||
.andExpect(jsonPath("$.business_cards[0].availability.confirmable").value(false))
|
||||
.andExpect(jsonPath("$.business_cards[0].availability.readonly_reason_code").value("CARD_LOCKED"));
|
||||
assertAuditCount("V4_CARD_CONFIRM", "v4-command-admin", seeded.orderTask().id().toString(), 2);
|
||||
@@ -219,6 +220,84 @@ class ReservationV4CommandControllerTest {
|
||||
.andExpect(jsonPath("$.details[0]").value("basic_information.account_code: Account Code 不在信息系统目录中。"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectBusinessCardConfirmWhenNestedRoomTypeCodeUnknown() throws Exception {
|
||||
SeededOrderTask seeded = seedOrderTaskWithBusinessCard(
|
||||
HOTEL_ID,
|
||||
"mail-v4-command-confirm-unknown-nested-room-001",
|
||||
Instant.parse("2026-07-19T01:13:00Z"),
|
||||
null,
|
||||
"""
|
||||
{"card_type":"ROOM_INFORMATION","event_type":"UPDATE_BOOKING","business_fields":{"order_ref":"ORDER-COMMAND","event_type":"UPDATE_BOOKING","after":{"room_items":[{"room_type_code":"UNKNOWN_ROOM","room_count":2}]}}}
|
||||
""");
|
||||
confirmBasicCard(seeded);
|
||||
|
||||
performAuthorized(mockMvc, adminToken(), post("/api/reservation/order-tasks/{orderTaskId}/cards/{cardId}/confirm",
|
||||
seeded.orderTask().id(),
|
||||
seeded.businessCard().id())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"version": 0
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.error_code").value("V4_FIELD_VALIDATION_FAILED"))
|
||||
.andExpect(jsonPath("$.details[0]").value("business_fields.after.room_items.0.room_type_code: 房型代码不在第一版目录中。"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConfirmWrappedBusinessFieldsAndDropInjectedFields() throws Exception {
|
||||
SeededOrderTask seeded = seedOrderTaskWithBusinessCard(
|
||||
HOTEL_ID,
|
||||
"mail-v4-command-confirm-wrapped-whitelist-001",
|
||||
Instant.parse("2026-07-19T01:13:10Z"),
|
||||
null,
|
||||
"""
|
||||
{"card_type":"ROOM_INFORMATION","event_type":"UPDATE_BOOKING","business_fields":{"order_ref":"ORDER-COMMAND","event_type":"UPDATE_BOOKING","after":{"room_items":[{"room_type_code":"TWN","room_count":2}]}}}
|
||||
""");
|
||||
confirmBasicCard(seeded);
|
||||
|
||||
performAuthorized(mockMvc, adminToken(), post("/api/reservation/order-tasks/{orderTaskId}/cards/{cardId}/confirm",
|
||||
seeded.orderTask().id(),
|
||||
seeded.businessCard().id())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"version": 0,
|
||||
"confirmed_payload": {
|
||||
"business_fields": {
|
||||
"order_ref": "MUTATED-ORDER",
|
||||
"event_type": "CANCEL_BOOKING",
|
||||
"after": {
|
||||
"room_items": [
|
||||
{
|
||||
"room_type_code": "DBL",
|
||||
"room_count": 1,
|
||||
"injected_debug_field": "SHOULD-NOT-PERSIST"
|
||||
}
|
||||
]
|
||||
},
|
||||
"injected_debug_field": "SHOULD-NOT-PERSIST"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.business_cards[0].confirmed_payload.business_fields.order_ref")
|
||||
.value("ORDER-COMMAND"))
|
||||
.andExpect(jsonPath("$.business_cards[0].confirmed_payload.business_fields.event_type")
|
||||
.value("UPDATE_BOOKING"))
|
||||
.andExpect(jsonPath("$.business_cards[0].confirmed_payload.business_fields.after.room_items[0].room_type_code")
|
||||
.value("DBL"))
|
||||
.andExpect(jsonPath("$.business_cards[0].confirmed_payload.business_fields.after.room_items[0].room_count")
|
||||
.value(1))
|
||||
.andExpect(jsonPath("$.business_cards[0].confirmed_payload.business_fields.after.room_items[0].injected_debug_field")
|
||||
.doesNotExist())
|
||||
.andExpect(jsonPath("$.business_cards[0].confirmed_payload.business_fields.injected_debug_field")
|
||||
.doesNotExist());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectRepeatedCardConfirm() throws Exception {
|
||||
SeededOrderTask seeded = seedOrderTask(
|
||||
@@ -957,6 +1036,17 @@ class ReservationV4CommandControllerTest {
|
||||
String externalMessageId,
|
||||
Instant receivedAt,
|
||||
Long orderId) {
|
||||
return seedOrderTaskWithBusinessCard(hotelId, externalMessageId, receivedAt, orderId, """
|
||||
{"card_type":"ROOM_INFORMATION","event_type":"NEW_BOOKING","room_items":[{"room_type_code":"TWN","room_count":2}]}
|
||||
""");
|
||||
}
|
||||
|
||||
private SeededOrderTask seedOrderTaskWithBusinessCard(
|
||||
String hotelId,
|
||||
String externalMessageId,
|
||||
Instant receivedAt,
|
||||
Long orderId,
|
||||
String businessDisplayPayloadJson) {
|
||||
SourceMessageCaptureResult source = captureSourceMessage(
|
||||
hotelId,
|
||||
externalMessageId,
|
||||
@@ -989,9 +1079,7 @@ class ReservationV4CommandControllerTest {
|
||||
""");
|
||||
ReservationV4TaskCardSnapshot businessCard = insertCard(orderTask, hotelId,
|
||||
ReservationV4CardType.ROOM_INFORMATION.name(), "NEW_BOOKING", 1, 30,
|
||||
ReservationV4CardStatus.PENDING_CONFIRM.name(), null, """
|
||||
{"card_type":"ROOM_INFORMATION","event_type":"NEW_BOOKING","room_items":[{"room_type_code":"TWN","room_count":2}]}
|
||||
""");
|
||||
ReservationV4CardStatus.PENDING_CONFIRM.name(), null, businessDisplayPayloadJson);
|
||||
return new SeededOrderTask(orderTask, sourceCard, basicCard, businessCard);
|
||||
}
|
||||
|
||||
|
||||
@@ -1256,6 +1256,29 @@ class SuperAgentTaskResultControllerTest {
|
||||
.contains("房型代码不在第一版目录中");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMarkV4UpdateBookingReviewRequiredWhenNestedRoomTypeCodeUnknown() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage("mail-v4-update-unknown-nested-room-001");
|
||||
String body = v4UpdateWithUnknownNestedRoomTypeBody("mail-v4-update-unknown-nested-room-001");
|
||||
|
||||
mockMvc.perform(signedPost(body, "nonce-v4-update-unknown-nested-room-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.accepted_count").value(1));
|
||||
|
||||
String roomValidationErrors = jdbcTemplate.queryForObject("""
|
||||
SELECT validation_errors_json
|
||||
FROM workflow_reservation_v4_task_card
|
||||
WHERE source_message_id = ?
|
||||
AND card_type = 'ROOM_INFORMATION'
|
||||
AND card_status = 'REVIEW_REQUIRED'
|
||||
AND review_status = 'PENDING'
|
||||
LIMIT 1
|
||||
""", String.class, source.inboxId());
|
||||
assertThat(roomValidationErrors)
|
||||
.contains("business_fields.after.room_items.0.room_type_code")
|
||||
.contains("房型代码不在第一版目录中");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateV4CancelTraceAndRoomingListTasksInEventOrder() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage("mail-v4-cancel-trace-rooming-001");
|
||||
@@ -4349,6 +4372,53 @@ class SuperAgentTaskResultControllerTest {
|
||||
""".formatted(externalSourceMessageId);
|
||||
}
|
||||
|
||||
private String v4UpdateWithUnknownNestedRoomTypeBody(String externalSourceMessageId) {
|
||||
return """
|
||||
{
|
||||
"route_code": null,
|
||||
"source_message": {
|
||||
"source_message_id": "%s",
|
||||
"conversation_id": "thread-v4-update-unknown-room-001",
|
||||
"subject": "Update booking room type",
|
||||
"sender": "agent@example.test",
|
||||
"sent_at": "2026-07-18T02:10:00Z",
|
||||
"body": "Please update room type.",
|
||||
"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-UNKNOWN-ROOM-001"
|
||||
},
|
||||
"after": {
|
||||
"room_items": [
|
||||
{
|
||||
"room_type_code": "UNKNOWN_ROOM",
|
||||
"room_count": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
"manual_review": null
|
||||
}
|
||||
]
|
||||
}
|
||||
""".formatted(externalSourceMessageId);
|
||||
}
|
||||
|
||||
private String[] createReadyTaskWithTwoOperaOperations(
|
||||
String externalMessageId,
|
||||
String nonce,
|
||||
|
||||
Reference in New Issue
Block a user