实现 M002 订单任务入站与队列规则
This commit is contained in:
@@ -0,0 +1,828 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.control;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import cn.nianxx.thhotel.ThHotelApplication;
|
||||
import cn.nianxx.thhotel.platform.message.common.request.CaptureSourceMessageCommand;
|
||||
import cn.nianxx.thhotel.platform.message.common.result.SourceMessageCaptureResult;
|
||||
import cn.nianxx.thhotel.platform.message.service.SourceMessageCaptureService;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.time.Instant;
|
||||
import java.util.HexFormat;
|
||||
import java.util.List;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
@SpringBootTest(
|
||||
classes = ThHotelApplication.class,
|
||||
properties = {
|
||||
"superagent.task-result.hmac-secret=test-superagent-secret",
|
||||
"superagent.task-result.clock-skew-seconds=300",
|
||||
"superagent.task-result.nonce-ttl-seconds=600",
|
||||
"superagent.task-result.max-body-bytes=12000"
|
||||
})
|
||||
@AutoConfigureMockMvc
|
||||
@ActiveProfiles("test")
|
||||
class SuperAgentTaskResultControllerTest {
|
||||
|
||||
private static final String ENDPOINT = "/api/integrations/superagent/task-results";
|
||||
private static final String CLIENT_ID = "superagent-test-client";
|
||||
private static final String SECRET = "test-superagent-secret";
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private SourceMessageCaptureService captureService;
|
||||
|
||||
@Autowired
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Test
|
||||
void shouldRejectRequestWithoutHmacHeaders() throws Exception {
|
||||
String body = minimalBody("1", "New Booking", "normal_task", "new_fit_reservation", """
|
||||
"case_keys": {},
|
||||
"extracted_fields": {}
|
||||
""");
|
||||
|
||||
mockMvc.perform(post(ENDPOINT)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(body))
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andExpect(jsonPath("$.error_code").value("AUTH_HEADER_MISSING"))
|
||||
.andExpect(content().string(not(containsString(SECRET))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectRequestWithInvalidSignature() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage("mail-hmac-invalid-001");
|
||||
String body = minimalBody(source.inboxId().toString(), "New Booking", "normal_task", "new_fit_reservation", """
|
||||
"case_keys": {},
|
||||
"extracted_fields": {}
|
||||
""");
|
||||
String timestamp = Instant.now().toString();
|
||||
|
||||
mockMvc.perform(post(ENDPOINT)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(body)
|
||||
.header("X-TH-Hotel-SuperAgent-Client-Id", CLIENT_ID)
|
||||
.header("X-TH-Hotel-SuperAgent-Timestamp", timestamp)
|
||||
.header("X-TH-Hotel-SuperAgent-Nonce", "nonce-invalid-signature")
|
||||
.header("X-TH-Hotel-SuperAgent-Signature", "sha256=invalid"))
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andExpect(jsonPath("$.error_code").value("AUTH_SIGNATURE_INVALID"))
|
||||
.andExpect(content().string(not(containsString(SECRET))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateTemporaryOrderTaskAndCardForNewBookingWithoutBusinessKey() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage("mail-new-booking-temp-001");
|
||||
String body = minimalBody(source.inboxId().toString(), "New Booking", "normal_task", "new_fit_reservation", """
|
||||
"case_keys": {},
|
||||
"extracted_fields": {
|
||||
"booking_object_type": "FIT Reservation",
|
||||
"arrival_date": "2026-08-01",
|
||||
"departure_date": "2026-08-02"
|
||||
}
|
||||
""");
|
||||
|
||||
mockMvc.perform(signedPost(body, "nonce-new-booking-temp-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.source_message_id").value(source.inboxId().toString()))
|
||||
.andExpect(jsonPath("$.idempotent_replay").value(false))
|
||||
.andExpect(jsonPath("$.accepted_count").value(1))
|
||||
.andExpect(jsonPath("$.items[0].source_event_index").value(1))
|
||||
.andExpect(jsonPath("$.items[0].array_index").value(1))
|
||||
.andExpect(jsonPath("$.items[0].system_task_type").value("NEW_BOOKING"))
|
||||
.andExpect(jsonPath("$.items[0].task_card_type").value("NEW_BOOKING"))
|
||||
.andExpect(jsonPath("$.items[0].task_status").value("PENDING_CONFIRM"))
|
||||
.andExpect(jsonPath("$.items[0].order_status").value("TEMPORARY"))
|
||||
.andExpect(content().string(not(containsString(SECRET))));
|
||||
|
||||
Long transitionCount = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM workflow_reservation_ai_transition
|
||||
WHERE source_message_id = ?
|
||||
AND ai_task_type = 'New Booking'
|
||||
AND system_task_type = 'NEW_BOOKING'
|
||||
""", Long.class, source.inboxId());
|
||||
Long temporaryOrderCount = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM workflow_reservation_order
|
||||
WHERE source_message_id = ?
|
||||
AND order_status = 'TEMPORARY'
|
||||
AND order_key_type = 'TEMPORARY'
|
||||
""", Long.class, source.inboxId());
|
||||
Long taskCardCount = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM workflow_reservation_task_card
|
||||
WHERE ai_payload_json LIKE '%New Booking%'
|
||||
AND field_contract_version = 'code-v1'
|
||||
""", Long.class);
|
||||
|
||||
org.assertj.core.api.Assertions.assertThat(transitionCount).isEqualTo(1L);
|
||||
org.assertj.core.api.Assertions.assertThat(temporaryOrderCount).isEqualTo(1L);
|
||||
org.assertj.core.api.Assertions.assertThat(taskCardCount).isGreaterThanOrEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnIdempotentReplayForSameBodyWithNewNonce() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage("mail-idempotent-replay-001");
|
||||
String body = minimalBody(source.inboxId().toString(), "New Booking", "normal_task", "new_fit_reservation", """
|
||||
"case_keys": {},
|
||||
"extracted_fields": {}
|
||||
""");
|
||||
|
||||
mockMvc.perform(signedPost(body, "nonce-idempotent-replay-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.idempotent_replay").value(false));
|
||||
|
||||
mockMvc.perform(signedPost(body, "nonce-idempotent-replay-002"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.idempotent_replay").value(true))
|
||||
.andExpect(jsonPath("$.warnings[0].code").value("IDEMPOTENT_REPLAY"));
|
||||
|
||||
Long batchCount = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM workflow_reservation_ai_batch
|
||||
WHERE source_message_id = ?
|
||||
""", Long.class, source.inboxId());
|
||||
org.assertj.core.api.Assertions.assertThat(batchCount).isEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectSameNonceReplayBeforeProcessingBodyAgain() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage("mail-nonce-replay-001");
|
||||
String body = minimalBody(source.inboxId().toString(), "New Booking", "normal_task", "new_fit_reservation", """
|
||||
"case_keys": {},
|
||||
"extracted_fields": {}
|
||||
""");
|
||||
|
||||
mockMvc.perform(signedPost(body, "nonce-replay-same"))
|
||||
.andExpect(status().isCreated());
|
||||
|
||||
mockMvc.perform(signedPost(body, "nonce-replay-same"))
|
||||
.andExpect(status().isConflict())
|
||||
.andExpect(jsonPath("$.error_code").value("AUTH_NONCE_REPLAY"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectDifferentPayloadForSameSourceMessage() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage("mail-idempotency-conflict-001");
|
||||
String firstBody = minimalBody(source.inboxId().toString(), "New Booking", "normal_task", "new_fit_reservation", """
|
||||
"case_keys": {},
|
||||
"extracted_fields": {"arrival_date": "2026-08-01"}
|
||||
""");
|
||||
String changedBody = minimalBody(source.inboxId().toString(), "New Booking", "normal_task", "new_fit_reservation", """
|
||||
"case_keys": {},
|
||||
"extracted_fields": {"arrival_date": "2026-08-03"}
|
||||
""");
|
||||
|
||||
mockMvc.perform(signedPost(firstBody, "nonce-idempotency-conflict-001"))
|
||||
.andExpect(status().isCreated());
|
||||
|
||||
mockMvc.perform(signedPost(changedBody, "nonce-idempotency-conflict-002"))
|
||||
.andExpect(status().isConflict())
|
||||
.andExpect(jsonPath("$.error_code").value("IDEMPOTENCY_CONFLICT"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectTaskResultWhenStableStringExceedsDatabaseLimit() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage("mail-field-too-long-001");
|
||||
String body = minimalBody(source.inboxId().toString(), "New Booking", "normal_task", "new_fit_reservation", """
|
||||
"case_keys": {},
|
||||
"extracted_fields": {}
|
||||
""").replace(
|
||||
"\"catalog_code\": \"S01\"",
|
||||
"\"catalog_code\": \"" + "S".repeat(33) + "\"");
|
||||
|
||||
mockMvc.perform(signedPost(body, "nonce-field-too-long-001"))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.error_code").value("TASK_RESULT_FIELD_TOO_LONG"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectSecurityHeaderWhenClientIdExceedsDatabaseLimit() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage("mail-client-id-too-long-001");
|
||||
String body = minimalBody(source.inboxId().toString(), "New Booking", "normal_task", "new_fit_reservation", """
|
||||
"case_keys": {},
|
||||
"extracted_fields": {}
|
||||
""");
|
||||
String longClientId = "client-" + "x".repeat(129);
|
||||
|
||||
mockMvc.perform(signedPostWithClientId(body, "nonce-client-id-too-long-001", longClientId))
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andExpect(jsonPath("$.error_code").value("AUTH_HEADER_INVALID"))
|
||||
.andExpect(content().string(not(containsString(SECRET))));
|
||||
|
||||
Long nonceCount = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM integration_superagent_task_result_nonce
|
||||
WHERE nonce = 'nonce-client-id-too-long-001'
|
||||
""", Long.class);
|
||||
assertThat(nonceCount).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldEnforceOnlyOneActiveOrderPerHotelAndBusinessKey() {
|
||||
jdbcTemplate.update("""
|
||||
INSERT INTO workflow_reservation_order (
|
||||
id, hotel_id, order_key_type, order_business_key, active_business_key,
|
||||
temporary_order_code, order_status, display_name, source_message_id,
|
||||
version, created_at, updated_at
|
||||
)
|
||||
VALUES (
|
||||
910000000000000001, 'HOTEL-TEST', 'GROUP_CODE', 'GRP-ACTIVE-UNIQUE-001',
|
||||
'GRP-ACTIVE-UNIQUE-001', 'TMP-UNIQUE-001', 'ACTIVE', 'GRP-ACTIVE-UNIQUE-001',
|
||||
910000000000000101, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
||||
)
|
||||
""");
|
||||
|
||||
assertThatThrownBy(() -> jdbcTemplate.update("""
|
||||
INSERT INTO workflow_reservation_order (
|
||||
id, hotel_id, order_key_type, order_business_key, active_business_key,
|
||||
temporary_order_code, order_status, display_name, source_message_id,
|
||||
version, created_at, updated_at
|
||||
)
|
||||
VALUES (
|
||||
910000000000000002, 'HOTEL-TEST', 'GROUP_CODE', 'GRP-ACTIVE-UNIQUE-001',
|
||||
'GRP-ACTIVE-UNIQUE-001', 'TMP-UNIQUE-002', 'ACTIVE', 'GRP-ACTIVE-UNIQUE-001',
|
||||
910000000000000102, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
||||
)
|
||||
""")).isInstanceOf(DataIntegrityViolationException.class);
|
||||
|
||||
jdbcTemplate.update("""
|
||||
INSERT INTO workflow_reservation_order (
|
||||
id, hotel_id, order_key_type, order_business_key, active_business_key,
|
||||
temporary_order_code, order_status, display_name, source_message_id,
|
||||
version, created_at, updated_at
|
||||
)
|
||||
VALUES (
|
||||
910000000000000003, 'HOTEL-TEST', 'CONFIRMATION_NUMBER', 'CNF-ACTIVE-UNIQUE-001',
|
||||
'CNF-ACTIVE-UNIQUE-001', 'TMP-UNIQUE-003', 'ACTIVE', 'CNF-ACTIVE-UNIQUE-001',
|
||||
910000000000000103, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
||||
)
|
||||
""");
|
||||
|
||||
assertThatThrownBy(() -> jdbcTemplate.update("""
|
||||
INSERT INTO workflow_reservation_order (
|
||||
id, hotel_id, order_key_type, order_business_key, active_business_key,
|
||||
temporary_order_code, order_status, display_name, source_message_id,
|
||||
version, created_at, updated_at
|
||||
)
|
||||
VALUES (
|
||||
910000000000000004, 'HOTEL-TEST', 'CONFIRMATION_NUMBER', 'CNF-ACTIVE-UNIQUE-001',
|
||||
'CNF-ACTIVE-UNIQUE-001', 'TMP-UNIQUE-004', 'ACTIVE', 'CNF-ACTIVE-UNIQUE-001',
|
||||
910000000000000104, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
||||
)
|
||||
""")).isInstanceOf(DataIntegrityViolationException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldEnforceUniqueExecutionOrderForQueueTasksInSameOrder() {
|
||||
jdbcTemplate.update("""
|
||||
INSERT INTO workflow_reservation_order (
|
||||
id, hotel_id, order_key_type, order_business_key, active_business_key,
|
||||
temporary_order_code, order_status, display_name, source_message_id,
|
||||
version, created_at, updated_at
|
||||
)
|
||||
VALUES (
|
||||
910000000000000201, 'HOTEL-TEST', 'GROUP_CODE', 'GRP-QUEUE-UNIQUE-001',
|
||||
'GRP-QUEUE-UNIQUE-001', 'TMP-QUEUE-UNIQUE-001', 'ACTIVE', 'GRP-QUEUE-UNIQUE-001',
|
||||
910000000000000301, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
||||
)
|
||||
""");
|
||||
jdbcTemplate.update("""
|
||||
INSERT INTO workflow_reservation_task (
|
||||
id, hotel_id, order_id, source_message_id, ai_transition_id,
|
||||
result_type, ai_task_type, system_task_type, task_card_type,
|
||||
task_status, queue_participation, execution_order,
|
||||
blocked_until_parent_completed, version, created_at, updated_at
|
||||
)
|
||||
VALUES (
|
||||
910000000000000202, 'HOTEL-TEST', 910000000000000201,
|
||||
910000000000000301, 910000000000000401, 'normal_task',
|
||||
'New Booking', 'NEW_BOOKING', 'NEW_BOOKING',
|
||||
'PENDING_CONFIRM', 1, 1, 0, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
||||
)
|
||||
""");
|
||||
|
||||
assertThatThrownBy(() -> jdbcTemplate.update("""
|
||||
INSERT INTO workflow_reservation_task (
|
||||
id, hotel_id, order_id, source_message_id, ai_transition_id,
|
||||
result_type, ai_task_type, system_task_type, task_card_type,
|
||||
task_status, queue_participation, execution_order,
|
||||
blocked_until_parent_completed, version, created_at, updated_at
|
||||
)
|
||||
VALUES (
|
||||
910000000000000203, 'HOTEL-TEST', 910000000000000201,
|
||||
910000000000000302, 910000000000000402, 'normal_task',
|
||||
'Update Booking', 'UPDATE_BOOKING', 'UPDATE_BOOKING',
|
||||
'PENDING_CONFIRM', 1, 1, 0, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
||||
)
|
||||
""")).isInstanceOf(DataIntegrityViolationException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAttachUpdateBookingToExistingActiveOrderByGroupCode() throws Exception {
|
||||
SourceMessageCaptureResult newSource = captureSourceMessage("mail-existing-order-new-001");
|
||||
String newBookingBody = minimalBody(newSource.inboxId().toString(), "New Booking", "normal_task", "new_group_block", """
|
||||
"case_keys": {"group_code": "GRP-M002-EXIST-001"},
|
||||
"extracted_fields": {"booking_object_type": "Group Block"}
|
||||
""");
|
||||
|
||||
MvcResult newBookingResult = mockMvc.perform(signedPost(newBookingBody, "nonce-existing-order-new-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.items[0].order_status").value("ACTIVE"))
|
||||
.andReturn();
|
||||
String existingOrderId = com.jayway.jsonpath.JsonPath.read(
|
||||
newBookingResult.getResponse().getContentAsString(),
|
||||
"$.items[0].order_id"
|
||||
);
|
||||
|
||||
SourceMessageCaptureResult updateSource = captureSourceMessage("mail-existing-order-update-001");
|
||||
String updateBody = minimalBody(updateSource.inboxId().toString(), "Update Booking", "normal_task", "update_stay_dates", """
|
||||
"case_keys": {"group_code": "GRP-M002-EXIST-001"},
|
||||
"extracted_fields": {"update_subtypes": ["update_stay_dates"]}
|
||||
""");
|
||||
|
||||
mockMvc.perform(signedPost(updateBody, "nonce-existing-order-update-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.items[0].order_id").value(existingOrderId))
|
||||
.andExpect(jsonPath("$.items[0].system_task_type").value("UPDATE_BOOKING"))
|
||||
.andExpect(jsonPath("$.items[0].task_card_type").value("UPDATE_BOOKING"))
|
||||
.andExpect(jsonPath("$.items[0].task_status").value("PENDING_CONFIRM"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateReadOnlyMessageNotificationOnTemporaryOrderOutsideQueue() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage("mail-message-notification-001");
|
||||
String body = minimalBody(
|
||||
source.inboxId().toString(),
|
||||
"Message Notification",
|
||||
"informational_message",
|
||||
"thank_you",
|
||||
"""
|
||||
"case_keys": {},
|
||||
"informational_message": {
|
||||
"message_type": "thank_you",
|
||||
"summary": "Guest acknowledged the booking update."
|
||||
}
|
||||
""");
|
||||
|
||||
mockMvc.perform(signedPost(body, "nonce-message-notification-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.items[0].system_task_type").value("INFORMATIONAL_MESSAGE"))
|
||||
.andExpect(jsonPath("$.items[0].task_card_type").value("MESSAGE_NOTIFICATION"))
|
||||
.andExpect(jsonPath("$.items[0].task_status").value("COMPLETED"))
|
||||
.andExpect(jsonPath("$.items[0].order_status").value("TEMPORARY"));
|
||||
|
||||
Long queueParticipationCount = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM workflow_reservation_task
|
||||
WHERE source_message_id = ?
|
||||
AND queue_participation = 0
|
||||
AND task_status = 'COMPLETED'
|
||||
""", Long.class, source.inboxId());
|
||||
org.assertj.core.api.Assertions.assertThat(queueParticipationCount).isEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMarkLaterTaskReadOnlyUntilPreviousQueueTaskIsCompletedOrFailed() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage("mail-queue-readonly-001");
|
||||
String body = twoTaskBody(source.inboxId().toString(), "GRP-M002-QUEUE-001");
|
||||
|
||||
MvcResult result = mockMvc.perform(signedPost(body, "nonce-queue-readonly-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.accepted_count").value(2))
|
||||
.andExpect(jsonPath("$.items[0].execution_order").value(1))
|
||||
.andExpect(jsonPath("$.items[1].execution_order").value(2))
|
||||
.andReturn();
|
||||
String firstTaskId = com.jayway.jsonpath.JsonPath.read(result.getResponse().getContentAsString(), "$.items[0].task_id");
|
||||
String secondTaskId = com.jayway.jsonpath.JsonPath.read(result.getResponse().getContentAsString(), "$.items[1].task_id");
|
||||
|
||||
mockMvc.perform(get("/api/reservation/tasks/{taskId}", secondTaskId))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.task_id").value(secondTaskId))
|
||||
.andExpect(jsonPath("$.availability.blocked").value(true))
|
||||
.andExpect(jsonPath("$.availability.read_only").value(true))
|
||||
.andExpect(jsonPath("$.availability.editable").value(false))
|
||||
.andExpect(jsonPath("$.availability.confirmable").value(false))
|
||||
.andExpect(jsonPath("$.availability.executable").value(false))
|
||||
.andExpect(jsonPath("$.availability.blocked_by_task_id").value(firstTaskId));
|
||||
|
||||
jdbcTemplate.update("""
|
||||
UPDATE workflow_reservation_task
|
||||
SET task_status = 'READY'
|
||||
WHERE id = ?
|
||||
""", Long.valueOf(firstTaskId));
|
||||
|
||||
mockMvc.perform(get("/api/reservation/tasks/{taskId}", secondTaskId))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.availability.blocked").value(true))
|
||||
.andExpect(jsonPath("$.availability.blocked_by_task_id").value(firstTaskId));
|
||||
|
||||
jdbcTemplate.update("""
|
||||
UPDATE workflow_reservation_task
|
||||
SET task_status = 'EXECUTING'
|
||||
WHERE id = ?
|
||||
""", Long.valueOf(firstTaskId));
|
||||
|
||||
mockMvc.perform(get("/api/reservation/tasks/{taskId}", secondTaskId))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.availability.blocked").value(true))
|
||||
.andExpect(jsonPath("$.availability.blocked_by_task_id").value(firstTaskId));
|
||||
|
||||
jdbcTemplate.update("""
|
||||
UPDATE workflow_reservation_task
|
||||
SET task_status = 'FAILED'
|
||||
WHERE id = ?
|
||||
""", Long.valueOf(firstTaskId));
|
||||
|
||||
mockMvc.perform(get("/api/reservation/tasks/{taskId}", secondTaskId))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.availability.blocked").value(false))
|
||||
.andExpect(jsonPath("$.availability.read_only").value(false))
|
||||
.andExpect(jsonPath("$.availability.editable").value(true))
|
||||
.andExpect(jsonPath("$.availability.confirmable").value(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnTaskDetailFieldsFromTaskCardMatrix() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage("mail-detail-matrix-001");
|
||||
String body = minimalBody(source.inboxId().toString(), "New Booking", "normal_task", "new_fit_reservation", """
|
||||
"case_keys": {"confirmation_number": "CNF-MATRIX-001"},
|
||||
"extracted_fields": {"booking_object_type": "FIT Reservation"}
|
||||
""");
|
||||
|
||||
MvcResult result = mockMvc.perform(signedPost(body, "nonce-detail-matrix-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn();
|
||||
String taskId = com.jayway.jsonpath.JsonPath.read(result.getResponse().getContentAsString(), "$.items[0].task_id");
|
||||
|
||||
mockMvc.perform(get("/api/reservation/tasks/{taskId}", taskId))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.task_card_type").value("NEW_BOOKING"))
|
||||
.andExpect(jsonPath("$.field_contract_version").value("code-v1"))
|
||||
.andExpect(jsonPath("$.fields.length()").value(18))
|
||||
.andExpect(jsonPath("$.fields[?(@.field_path=='case_keys.confirmation_number')].display_name")
|
||||
.value(org.hamcrest.Matchers.contains("Confirmation No.")))
|
||||
.andExpect(jsonPath("$.fields[?(@.field_path=='case_keys.confirmation_number')].editable")
|
||||
.value(org.hamcrest.Matchers.contains("是")))
|
||||
.andExpect(jsonPath("$.fields[?(@.field_path=='case_keys.confirmation_number')].write_path")
|
||||
.value(org.hamcrest.Matchers.contains("confirmed_payload_json.case_keys.confirmation_number")))
|
||||
.andExpect(jsonPath("$.fields[?(@.field_path=='case_keys.confirmation_number')].value")
|
||||
.value(org.hamcrest.Matchers.contains("CNF-MATRIX-001")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConvertFallbackToUpdateBookingAndLogicDeleteEmptyTemporaryOrder() throws Exception {
|
||||
SourceMessageCaptureResult targetSource = captureSourceMessage("mail-fallback-target-order-001");
|
||||
String targetOrderBody = minimalBody(targetSource.inboxId().toString(), "New Booking", "normal_task", "new_group_block", """
|
||||
"case_keys": {"group_code": "GRP-FALLBACK-TARGET-001"},
|
||||
"extracted_fields": {"booking_object_type": "Group Block"}
|
||||
""");
|
||||
MvcResult targetOrderResult = mockMvc.perform(signedPost(targetOrderBody, "nonce-fallback-target-order-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn();
|
||||
String targetOrderId = com.jayway.jsonpath.JsonPath.read(
|
||||
targetOrderResult.getResponse().getContentAsString(),
|
||||
"$.items[0].order_id"
|
||||
);
|
||||
|
||||
SourceMessageCaptureResult fallbackSource = captureSourceMessage("mail-fallback-convert-001");
|
||||
String fallbackBody = minimalBody(fallbackSource.inboxId().toString(), "Fallback", "manual_review", "manual_review", """
|
||||
"case_keys": {"group_code": "GRP-FALLBACK-CANDIDATE-001"},
|
||||
"manual_review": {"reason_code": "business_boundary_unclear", "visible_reason": "Need human routing."},
|
||||
"extracted_fields": {}
|
||||
""");
|
||||
MvcResult fallbackResult = mockMvc.perform(signedPost(fallbackBody, "nonce-fallback-convert-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn();
|
||||
String fallbackTaskId = com.jayway.jsonpath.JsonPath.read(
|
||||
fallbackResult.getResponse().getContentAsString(),
|
||||
"$.items[0].task_id"
|
||||
);
|
||||
String temporaryOrderId = com.jayway.jsonpath.JsonPath.read(
|
||||
fallbackResult.getResponse().getContentAsString(),
|
||||
"$.items[0].order_id"
|
||||
);
|
||||
|
||||
mockMvc.perform(post("/api/reservation/tasks/{taskId}/manual-review-conversions", fallbackTaskId)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"target_task_type": "UPDATE_BOOKING",
|
||||
"target_order_id": "%s"
|
||||
}
|
||||
""".formatted(targetOrderId)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.task_id").value(fallbackTaskId))
|
||||
.andExpect(jsonPath("$.order_id").value(targetOrderId))
|
||||
.andExpect(jsonPath("$.system_task_type").value("UPDATE_BOOKING"))
|
||||
.andExpect(jsonPath("$.task_card_type").value("UPDATE_BOOKING"))
|
||||
.andExpect(jsonPath("$.original_order_logic_deleted").value(true));
|
||||
|
||||
String tempOrderStatus = jdbcTemplate.queryForObject("""
|
||||
SELECT order_status
|
||||
FROM workflow_reservation_order
|
||||
WHERE id = ?
|
||||
""", String.class, Long.valueOf(temporaryOrderId));
|
||||
Long auditCount = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM workflow_reservation_audit_log
|
||||
WHERE task_id = ?
|
||||
AND action = 'MANUAL_REVIEW_CONVERT'
|
||||
""", Long.class, Long.valueOf(fallbackTaskId));
|
||||
assertThat(tempOrderStatus).isEqualTo("LOGIC_DELETED");
|
||||
assertThat(auditCount).isEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConvertFallbackToNewBookingAndKeepOriginalTemporaryOrder() throws Exception {
|
||||
SourceMessageCaptureResult fallbackSource = captureSourceMessage("mail-fallback-convert-new-001");
|
||||
String fallbackBody = minimalBody(fallbackSource.inboxId().toString(), "Fallback", "manual_review", "manual_review", """
|
||||
"case_keys": {"confirmation_number": "CNF-FALLBACK-NEW-001"},
|
||||
"manual_review": {"reason_code": "business_boundary_unclear", "visible_reason": "Need human routing."},
|
||||
"extracted_fields": {}
|
||||
""");
|
||||
MvcResult fallbackResult = mockMvc.perform(signedPost(fallbackBody, "nonce-fallback-convert-new-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn();
|
||||
String fallbackTaskId = com.jayway.jsonpath.JsonPath.read(
|
||||
fallbackResult.getResponse().getContentAsString(),
|
||||
"$.items[0].task_id"
|
||||
);
|
||||
String temporaryOrderId = com.jayway.jsonpath.JsonPath.read(
|
||||
fallbackResult.getResponse().getContentAsString(),
|
||||
"$.items[0].order_id"
|
||||
);
|
||||
|
||||
mockMvc.perform(post("/api/reservation/tasks/{taskId}/manual-review-conversions", fallbackTaskId)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"target_task_type": "NEW_BOOKING"
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.task_id").value(fallbackTaskId))
|
||||
.andExpect(jsonPath("$.order_id").value(temporaryOrderId))
|
||||
.andExpect(jsonPath("$.system_task_type").value("NEW_BOOKING"))
|
||||
.andExpect(jsonPath("$.task_card_type").value("NEW_BOOKING"))
|
||||
.andExpect(jsonPath("$.original_order_logic_deleted").value(false));
|
||||
|
||||
String tempOrderStatus = jdbcTemplate.queryForObject("""
|
||||
SELECT order_status
|
||||
FROM workflow_reservation_order
|
||||
WHERE id = ?
|
||||
""", String.class, Long.valueOf(temporaryOrderId));
|
||||
assertThat(tempOrderStatus).isEqualTo("ACTIVE");
|
||||
String orderBusinessKey = jdbcTemplate.queryForObject("""
|
||||
SELECT order_business_key
|
||||
FROM workflow_reservation_order
|
||||
WHERE id = ?
|
||||
""", String.class, Long.valueOf(temporaryOrderId));
|
||||
String activeBusinessKey = jdbcTemplate.queryForObject("""
|
||||
SELECT active_business_key
|
||||
FROM workflow_reservation_order
|
||||
WHERE id = ?
|
||||
""", String.class, Long.valueOf(temporaryOrderId));
|
||||
String displayName = jdbcTemplate.queryForObject("""
|
||||
SELECT display_name
|
||||
FROM workflow_reservation_order
|
||||
WHERE id = ?
|
||||
""", String.class, Long.valueOf(temporaryOrderId));
|
||||
assertThat(orderBusinessKey).isEqualTo("CNF-FALLBACK-NEW-001");
|
||||
assertThat(activeBusinessKey).isEqualTo("CNF-FALLBACK-NEW-001");
|
||||
assertThat(displayName).isEqualTo("CNF-FALLBACK-NEW-001");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConvertFallbackToCancelBookingWhenTargetOrderProvided() throws Exception {
|
||||
SourceMessageCaptureResult targetSource = captureSourceMessage("mail-fallback-cancel-target-001");
|
||||
String targetOrderBody = minimalBody(targetSource.inboxId().toString(), "New Booking", "normal_task", "new_fit_reservation", """
|
||||
"case_keys": {"confirmation_number": "CNF-FALLBACK-CANCEL-001"},
|
||||
"extracted_fields": {"booking_object_type": "FIT Reservation"}
|
||||
""");
|
||||
MvcResult targetOrderResult = mockMvc.perform(signedPost(targetOrderBody, "nonce-fallback-cancel-target-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn();
|
||||
String targetOrderId = com.jayway.jsonpath.JsonPath.read(
|
||||
targetOrderResult.getResponse().getContentAsString(),
|
||||
"$.items[0].order_id"
|
||||
);
|
||||
|
||||
SourceMessageCaptureResult fallbackSource = captureSourceMessage("mail-fallback-convert-cancel-001");
|
||||
String fallbackBody = minimalBody(fallbackSource.inboxId().toString(), "Fallback", "manual_review", "manual_review", """
|
||||
"case_keys": {},
|
||||
"manual_review": {"reason_code": "route_conflict", "visible_reason": "Need human routing."},
|
||||
"extracted_fields": {}
|
||||
""");
|
||||
MvcResult fallbackResult = mockMvc.perform(signedPost(fallbackBody, "nonce-fallback-convert-cancel-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn();
|
||||
String fallbackTaskId = com.jayway.jsonpath.JsonPath.read(
|
||||
fallbackResult.getResponse().getContentAsString(),
|
||||
"$.items[0].task_id"
|
||||
);
|
||||
|
||||
mockMvc.perform(post("/api/reservation/tasks/{taskId}/manual-review-conversions", fallbackTaskId)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"target_task_type": "CANCEL_BOOKING",
|
||||
"target_order_id": "%s"
|
||||
}
|
||||
""".formatted(targetOrderId)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.task_id").value(fallbackTaskId))
|
||||
.andExpect(jsonPath("$.order_id").value(targetOrderId))
|
||||
.andExpect(jsonPath("$.system_task_type").value("CANCEL_BOOKING"))
|
||||
.andExpect(jsonPath("$.task_card_type").value("CANCEL_BOOKING"))
|
||||
.andExpect(jsonPath("$.original_order_logic_deleted").value(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRequireTargetOrderWhenConvertingFallbackToUpdateOrCancel() throws Exception {
|
||||
SourceMessageCaptureResult fallbackSource = captureSourceMessage("mail-fallback-target-required-001");
|
||||
String fallbackBody = minimalBody(fallbackSource.inboxId().toString(), "Fallback", "manual_review", "manual_review", """
|
||||
"case_keys": {},
|
||||
"manual_review": {"reason_code": "route_conflict", "visible_reason": "Need human routing."},
|
||||
"extracted_fields": {}
|
||||
""");
|
||||
MvcResult fallbackResult = mockMvc.perform(signedPost(fallbackBody, "nonce-fallback-target-required-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andReturn();
|
||||
String fallbackTaskId = com.jayway.jsonpath.JsonPath.read(
|
||||
fallbackResult.getResponse().getContentAsString(),
|
||||
"$.items[0].task_id"
|
||||
);
|
||||
|
||||
mockMvc.perform(post("/api/reservation/tasks/{taskId}/manual-review-conversions", fallbackTaskId)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"target_task_type": "CANCEL_BOOKING"
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.error_code").value("TARGET_ORDER_REQUIRED"));
|
||||
}
|
||||
|
||||
private SourceMessageCaptureResult captureSourceMessage(String externalMessageId) {
|
||||
return captureService.capture(new CaptureSourceMessageCommand(
|
||||
"HOTEL-TEST",
|
||||
"AGENTBUS",
|
||||
"EMAIL",
|
||||
externalMessageId,
|
||||
"thread-" + externalMessageId,
|
||||
"frame-" + externalMessageId,
|
||||
"session-m002",
|
||||
Instant.parse("2026-07-07T08:00:00Z"),
|
||||
"guest@example.test",
|
||||
"M002 SuperAgent intake",
|
||||
"Please handle booking message.",
|
||||
"<html><body>Please handle booking message.</body></html>",
|
||||
"{\"source\":{\"external_message_id\":\"" + externalMessageId + "\"}}",
|
||||
"agentbus-outlook-v1",
|
||||
List.of()
|
||||
));
|
||||
}
|
||||
|
||||
private org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder signedPost(
|
||||
String body,
|
||||
String nonce) throws Exception {
|
||||
return signedPostWithClientId(body, nonce, CLIENT_ID);
|
||||
}
|
||||
|
||||
private org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder signedPostWithClientId(
|
||||
String body,
|
||||
String nonce,
|
||||
String clientId) throws Exception {
|
||||
String timestamp = Instant.now().toString();
|
||||
return post(ENDPOINT)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(body)
|
||||
.header("X-TH-Hotel-SuperAgent-Client-Id", clientId)
|
||||
.header("X-TH-Hotel-SuperAgent-Timestamp", timestamp)
|
||||
.header("X-TH-Hotel-SuperAgent-Nonce", nonce)
|
||||
.header("X-TH-Hotel-SuperAgent-Signature", signature(body, nonce, timestamp, clientId));
|
||||
}
|
||||
|
||||
private String minimalBody(
|
||||
String sourceMessageId,
|
||||
String taskType,
|
||||
String resultType,
|
||||
String taskSubtype,
|
||||
String itemFields) {
|
||||
return """
|
||||
{
|
||||
"source_message_id": "%s",
|
||||
"ai_task_results": [
|
||||
{
|
||||
"source_event_index": 1,
|
||||
"catalog_code": "S01",
|
||||
"skill_id": "S01_new_booking_skill",
|
||||
"result_type": "%s",
|
||||
"task_type": "%s",
|
||||
"task_subtype": "%s",
|
||||
"current_or_history": "current",
|
||||
"visible_reason": "AI extracted a task result.",
|
||||
"relevant_message_excerpt": "Please handle booking message.",
|
||||
"attachments": [],
|
||||
"file_references": [],
|
||||
"context_used": {},
|
||||
%s,
|
||||
"additional_operations": [],
|
||||
"idempotency_key": null
|
||||
}
|
||||
],
|
||||
"extraction_warnings": []
|
||||
}
|
||||
""".formatted(sourceMessageId, resultType, taskType, taskSubtype, itemFields);
|
||||
}
|
||||
|
||||
private String twoTaskBody(String sourceMessageId, String groupCode) {
|
||||
return """
|
||||
{
|
||||
"source_message_id": "%s",
|
||||
"ai_task_results": [
|
||||
{
|
||||
"source_event_index": 1,
|
||||
"catalog_code": "S01",
|
||||
"skill_id": "S01_new_booking_skill",
|
||||
"result_type": "normal_task",
|
||||
"task_type": "New Booking",
|
||||
"task_subtype": "new_group_block",
|
||||
"current_or_history": "current",
|
||||
"visible_reason": "AI extracted a new booking.",
|
||||
"relevant_message_excerpt": "Please create the group booking.",
|
||||
"attachments": [],
|
||||
"file_references": [],
|
||||
"context_used": {},
|
||||
"case_keys": {"group_code": "%s"},
|
||||
"extracted_fields": {"booking_object_type": "Group Block"},
|
||||
"additional_operations": [],
|
||||
"idempotency_key": null
|
||||
},
|
||||
{
|
||||
"source_event_index": 2,
|
||||
"catalog_code": "S02",
|
||||
"skill_id": "S02_update_booking_amendment_skill",
|
||||
"result_type": "normal_task",
|
||||
"task_type": "Update Booking",
|
||||
"task_subtype": "update_stay_dates",
|
||||
"current_or_history": "current",
|
||||
"visible_reason": "AI extracted an update.",
|
||||
"relevant_message_excerpt": "Please update the group booking.",
|
||||
"attachments": [],
|
||||
"file_references": [],
|
||||
"context_used": {},
|
||||
"case_keys": {"group_code": "%s"},
|
||||
"extracted_fields": {"update_subtypes": ["update_stay_dates"]},
|
||||
"additional_operations": [],
|
||||
"idempotency_key": null
|
||||
}
|
||||
],
|
||||
"extraction_warnings": []
|
||||
}
|
||||
""".formatted(sourceMessageId, groupCode, groupCode);
|
||||
}
|
||||
|
||||
private String signature(String body, String nonce, String timestamp) throws Exception {
|
||||
return signature(body, nonce, timestamp, CLIENT_ID);
|
||||
}
|
||||
|
||||
private String signature(String body, String nonce, String timestamp, String clientId) throws Exception {
|
||||
String bodySha256 = sha256(body);
|
||||
String canonical = "POST\n" + ENDPOINT + "\n" + timestamp + "\n" + nonce + "\n" + clientId + "\n" + bodySha256;
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(SECRET.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
|
||||
return "sha256=" + HexFormat.of().formatHex(mac.doFinal(canonical.getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
|
||||
private String sha256(String body) throws Exception {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
return HexFormat.of().formatHex(digest.digest(body.getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.control;
|
||||
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import cn.nianxx.thhotel.ThHotelApplication;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
@SpringBootTest(
|
||||
classes = ThHotelApplication.class,
|
||||
properties = {
|
||||
"superagent.task-result.hmac-secret=test-superagent-secret",
|
||||
"superagent.task-result.max-body-bytes=16"
|
||||
})
|
||||
@AutoConfigureMockMvc
|
||||
@ActiveProfiles("test")
|
||||
class SuperAgentTaskResultMaxBodyControllerTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Test
|
||||
void shouldRejectBodyLargerThanConfiguredLimitBeforeParsingJson() throws Exception {
|
||||
mockMvc.perform(post("/api/integrations/superagent/task-results")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"source_message_id\":\"too-large\"}"))
|
||||
.andExpect(status().isPayloadTooLarge())
|
||||
.andExpect(jsonPath("$.error_code").value("REQUEST_BODY_TOO_LARGE"))
|
||||
.andExpect(content().string(not(containsString("test-superagent-secret"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectBodyLargerThanConfiguredLimitWhenContextPathIsConfigured() throws Exception {
|
||||
mockMvc.perform(post("/hotel/api/integrations/superagent/task-results")
|
||||
.contextPath("/hotel")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"source_message_id\":\"too-large-context-path\"}"))
|
||||
.andExpect(status().isPayloadTooLarge())
|
||||
.andExpect(jsonPath("$.error_code").value("REQUEST_BODY_TOO_LARGE"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user