增强Debug EML SSE诊断与心跳
This commit is contained in:
@@ -23,6 +23,7 @@ import cn.nianxx.thhotel.integrations.storage.aliyunoss.common.request.ObjectSto
|
||||
import cn.nianxx.thhotel.integrations.storage.aliyunoss.common.result.ObjectStoragePutResult;
|
||||
import cn.nianxx.thhotel.integrations.storage.aliyunoss.service.ObjectStorageService;
|
||||
import cn.nianxx.thhotel.platform.debug.service.DebugEmlSuperAgentRunService;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -30,10 +31,13 @@ import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
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.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.boot.test.system.CapturedOutput;
|
||||
import org.springframework.boot.test.system.OutputCaptureExtension;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
@@ -48,12 +52,14 @@ import org.springframework.web.client.RestClientResponseException;
|
||||
"debug.eml-upload.enabled=true",
|
||||
"debug.eml-upload.access-key=test-debug-upload-key",
|
||||
"debug.eml-upload.max-file-bytes=1048576",
|
||||
"debug.eml-upload.sse-heartbeat-interval=25ms",
|
||||
"aliyun.oss.debug-eml-prefix=debug/eml/",
|
||||
"superagent.open-api.enabled=true",
|
||||
"superagent.open-api.external-subject-id=test-debug-eml"
|
||||
})
|
||||
@AutoConfigureMockMvc
|
||||
@ActiveProfiles("test")
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
class DebugEmlSuperAgentControllerTest {
|
||||
|
||||
private static final String ENDPOINT = "/api/system/debug/eml-superagent-runs";
|
||||
@@ -254,6 +260,50 @@ class DebugEmlSuperAgentControllerTest {
|
||||
.andExpect(content().string(not(containsString("test-debug-upload-key"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSendHeartbeatWhileWaitingForSuperAgentStreamResult() 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(), any())).thenAnswer(invocation -> {
|
||||
Thread.sleep(120);
|
||||
return new SuperAgentOpenApiResult(
|
||||
"session-heartbeat-001",
|
||||
"run-heartbeat-001",
|
||||
"profile-debug",
|
||||
"profile-version-debug",
|
||||
"debug-model",
|
||||
"{\"ai_task_results\":[{\"task_type\":\"New Booking\"}]}",
|
||||
11,
|
||||
7,
|
||||
18,
|
||||
List.of("metadata", "values", "end"),
|
||||
List.of());
|
||||
});
|
||||
|
||||
MvcResult mvcResult = mockMvc.perform(multipart(ENDPOINT + "/stream")
|
||||
.file(emlFile())
|
||||
.param("hotel_id", "HOTEL-TEST")
|
||||
.param("run_label", "stream-heartbeat")
|
||||
.header("X-TH-Hotel-Debug-Upload-Key", "test-debug-upload-key"))
|
||||
.andExpect(request().asyncStarted())
|
||||
.andReturn();
|
||||
|
||||
mockMvc.perform(asyncDispatch(mvcResult))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_EVENT_STREAM))
|
||||
.andExpect(content().string(containsString("event: debug_heartbeat")))
|
||||
.andExpect(content().string(containsString("\"status\":\"CALLING_SUPERAGENT\"")))
|
||||
.andExpect(content().string(containsString("event: superagent_result")))
|
||||
.andExpect(content().string(containsString("event: done")))
|
||||
.andExpect(content().string(not(containsString("test-debug-upload-key"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldKeepBusinessRunStatusWhenSseClientDisconnects() {
|
||||
when(objectStorageService.putObject(any())).thenAnswer(invocation -> {
|
||||
@@ -313,6 +363,36 @@ class DebugEmlSuperAgentControllerTest {
|
||||
org.assertj.core.api.Assertions.assertThat(debugRunCount).isEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldLogSafeRootCauseWhenSuperAgentSseReadFails(CapturedOutput output) {
|
||||
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(), any()))
|
||||
.thenThrow(new SuperAgentOpenApiException(
|
||||
"SuperAgent SSE 读取失败。",
|
||||
new IOException("simulated stream reset Authorization: Bearer should-not-log")));
|
||||
|
||||
runService.uploadAndRunStream(
|
||||
"test-debug-upload-key",
|
||||
emlFile(),
|
||||
"HOTEL-TEST",
|
||||
"stream-read-failed-log",
|
||||
new ByteArrayOutputStream());
|
||||
|
||||
org.assertj.core.api.Assertions.assertThat(output.getOut())
|
||||
.contains("Debug EML SuperAgent Open API failed")
|
||||
.contains("SuperAgentOpenApiException: SuperAgent SSE 读取失败。")
|
||||
.contains("IOException: simulated stream reset")
|
||||
.doesNotContain("should-not-log")
|
||||
.doesNotContain("Bearer should-not-log");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRecordPhaseBeforeUploadingOriginalEmlToOss() throws Exception {
|
||||
AtomicInteger uploadIndex = new AtomicInteger();
|
||||
|
||||
Reference in New Issue
Block a user