修复V4复核指针与任务详情脱敏
This commit is contained in:
@@ -1144,6 +1144,9 @@ public class ReservationV4CommandServiceImpl implements ReservationV4CommandServ
|
||||
ReservationV4OrderTaskSnapshot orderTask,
|
||||
ReservationV4TaskCardSnapshot card,
|
||||
ObjectNode displayPayload) {
|
||||
if (displayPayload.path("room_information").path("final_values").isObject()) {
|
||||
return stableRoomInformationDisplayModel(orderTask, card, displayPayload);
|
||||
}
|
||||
String eventType = firstText(card.eventType(), textAt(displayPayload, "event_type"));
|
||||
JsonNode targetOrder = displayPayload.path("target_order");
|
||||
String bookingType = firstText(textAt(targetOrder, "booking_type"), orderTask.targetBookingType());
|
||||
@@ -1164,6 +1167,32 @@ public class ReservationV4CommandServiceImpl implements ReservationV4CommandServ
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容已经持久化为稳定 room_information 的展示快照,复核时直接以该稳定模型作为白名单基准。
|
||||
*/
|
||||
private ObjectNode stableRoomInformationDisplayModel(
|
||||
ReservationV4OrderTaskSnapshot orderTask,
|
||||
ReservationV4TaskCardSnapshot card,
|
||||
ObjectNode displayPayload) {
|
||||
JsonNode roomInformation = displayPayload.path("room_information");
|
||||
String eventType = firstText(card.eventType(), firstText(textAt(roomInformation, "event_type"), textAt(displayPayload, "event_type")));
|
||||
String bookingType = firstText(textAt(roomInformation, "booking_type"), orderTask.targetBookingType());
|
||||
ObjectNode currentValues = roomInformationDisplayValues(bookingType, roomInformation.path("current_values"), true);
|
||||
ObjectNode proposedValues = roomInformationDisplayValues(bookingType, roomInformation.path("proposed_values"), true);
|
||||
ObjectNode finalValues = roomInformationDisplayValues(bookingType, roomInformation.path("final_values"), true);
|
||||
ensureRoomInformationEditablePlaceholders(card, bookingType, finalValues);
|
||||
normalizeRoomInformationDerivedFields(bookingType, finalValues);
|
||||
|
||||
ObjectNode model = objectMapper.createObjectNode();
|
||||
model.put("event_type", eventType);
|
||||
model.put("booking_type", bookingType);
|
||||
model.set("current_values", currentValues);
|
||||
model.set("proposed_values", proposedValues);
|
||||
model.set("final_values", finalValues);
|
||||
model.set("change_summary", changeSummary(currentValues, finalValues));
|
||||
return model;
|
||||
}
|
||||
|
||||
private ObjectNode currentRoomInformationProjection(ReservationV4OrderTaskSnapshot orderTask) {
|
||||
ObjectNode current = objectMapper.createObjectNode();
|
||||
if (orderTask.orderId() == null) {
|
||||
|
||||
@@ -111,6 +111,13 @@ public class ReservationV4QueryServiceImpl implements ReservationV4QueryService
|
||||
"order_ref",
|
||||
"route_code",
|
||||
"target_order");
|
||||
private static final Set<String> V4_BUSINESS_PAYLOAD_SENSITIVE_FIELDS = Set.of(
|
||||
"attachment_url",
|
||||
"html_body",
|
||||
"html_body_sanitized",
|
||||
"pms_raw_response",
|
||||
"raw_evidence",
|
||||
"target_order");
|
||||
|
||||
private final ReservationV4WorkflowRepository workflowRepository;
|
||||
private final ReservationAiWorkflowRepository aiWorkflowRepository;
|
||||
@@ -434,9 +441,13 @@ public class ReservationV4QueryServiceImpl implements ReservationV4QueryService
|
||||
if (ReservationV4CardType.BASIC_INFORMATION.name().equals(card.cardType())) {
|
||||
return safeBasicInformationPayload(displayPayload);
|
||||
}
|
||||
if (!isRoomInformationEventCard(card) || !displayPayload.isObject()) {
|
||||
if (ReservationV4CardType.SOURCE_MESSAGE_DISPLAY.name().equals(card.cardType())
|
||||
|| ReservationV4CardType.SOURCE_MESSAGE_NOTIFICATION.name().equals(card.cardType())) {
|
||||
return displayPayload;
|
||||
}
|
||||
if (!isRoomInformationEventCard(card) || !displayPayload.isObject()) {
|
||||
return safeBusinessPayload(displayPayload);
|
||||
}
|
||||
ObjectNode source = (ObjectNode) displayPayload;
|
||||
ObjectNode safePayload = objectMapper.createObjectNode();
|
||||
safePayload.put("card_type", firstText(textAt(source, "card_type"), card.cardType()));
|
||||
@@ -456,7 +467,7 @@ public class ReservationV4QueryServiceImpl implements ReservationV4QueryService
|
||||
if (ReservationV4CardType.BASIC_INFORMATION.name().equals(card.cardType())) {
|
||||
return safeBasicInformationPayload(confirmedPayload);
|
||||
}
|
||||
return confirmedPayload;
|
||||
return safeBusinessPayload(confirmedPayload);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -494,6 +505,83 @@ public class ReservationV4QueryServiceImpl implements ReservationV4QueryService
|
||||
target.set(fieldName, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清洗普通业务卡展示 / 确认 payload,避免 Agent 定位三元组、邮件正文或外部原始证据混入任务详情。
|
||||
*/
|
||||
private JsonNode safeBusinessPayload(JsonNode payload) {
|
||||
if (payload == null || payload.isMissingNode() || payload.isNull()) {
|
||||
return NullNode.getInstance();
|
||||
}
|
||||
JsonNode copied = payload.deepCopy();
|
||||
removeSensitiveBusinessPayloadFields(copied);
|
||||
return copied;
|
||||
}
|
||||
|
||||
private void removeSensitiveBusinessPayloadFields(JsonNode node) {
|
||||
if (node == null || node.isMissingNode() || node.isNull()) {
|
||||
return;
|
||||
}
|
||||
if (node.isObject()) {
|
||||
ObjectNode objectNode = (ObjectNode) node;
|
||||
objectNode.remove(V4_BUSINESS_PAYLOAD_SENSITIVE_FIELDS);
|
||||
Iterator<Map.Entry<String, JsonNode>> iterator = objectNode.fields();
|
||||
List<String> fieldsToRemove = new ArrayList<>();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, JsonNode> entry = iterator.next();
|
||||
if (isSensitiveBusinessPayloadField(entry.getKey()) || isSensitiveBusinessPayloadValue(entry.getValue())) {
|
||||
fieldsToRemove.add(entry.getKey());
|
||||
continue;
|
||||
}
|
||||
removeSensitiveBusinessPayloadFields(entry.getValue());
|
||||
}
|
||||
if (!fieldsToRemove.isEmpty()) {
|
||||
objectNode.remove(fieldsToRemove);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (node.isArray()) {
|
||||
ArrayNode arrayNode = (ArrayNode) node;
|
||||
for (int index = 0; index < arrayNode.size(); index++) {
|
||||
JsonNode item = arrayNode.get(index);
|
||||
if (isSensitiveBusinessPayloadValue(item)) {
|
||||
arrayNode.set(index, NullNode.getInstance());
|
||||
continue;
|
||||
}
|
||||
removeSensitiveBusinessPayloadFields(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSensitiveBusinessPayloadField(String fieldName) {
|
||||
String normalized = fieldName == null ? "" : fieldName.toLowerCase(Locale.ROOT);
|
||||
String compact = normalized.replace("_", "").replace("-", "");
|
||||
return V4_BUSINESS_PAYLOAD_SENSITIVE_FIELDS.contains(normalized)
|
||||
|| "url".equals(normalized)
|
||||
|| normalized.endsWith("_url")
|
||||
|| normalized.contains("private_url")
|
||||
|| normalized.contains("download_url")
|
||||
|| normalized.contains("signed_url")
|
||||
|| normalized.contains("attachment_url")
|
||||
|| normalized.contains("external_url")
|
||||
|| compact.endsWith("url")
|
||||
|| compact.contains("externalurl")
|
||||
|| compact.contains("signedurl")
|
||||
|| compact.contains("privateurl")
|
||||
|| compact.contains("downloadurl");
|
||||
}
|
||||
|
||||
private boolean isSensitiveBusinessPayloadValue(JsonNode value) {
|
||||
if (value == null || !value.isTextual()) {
|
||||
return false;
|
||||
}
|
||||
String text = value.asText("");
|
||||
String normalized = text.toLowerCase(Locale.ROOT);
|
||||
return normalized.startsWith("http://")
|
||||
|| normalized.startsWith("https://")
|
||||
|| normalized.startsWith("oss://")
|
||||
|| normalized.startsWith("file://");
|
||||
}
|
||||
|
||||
private ReservationV4TaskCardResult toSourceNotificationCard(
|
||||
ReservationV4SourceNotificationSnapshot notification,
|
||||
ReservationV4ActionAvailabilityResult availability) {
|
||||
@@ -1062,6 +1150,34 @@ public class ReservationV4QueryServiceImpl implements ReservationV4QueryService
|
||||
ReservationV4OrderTaskSnapshot orderTask,
|
||||
ReservationV4TaskCardSnapshot card,
|
||||
ObjectNode displayPayload) {
|
||||
ObjectNode model;
|
||||
if (displayPayload.path("room_information").path("final_values").isObject()) {
|
||||
model = stableRoomInformationDisplayModel(orderTask, card, displayPayload);
|
||||
} else {
|
||||
model = derivedRoomInformationDisplayModel(orderTask, card, displayPayload);
|
||||
}
|
||||
ObjectNode confirmedFinalValues = confirmedStableRoomInformationFinalValues(card);
|
||||
if (confirmedFinalValues != null) {
|
||||
String bookingType = textAt(model, "booking_type");
|
||||
normalizeRoomInformationDerivedFields(bookingType, confirmedFinalValues);
|
||||
ObjectNode finalValues = roomInformationDisplayValues(bookingType, confirmedFinalValues, true);
|
||||
ObjectNode currentValues = model.path("current_values").isObject()
|
||||
? (ObjectNode) model.path("current_values")
|
||||
: objectMapper.createObjectNode();
|
||||
model.set("final_values", finalValues);
|
||||
model.set("change_summary", changeSummary(currentValues, finalValues));
|
||||
}
|
||||
model.set("group_booking_status_options", groupBookingStatusOptions());
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Agent 原始 Room Information payload 生成稳定展示模型。
|
||||
*/
|
||||
private ObjectNode derivedRoomInformationDisplayModel(
|
||||
ReservationV4OrderTaskSnapshot orderTask,
|
||||
ReservationV4TaskCardSnapshot card,
|
||||
ObjectNode displayPayload) {
|
||||
String eventType = firstText(card.eventType(), textAt(displayPayload, "event_type"));
|
||||
JsonNode targetOrder = displayPayload.path("target_order");
|
||||
String bookingType = firstText(textAt(targetOrder, "booking_type"), orderTask.targetBookingType());
|
||||
@@ -1070,11 +1186,6 @@ public class ReservationV4QueryServiceImpl implements ReservationV4QueryService
|
||||
ObjectNode currentValues = currentRoomInformationProjection(orderTask, card);
|
||||
ObjectNode proposedValues = proposedRoomInformationValues(eventType, bookingType, locatorType, locatorValue, displayPayload);
|
||||
ObjectNode finalValues = finalRoomInformationValues(eventType, bookingType, locatorType, locatorValue, currentValues, proposedValues);
|
||||
ObjectNode confirmedFinalValues = confirmedStableRoomInformationFinalValues(card);
|
||||
if (confirmedFinalValues != null) {
|
||||
normalizeRoomInformationDerivedFields(bookingType, confirmedFinalValues);
|
||||
finalValues = roomInformationDisplayValues(bookingType, confirmedFinalValues, true);
|
||||
}
|
||||
|
||||
ObjectNode model = objectMapper.createObjectNode();
|
||||
model.put("event_type", eventType);
|
||||
@@ -1083,7 +1194,31 @@ public class ReservationV4QueryServiceImpl implements ReservationV4QueryService
|
||||
model.set("proposed_values", EVENT_CANCEL_BOOKING.equals(eventType) ? objectMapper.createObjectNode() : proposedValues);
|
||||
model.set("final_values", finalValues);
|
||||
model.set("change_summary", changeSummary(EVENT_NEW_BOOKING.equals(eventType) ? objectMapper.createObjectNode() : currentValues, finalValues));
|
||||
model.set("group_booking_status_options", groupBookingStatusOptions());
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容已经持久化为稳定 room_information 结构的任务卡,避免刷新或复核时丢失前端白名单字段。
|
||||
*/
|
||||
private ObjectNode stableRoomInformationDisplayModel(
|
||||
ReservationV4OrderTaskSnapshot orderTask,
|
||||
ReservationV4TaskCardSnapshot card,
|
||||
ObjectNode displayPayload) {
|
||||
JsonNode roomInformation = displayPayload.path("room_information");
|
||||
String eventType = firstText(card.eventType(), firstText(textAt(roomInformation, "event_type"), textAt(displayPayload, "event_type")));
|
||||
String bookingType = firstText(textAt(roomInformation, "booking_type"), orderTask.targetBookingType());
|
||||
ObjectNode currentValues = roomInformationDisplayValues(bookingType, roomInformation.path("current_values"), true);
|
||||
ObjectNode proposedValues = roomInformationDisplayValues(bookingType, roomInformation.path("proposed_values"), true);
|
||||
ObjectNode finalValues = roomInformationDisplayValues(bookingType, roomInformation.path("final_values"), true);
|
||||
normalizeRoomInformationDerivedFields(bookingType, finalValues);
|
||||
|
||||
ObjectNode model = objectMapper.createObjectNode();
|
||||
model.put("event_type", eventType);
|
||||
model.put("booking_type", bookingType);
|
||||
model.set("current_values", currentValues);
|
||||
model.set("proposed_values", proposedValues);
|
||||
model.set("final_values", finalValues);
|
||||
model.set("change_summary", changeSummary(currentValues, finalValues));
|
||||
return model;
|
||||
}
|
||||
|
||||
|
||||
@@ -1019,6 +1019,146 @@ class ReservationV4CommandControllerTest {
|
||||
.value("/room_information/final_values/room_items/0/room_type_code"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveRoomInformationReviewWithEditableFieldPointerFromTaskDetail() throws Exception {
|
||||
SeededOrderTask seeded = seedReviewOrderTaskWithBusinessCard(
|
||||
HOTEL_ID,
|
||||
"mail-v4-command-review-room-info-field-contract-001",
|
||||
Instant.parse("2026-07-19T01:22:16Z"),
|
||||
990000000000070114L,
|
||||
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-RI-FIELD-001"},
|
||||
"business_fields":{
|
||||
"event_type":"UPDATE_BOOKING",
|
||||
"after":{
|
||||
"arrival_date":"2026-08-01",
|
||||
"departure_date":"2026-08-03",
|
||||
"room_items":[{"room_type_code":"UNKNOWN_TYPE","room_count":2}]
|
||||
}
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
[
|
||||
{
|
||||
"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);
|
||||
|
||||
performAuthorized(mockMvc, adminToken(), get("/api/reservation/order-tasks/{orderTaskId}", seeded.orderTask().id())
|
||||
.param("hotel_id", HOTEL_ID))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.business_cards[0].fields[?(@.field_pointer=='/room_information/final_values/room_items/0/room_type_code')].editable")
|
||||
.value(contains(true)))
|
||||
.andExpect(jsonPath("$.business_cards[0].fields[?(@.field_pointer=='/room_information/final_values/room_items/0/room_type_code')].write_target")
|
||||
.value(contains("review_resolution.field_overrides")));
|
||||
|
||||
performAuthorized(mockMvc, adminToken(), post(
|
||||
"/api/reservation/order-tasks/{orderTaskId}/cards/{cardId}/review-resolution",
|
||||
seeded.orderTask().id(),
|
||||
seeded.businessCard().id())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"version": 0,
|
||||
"reason": "按详情页白名单修正房型",
|
||||
"field_overrides": [
|
||||
{
|
||||
"field_pointer": "/room_information/final_values/room_items/0/room_type_code",
|
||||
"value": "RM2"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""))
|
||||
.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("RM2"))
|
||||
.andExpect(jsonPath("$.business_cards[0].review_resolution.field_overrides[0].field_pointer")
|
||||
.value("/room_information/final_values/room_items/0/room_type_code"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveRoomInformationReviewWhenDisplayPayloadAlreadyUsesStableModel() throws Exception {
|
||||
SeededOrderTask seeded = seedReviewOrderTaskWithBusinessCard(
|
||||
HOTEL_ID,
|
||||
"mail-v4-command-review-room-info-stable-display-001",
|
||||
Instant.parse("2026-07-19T01:22:26Z"),
|
||||
990000000000070124L,
|
||||
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",
|
||||
"room_information":{
|
||||
"event_type":"UPDATE_BOOKING",
|
||||
"booking_type":"GROUP",
|
||||
"current_values":{},
|
||||
"proposed_values":{"room_items":[{"room_type_code":"UNKNOWN_TYPE","room_count":2}]},
|
||||
"final_values":{
|
||||
"arrival_date":"2026-08-01",
|
||||
"departure_date":"2026-08-03",
|
||||
"room_items":[{"room_type_code":"UNKNOWN_TYPE","room_count":2}],
|
||||
"breakfast_included":true,
|
||||
"group_booking_status":"TEN",
|
||||
"group_booking_status_label":"TEN-Tentative"
|
||||
}
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
[
|
||||
{
|
||||
"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);
|
||||
|
||||
performAuthorized(mockMvc, adminToken(), post(
|
||||
"/api/reservation/order-tasks/{orderTaskId}/cards/{cardId}/review-resolution",
|
||||
seeded.orderTask().id(),
|
||||
seeded.businessCard().id())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"version": 0,
|
||||
"reason": "修正已稳定展示模型中的房型",
|
||||
"field_overrides": [
|
||||
{
|
||||
"field_pointer": "/room_information/final_values/room_items/0/room_type_code",
|
||||
"value": "RM2"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.business_cards[0].confirmed_payload.room_information.final_values.room_items[0].room_type_code")
|
||||
.value("RM2"))
|
||||
.andExpect(jsonPath("$.business_cards[0].confirmed_payload.room_information.final_values.nights")
|
||||
.value(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectRoomInformationReviewWhenPointerTargetsDerivedReadonlyField() throws Exception {
|
||||
SeededOrderTask seeded = seedReviewOrderTaskWithBusinessCard(
|
||||
|
||||
@@ -526,6 +526,73 @@ class ReservationV4QueryControllerTest {
|
||||
.andExpect(content().string(not(containsString("SHOULD-NOT-LEAK"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotExposeTargetOrderInRoomingListDisplayOrConfirmedPayload() throws Exception {
|
||||
ReservationV4OrderTaskSnapshot orderTask = seedOrderTaskWithBusinessCardType(
|
||||
"mail-v4-query-rooming-target-order-safe-001",
|
||||
Instant.parse("2026-07-18T03:02:45Z"),
|
||||
"GROUP",
|
||||
"GROUP_CODE",
|
||||
"GRP-V4-ROOMING-SAFE-001",
|
||||
ReservationV4CardType.ROOMING_LIST.name(),
|
||||
"ROOMING_LIST",
|
||||
ReservationV4CardStatus.CONFIRMED.name(),
|
||||
ReservationV4CardStatus.PENDING_CONFIRM.name(),
|
||||
"""
|
||||
{
|
||||
"card_type":"ROOMING_LIST",
|
||||
"event_type":"ROOMING_LIST",
|
||||
"target_order":{"locator_value":"SHOULD-NOT-LEAK-ROOMING-DISPLAY"},
|
||||
"attachments":[
|
||||
{
|
||||
"name":"rooming-list.xlsx",
|
||||
"url":"https://oss.example.test/rooming/display.xlsx",
|
||||
"externalUrl":"https://oss.example.test/rooming/display-external.xlsx"
|
||||
}
|
||||
],
|
||||
"business_fields":{
|
||||
"attachment_ids":["att-rooming-safe-001"],
|
||||
"rooming_list_action":"CONFIRM_RECEIVED",
|
||||
"evidence_url":"https://oss.example.test/rooming/evidence.pdf",
|
||||
"file_references":["https://oss.example.test/rooming/reference.pdf"]
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
{
|
||||
"card_type":"ROOMING_LIST",
|
||||
"event_type":"ROOMING_LIST",
|
||||
"target_order":{"locator_value":"SHOULD-NOT-LEAK-ROOMING-CONFIRMED"},
|
||||
"attachments":[
|
||||
{
|
||||
"name":"rooming-list-confirmed.xlsx",
|
||||
"download_url":"https://oss.example.test/rooming/confirmed.xlsx"
|
||||
}
|
||||
],
|
||||
"business_fields":{
|
||||
"attachment_ids":["att-rooming-safe-001"],
|
||||
"rooming_list_action":"CONFIRM_RECEIVED",
|
||||
"signedUrl":"https://oss.example.test/rooming/signed.pdf"
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
performAuthorized(mockMvc, adminToken(), get("/api/reservation/order-tasks/{orderTaskId}", orderTask.id())
|
||||
.param("hotel_id", HOTEL_ID))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.business_cards[0].card_type").value("ROOMING_LIST"))
|
||||
.andExpect(jsonPath("$.business_cards[0].display_payload.target_order").doesNotExist())
|
||||
.andExpect(jsonPath("$.business_cards[0].confirmed_payload.target_order").doesNotExist())
|
||||
.andExpect(jsonPath("$.business_cards[0].display_payload.attachments[0].name").value("rooming-list.xlsx"))
|
||||
.andExpect(jsonPath("$.business_cards[0].display_payload.attachments[0].url").doesNotExist())
|
||||
.andExpect(jsonPath("$.business_cards[0].display_payload.attachments[0].externalUrl").doesNotExist())
|
||||
.andExpect(jsonPath("$.business_cards[0].display_payload.business_fields.evidence_url").doesNotExist())
|
||||
.andExpect(jsonPath("$.business_cards[0].confirmed_payload.attachments[0].download_url").doesNotExist())
|
||||
.andExpect(jsonPath("$.business_cards[0].confirmed_payload.business_fields.signedUrl").doesNotExist())
|
||||
.andExpect(content().string(not(containsString("SHOULD-NOT-LEAK-ROOMING"))))
|
||||
.andExpect(content().string(not(containsString("oss.example.test"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnReadonlyRoomInformationDisplayModelForCancelBooking() throws Exception {
|
||||
Long orderId = 990000000000777002L;
|
||||
@@ -1107,6 +1174,67 @@ class ReservationV4QueryControllerTest {
|
||||
return orderTask;
|
||||
}
|
||||
|
||||
private ReservationV4OrderTaskSnapshot seedOrderTaskWithBusinessCardType(
|
||||
String externalMessageId,
|
||||
Instant receivedAt,
|
||||
String targetBookingType,
|
||||
String targetLocatorType,
|
||||
String targetLocatorValue,
|
||||
String businessCardType,
|
||||
String businessEventType,
|
||||
String basicCardStatus,
|
||||
String businessCardStatus,
|
||||
String businessDisplayPayloadJson,
|
||||
String confirmedPayloadJson) {
|
||||
SourceMessageCaptureResult source = captureSourceMessage(externalMessageId, "V4 Query Business", receivedAt, HOTEL_ID);
|
||||
LocalDateTime now = LocalDateTime.ofInstant(receivedAt.plusSeconds(10), ZoneOffset.UTC);
|
||||
ReservationV4OrderTaskSnapshot orderTask = workflowRepository.findOrCreateOrderTask(new ReservationV4OrderTaskDraft(
|
||||
HOTEL_ID,
|
||||
source.inboxId(),
|
||||
990000000000003001L + Math.abs(externalMessageId.hashCode()),
|
||||
"order-generic-card-" + externalMessageId,
|
||||
1,
|
||||
null,
|
||||
targetBookingType,
|
||||
targetLocatorType,
|
||||
targetLocatorValue,
|
||||
ReservationV4TargetResolutionStatus.RESOLVED.name(),
|
||||
ReservationV4OrderTaskStatus.OPEN.name(),
|
||||
LocalDateTime.ofInstant(receivedAt, ZoneOffset.UTC),
|
||||
now));
|
||||
insertCard(orderTask, ReservationV4CardType.SOURCE_MESSAGE_DISPLAY.name(), null, 0, 10,
|
||||
ReservationV4CardStatus.READONLY.name(), null, """
|
||||
{"card_type":"SOURCE_MESSAGE_DISPLAY","source_message":{"subject":"V4 Query Business"}}
|
||||
""");
|
||||
insertCard(orderTask, ReservationV4CardType.BASIC_INFORMATION.name(), null, 0, 20,
|
||||
basicCardStatus, reviewStatusFor(basicCardStatus), """
|
||||
{
|
||||
"card_type":"BASIC_INFORMATION",
|
||||
"order_ref":"order-1",
|
||||
"basic_information":{"account_code":"QBD_TRAVEL","market_code":"LEISURE","source_code":"TRAVEL_AGENT"}
|
||||
}
|
||||
""");
|
||||
ReservationV4TaskCardSnapshot businessCard = insertCard(
|
||||
orderTask,
|
||||
businessCardType,
|
||||
businessEventType,
|
||||
1,
|
||||
30,
|
||||
businessCardStatus,
|
||||
reviewStatusFor(businessCardStatus),
|
||||
businessDisplayPayloadJson);
|
||||
if (confirmedPayloadJson != null) {
|
||||
workflowRepository.confirmTaskCardWithVersion(
|
||||
HOTEL_ID,
|
||||
businessCard.id(),
|
||||
businessCard.version(),
|
||||
confirmedPayloadJson,
|
||||
"v4-query-admin",
|
||||
now);
|
||||
}
|
||||
return orderTask;
|
||||
}
|
||||
|
||||
private void seedConfirmedRoomInformationProjection(
|
||||
Long orderId,
|
||||
String externalMessageId,
|
||||
|
||||
Reference in New Issue
Block a user