增强Debug EML SSE诊断与心跳

This commit is contained in:
andy
2026-07-12 17:43:20 +08:00
parent 5d82b29c53
commit 937aa569ff
10 changed files with 237 additions and 6 deletions

View File

@@ -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();