修复V4复核字段指针运行时校验

This commit is contained in:
andy
2026-07-21 14:46:27 +07:00
parent ef6658aa34
commit 3527659dd0
5 changed files with 111 additions and 84 deletions

View File

@@ -1183,6 +1183,86 @@ class ReservationV4CommandControllerTest {
.value("RM2"));
}
@Test
void shouldResolveEditableRoomInformationPointerWhenLeafMissingInPayload() throws Exception {
SeededOrderTask seeded = seedReviewOrderTaskWithBusinessCard(
HOTEL_ID,
"mail-v4-command-review-pointer-missing-leaf-001",
Instant.parse("2026-07-19T01:22:19Z"),
990000000000070117L,
ReservationV4TargetResolutionStatus.RESOLVED.name(),
ReservationV4CardStatus.PENDING_CONFIRM.name(),
ReservationV4CardStatus.REVIEW_REQUIRED.name(),
ReservationV4CardType.ROOM_INFORMATION.name(),
"UPDATE_BOOKING",
"""
{
"card_type":"ROOM_INFORMATION",
"event_type":"UPDATE_BOOKING",
"target_order":{"booking_type":"GROUP","locator_type":"GROUP_CODE","locator_value":"GRP-V4-REVIEW-MISSING-LEAF-001"},
"business_fields":{
"event_type":"UPDATE_BOOKING",
"after":{
"arrival_date":"2026-08-01",
"departure_date":"2026-08-03",
"room_items":[{"room_count":1}]
}
}
}
""",
"""
[
{
"field_path": "room_information.final_values.room_items.0.room_type_code",
"field_pointer": "/room_information/final_values/room_items/0/room_type_code",
"message": "房型代码不能为空。",
"detail": "room_information.final_values.room_items.0.room_type_code: 房型代码不能为空。"
}
]
""");
confirmBasicCard(seeded);
MvcResult detailResult = performAuthorized(mockMvc, adminToken(),
get("/api/reservation/order-tasks/{orderTaskId}", seeded.orderTask().id())
.param("hotel_id", HOTEL_ID))
.andExpect(status().isOk())
.andReturn();
JsonNode detail = objectMapper.readTree(detailResult.getResponse().getContentAsString());
JsonNode editableField = null;
for (JsonNode field : detail.path("business_cards").get(0).path("fields")) {
if ("/room_information/final_values/room_items/0/room_type_code"
.equals(field.path("field_pointer").asText())) {
editableField = field;
break;
}
}
org.assertj.core.api.Assertions.assertThat(editableField)
.as("任务详情必须返回缺失叶子字段的可复核 pointer")
.isNotNull();
org.assertj.core.api.Assertions.assertThat(editableField.path("editable").asBoolean(false)).isTrue();
org.assertj.core.api.Assertions.assertThat(editableField.path("write_target").asText())
.isEqualTo("review_resolution.field_overrides");
ObjectNode request = objectMapper.createObjectNode();
request.put("version", 0);
request.put("reason", "按详情页返回的缺失叶子字段补房型");
ArrayNode overrides = request.putArray("field_overrides");
ObjectNode override = overrides.addObject();
override.put("field_pointer", editableField.path("field_pointer").asText());
override.put("value", "TWN");
performAuthorized(mockMvc, adminToken(), post(
"/api/reservation/order-tasks/{orderTaskId}/cards/{cardId}/review-resolution",
seeded.orderTask().id(),
seeded.businessCard().id())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.business_cards[0].card_status").value("CONFIRMED"))
.andExpect(jsonPath("$.business_cards[0].confirmed_payload.room_information.final_values.room_items[0].room_type_code")
.value("TWN"));
}
@Test
void shouldResolveRoomInformationReviewWhenDisplayPayloadAlreadyUsesStableModel() throws Exception {
SeededOrderTask seeded = seedReviewOrderTaskWithBusinessCard(
@@ -1303,14 +1383,17 @@ class ReservationV4CommandControllerTest {
.andExpect(jsonPath("$.error_code").value("V4_REVIEW_POINTER_READONLY"));
org.assertj.core.api.Assertions.assertThat(output.getOut())
.contains("review_pointer_policy=m002_v4_review_pointer_runtime_trace_v1")
.contains("review_pointer_policy=m002_v4_review_pointer_runtime_fix_v1")
.contains("order_task_id=" + seeded.orderTask().id())
.contains("card_id=" + seeded.businessCard().id())
.contains("incoming_pointer=/room_information/final_values/nights")
.contains("query_side_editable_pointers=")
.contains("/room_information/final_values/room_items/0/room_type_code")
.contains("command_side_allowed_pointers=")
.contains("validation_error_pointers=")
.contains("/room_information/final_values/nights")
.contains("reject_reason=DERIVED_OR_SYSTEM_FIELD_READONLY")
.contains("display_payload_has_room_information_final_values=true")
.doesNotContain("target_order")
.doesNotContain("business_fields")
.doesNotContain("raw_evidence")
@@ -1530,7 +1613,7 @@ class ReservationV4CommandControllerTest {
.andExpect(jsonPath("$.error_code").value("V4_REVIEW_POINTER_INVALID"));
org.assertj.core.api.Assertions.assertThat(output.getOut())
.contains("review_pointer_policy=m002_v4_review_pointer_runtime_trace_v1")
.contains("review_pointer_policy=m002_v4_review_pointer_runtime_fix_v1")
.contains("order_task_id=" + seeded.orderTask().id())
.contains("card_id=" + seeded.businessCard().id())
.contains("incoming_pointer=business_fields/room_items/0/pms_room_type_code")
@@ -1570,7 +1653,7 @@ class ReservationV4CommandControllerTest {
.andExpect(jsonPath("$.error_code").value("V4_REVIEW_POINTER_INVALID"));
org.assertj.core.api.Assertions.assertThat(output.getOut())
.contains("review_pointer_policy=m002_v4_review_pointer_runtime_trace_v1")
.contains("review_pointer_policy=m002_v4_review_pointer_runtime_fix_v1")
.contains("incoming_pointer=bad/[EMAIL]/[TOKEN]/[URL]")
.contains("reject_reason=V4_REVIEW_POINTER_INVALID")
.doesNotContain("user@example.test")