实现SuperAgent特殊入口结果处理
This commit is contained in:
@@ -31,6 +31,7 @@ import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.web.client.RestClientResponseException;
|
||||
|
||||
@SpringBootTest(
|
||||
classes = ThHotelApplication.class,
|
||||
@@ -212,6 +213,43 @@ class DebugEmlSuperAgentControllerTest {
|
||||
.andExpect(jsonPath("$.html_render_mode").value("SANITIZED_HTML"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTreatSuperAgentS000AnswerAsRecognizedEntryResult() throws Exception {
|
||||
when(objectStorageService.putObject(any())).thenAnswer(invocation -> {
|
||||
ObjectStoragePutRequest request = invocation.getArgument(0);
|
||||
return new ObjectStoragePutResult(
|
||||
request.objectKey(),
|
||||
"https://oss.example.test/" + request.objectKey(),
|
||||
request.contentType(),
|
||||
request.sizeBytes());
|
||||
});
|
||||
when(superAgentOpenApiClient.invokeMailDebug(any())).thenReturn(new SuperAgentOpenApiResult(
|
||||
"session-debug-s000",
|
||||
"run-debug-s000",
|
||||
"profile-debug",
|
||||
"profile-version-debug",
|
||||
"debug-model",
|
||||
"S000,debug-eml-run-source",
|
||||
11,
|
||||
3,
|
||||
14,
|
||||
List.of("metadata", "values", "end")));
|
||||
|
||||
mockMvc.perform(multipart(ENDPOINT)
|
||||
.file(emlFile())
|
||||
.param("hotel_id", "HOTEL-TEST")
|
||||
.param("run_label", "s000-debug-upload")
|
||||
.header("X-TH-Hotel-Debug-Upload-Key", "test-debug-upload-key"))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.superagent_raw_answer").value("S000,debug-eml-run-source"))
|
||||
.andExpect(jsonPath("$.superagent_parsed_json.entry_result_code").value("S000"))
|
||||
.andExpect(jsonPath("$.superagent_parsed_json.entry_result_source_message_id")
|
||||
.value("debug-eml-run-source"))
|
||||
.andExpect(jsonPath("$.superagent_parsed_json.entry_result_meaning").value("PURE_INFORMATION"))
|
||||
.andExpect(jsonPath("$.status").value("SUPERAGENT_SUCCEEDED"))
|
||||
.andExpect(content().string(not(containsString("不是合法 JSON"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldKeepCapturedSourceMessageWhenSuperAgentFails() throws Exception {
|
||||
when(objectStorageService.putObject(any())).thenAnswer(invocation -> {
|
||||
@@ -223,7 +261,15 @@ class DebugEmlSuperAgentControllerTest {
|
||||
request.sizeBytes());
|
||||
});
|
||||
when(superAgentOpenApiClient.invokeMailDebug(any()))
|
||||
.thenThrow(new SuperAgentOpenApiException("SuperAgent Open API 调用失败。"));
|
||||
.thenThrow(new SuperAgentOpenApiException(
|
||||
"SuperAgent Open API 调用失败。",
|
||||
new RestClientResponseException(
|
||||
"401 Unauthorized",
|
||||
401,
|
||||
"Unauthorized",
|
||||
null,
|
||||
new byte[0],
|
||||
StandardCharsets.UTF_8)));
|
||||
|
||||
mockMvc.perform(multipart(ENDPOINT)
|
||||
.file(emlFile())
|
||||
@@ -232,6 +278,7 @@ class DebugEmlSuperAgentControllerTest {
|
||||
.andExpect(status().isBadGateway())
|
||||
.andExpect(jsonPath("$.error_code").value("SUPERAGENT_OPEN_API_FAILED"))
|
||||
.andExpect(content().string(not(containsString("test-debug-upload-key"))))
|
||||
.andExpect(content().string(not(containsString("401"))))
|
||||
.andExpect(content().string(not(containsString("Please create booking"))));
|
||||
|
||||
Long linkedFailedRunCount = jdbcTemplate.queryForObject("""
|
||||
@@ -241,7 +288,7 @@ class DebugEmlSuperAgentControllerTest {
|
||||
AND run_status = 'SUPERAGENT_FAILED'
|
||||
AND source_message_id IS NOT NULL
|
||||
AND original_eml_oss_url IS NOT NULL
|
||||
AND safe_error_summary = 'SuperAgent 调用失败。'
|
||||
AND safe_error_summary = 'SuperAgent Open API HTTP 调用失败,HTTP 状态:401。'
|
||||
""", Long.class);
|
||||
org.assertj.core.api.Assertions.assertThat(linkedFailedRunCount).isEqualTo(1L);
|
||||
}
|
||||
|
||||
@@ -446,6 +446,160 @@ class SuperAgentTaskResultControllerTest {
|
||||
org.assertj.core.api.Assertions.assertThat(queueParticipationCount).isEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateReadOnlySourceMessageOnlyTaskForS000TextResult() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage("mail-s000-entry-result-001");
|
||||
String body = "S000,mail-s000-entry-result-001";
|
||||
|
||||
MvcResult result = mockMvc.perform(signedPlainPost(body, "nonce-s000-entry-result-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.source_message_id").value("mail-s000-entry-result-001"))
|
||||
.andExpect(jsonPath("$.accepted_count").value(1))
|
||||
.andExpect(jsonPath("$.items[0].source_event_index").value(1))
|
||||
.andExpect(jsonPath("$.items[0].system_task_type").value("SOURCE_MESSAGE_ONLY"))
|
||||
.andExpect(jsonPath("$.items[0].task_card_type").value("SOURCE_MESSAGE_ONLY"))
|
||||
.andExpect(jsonPath("$.items[0].task_status").value("COMPLETED"))
|
||||
.andExpect(jsonPath("$.items[0].order_status").value("TEMPORARY"))
|
||||
.andExpect(content().string(not(containsString(SECRET))))
|
||||
.andReturn();
|
||||
String taskId = com.jayway.jsonpath.JsonPath.read(result.getResponse().getContentAsString(), "$.items[0].task_id");
|
||||
String orderId = com.jayway.jsonpath.JsonPath.read(result.getResponse().getContentAsString(), "$.items[0].order_id");
|
||||
|
||||
mockMvc.perform(get("/api/reservation/tasks/{taskId}", taskId))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.system_task_type").value("SOURCE_MESSAGE_ONLY"))
|
||||
.andExpect(jsonPath("$.task_card_type").value("SOURCE_MESSAGE_ONLY"))
|
||||
.andExpect(jsonPath("$.task_status").value("COMPLETED"))
|
||||
.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("$.source_message_only_result.entry_result_code").value("S000"))
|
||||
.andExpect(jsonPath("$.source_message_only_result.entry_result_meaning").value("PURE_INFORMATION"))
|
||||
.andExpect(jsonPath("$.source_message_only_result.entry_result_source_message_id")
|
||||
.value("mail-s000-entry-result-001"))
|
||||
.andExpect(jsonPath("$.source_message_only_result.raw_answer").value(body))
|
||||
.andExpect(jsonPath("$.fields.length()").value(0))
|
||||
.andExpect(jsonPath("$.opera_operations.length()").value(0));
|
||||
|
||||
mockMvc.perform(put("/api/reservation/tasks/{taskId}/draft", taskId)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"field_values": {}
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isConflict())
|
||||
.andExpect(jsonPath("$.error_code").value("TASK_STATUS_NOT_EDITABLE"));
|
||||
|
||||
mockMvc.perform(post("/api/reservation/tasks/{taskId}/manual-review-conversions", taskId)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"target_task_type": "NEW_BOOKING",
|
||||
"reason": "特殊入口结果不允许人工转换"
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.error_code").value("TASK_NOT_MANUAL_REVIEW"));
|
||||
|
||||
mockMvc.perform(get("/api/reservation/tasks")
|
||||
.param("hotel_id", "HOTEL-TEST")
|
||||
.param("task_type", "SOURCE_MESSAGE_ONLY")
|
||||
.param("keyword", "mail-s000-entry-result-001"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.items[0].task_id").value(taskId))
|
||||
.andExpect(jsonPath("$.items[0].task_type").value("SOURCE_MESSAGE_ONLY"))
|
||||
.andExpect(jsonPath("$.items[0].card_name").value("SOURCE_MESSAGE_ONLY"))
|
||||
.andExpect(jsonPath("$.items[0].task_subtype").value("S000"))
|
||||
.andExpect(jsonPath("$.items[0].queue_participation").value(false))
|
||||
.andExpect(jsonPath("$.items[0].can_process").value(false));
|
||||
|
||||
mockMvc.perform(get("/api/reservation/orders")
|
||||
.param("hotel_id", "HOTEL-TEST")
|
||||
.param("keyword", "mail-s000-entry-result-001"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.page.total").value(0));
|
||||
|
||||
Long hiddenOrderCount = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM workflow_reservation_order
|
||||
WHERE id = ?
|
||||
AND order_visibility = 'HIDDEN_SYSTEM'
|
||||
""", Long.class, Long.valueOf(orderId));
|
||||
Long sourceOnlyTaskCount = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM workflow_reservation_task
|
||||
WHERE id = ?
|
||||
AND source_message_id = ?
|
||||
AND system_task_type = 'SOURCE_MESSAGE_ONLY'
|
||||
AND task_card_type = 'SOURCE_MESSAGE_ONLY'
|
||||
AND task_subtype = 'S000'
|
||||
AND queue_participation = 0
|
||||
AND task_status = 'COMPLETED'
|
||||
""", Long.class, Long.valueOf(taskId), source.inboxId());
|
||||
assertThat(hiddenOrderCount).isEqualTo(1L);
|
||||
assertThat(sourceOnlyTaskCount).isEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateReadOnlySourceMessageOnlyTaskForS999TextResult() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage("mail-s999-entry-result-001");
|
||||
String body = "S999,mail-s999-entry-result-001";
|
||||
|
||||
MvcResult result = mockMvc.perform(signedPlainPost(body, "nonce-s999-entry-result-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.source_message_id").value("mail-s999-entry-result-001"))
|
||||
.andExpect(jsonPath("$.items[0].system_task_type").value("SOURCE_MESSAGE_ONLY"))
|
||||
.andExpect(jsonPath("$.items[0].task_card_type").value("SOURCE_MESSAGE_ONLY"))
|
||||
.andExpect(jsonPath("$.items[0].task_status").value("COMPLETED"))
|
||||
.andReturn();
|
||||
String taskId = com.jayway.jsonpath.JsonPath.read(result.getResponse().getContentAsString(), "$.items[0].task_id");
|
||||
|
||||
Long sourceOnlyTaskCount = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM workflow_reservation_task
|
||||
WHERE id = ?
|
||||
AND source_message_id = ?
|
||||
AND system_task_type = 'SOURCE_MESSAGE_ONLY'
|
||||
AND task_subtype = 'S999'
|
||||
AND queue_participation = 0
|
||||
AND task_status = 'COMPLETED'
|
||||
""", Long.class, Long.valueOf(taskId), source.inboxId());
|
||||
assertThat(sourceOnlyTaskCount).isEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnIdempotentReplayForSameS000TextResultWithNewNonce() throws Exception {
|
||||
captureSourceMessage("mail-s000-idempotent-001");
|
||||
String body = "S000,mail-s000-idempotent-001";
|
||||
|
||||
mockMvc.perform(signedPlainPost(body, "nonce-s000-idempotent-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.idempotent_replay").value(false));
|
||||
|
||||
mockMvc.perform(signedPlainPost(body, "nonce-s000-idempotent-002"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.idempotent_replay").value(true))
|
||||
.andExpect(jsonPath("$.warnings[0].code").value("IDEMPOTENT_REPLAY"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectTextResultWhenExternalSourceMessageIdNotFound() throws Exception {
|
||||
mockMvc.perform(signedPlainPost("S000,missing-source-message-001", "nonce-s000-source-missing-001"))
|
||||
.andExpect(status().isNotFound())
|
||||
.andExpect(jsonPath("$.error_code").value("SOURCE_MESSAGE_NOT_FOUND"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectUnsupportedContentTypeForTaskResultCallback() throws Exception {
|
||||
String body = "S000,source-message-unsupported-content-type-001";
|
||||
|
||||
mockMvc.perform(signedPostWithContentType(body, "nonce-unsupported-content-type-001", MediaType.APPLICATION_XML))
|
||||
.andExpect(status().isUnsupportedMediaType())
|
||||
.andExpect(jsonPath("$.error_code").value("REQUEST_CONTENT_TYPE_UNSUPPORTED"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMarkLaterTaskReadOnlyUntilPreviousQueueTaskIsCompletedOrFailed() throws Exception {
|
||||
SourceMessageCaptureResult source = captureSourceMessage("mail-queue-readonly-001");
|
||||
@@ -1130,6 +1284,26 @@ class SuperAgentTaskResultControllerTest {
|
||||
return signedPostWithClientId(body, nonce, CLIENT_ID);
|
||||
}
|
||||
|
||||
private org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder signedPlainPost(
|
||||
String body,
|
||||
String nonce) throws Exception {
|
||||
return signedPostWithContentType(body, nonce, MediaType.TEXT_PLAIN);
|
||||
}
|
||||
|
||||
private org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder signedPostWithContentType(
|
||||
String body,
|
||||
String nonce,
|
||||
MediaType contentType) throws Exception {
|
||||
String timestamp = Instant.now().toString();
|
||||
return post(ENDPOINT)
|
||||
.contentType(contentType)
|
||||
.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)
|
||||
.header("X-TH-Hotel-SuperAgent-Signature", signature(body, nonce, timestamp, CLIENT_ID));
|
||||
}
|
||||
|
||||
private org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder signedPostWithClientId(
|
||||
String body,
|
||||
String nonce,
|
||||
|
||||
Reference in New Issue
Block a user