|
|
@@ -0,0 +1,95 @@
|
|
|
+package com.etotem.cfc.service;
|
|
|
+
|
|
|
+import com.sun.net.httpserver.HttpServer;
|
|
|
+import org.junit.jupiter.api.AfterEach;
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
+import org.junit.jupiter.api.extension.ExtendWith;
|
|
|
+import org.mockito.Mock;
|
|
|
+import org.mockito.junit.jupiter.MockitoExtension;
|
|
|
+import org.springframework.test.util.ReflectionTestUtils;
|
|
|
+
|
|
|
+import java.io.IOException;
|
|
|
+import java.lang.reflect.Field;
|
|
|
+import java.net.InetSocketAddress;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+import static org.junit.jupiter.api.Assertions.*;
|
|
|
+import static org.mockito.ArgumentMatchers.any;
|
|
|
+import static org.mockito.ArgumentMatchers.isNull;
|
|
|
+import static org.mockito.Mockito.when;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 回归测试:LangGraph 服务挂起(accept 连接但永不响应)时,
|
|
|
+ * AiGateway 必须在 python.timeout-ms 内返回 null(走 mock 兜底),
|
|
|
+ * 而不是无限阻塞拖死后端线程。
|
|
|
+ *
|
|
|
+ * 背景:AiGateway 曾使用 `new RestTemplate()`(默认无限 connect/read 超时),
|
|
|
+ * 配置的 python.timeout-ms 从未应用,LangGraph 挂起时问卷生成永远转圈。
|
|
|
+ */
|
|
|
+@ExtendWith(MockitoExtension.class)
|
|
|
+class AiGatewayTimeoutTest {
|
|
|
+
|
|
|
+ private HttpServer server;
|
|
|
+ private AiGateway gateway;
|
|
|
+
|
|
|
+ @Mock
|
|
|
+ private PiisService piisService;
|
|
|
+
|
|
|
+ private void startHungServer() throws IOException {
|
|
|
+ server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
|
|
|
+ server.setExecutor(runnable -> {
|
|
|
+ Thread t = new Thread(runnable);
|
|
|
+ t.setDaemon(true);
|
|
|
+ t.start();
|
|
|
+ });
|
|
|
+ server.createContext("/", exchange -> {
|
|
|
+ // 不写响应,模拟挂起/卡死的上游服务
|
|
|
+ try {
|
|
|
+ Thread.sleep(TimeUnit.HOURS.toMillis(1));
|
|
|
+ } catch (InterruptedException ignored) {
|
|
|
+ }
|
|
|
+ });
|
|
|
+ server.start();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void buildGateway(int timeoutMs) throws Exception {
|
|
|
+ gateway = new AiGateway();
|
|
|
+ ReflectionTestUtils.setField(gateway, "enabled", true);
|
|
|
+ ReflectionTestUtils.setField(gateway, "baseUrl",
|
|
|
+ "http://127.0.0.1:" + server.getAddress().getPort());
|
|
|
+ ReflectionTestUtils.setField(gateway, "timeoutMs", timeoutMs);
|
|
|
+ ReflectionTestUtils.setField(gateway, "readTimeoutMs", timeoutMs);
|
|
|
+ ReflectionTestUtils.setField(gateway, "failureThreshold", 3);
|
|
|
+ ReflectionTestUtils.setField(gateway, "resetTimeoutMs", 30000);
|
|
|
+ when(piisService.sanitizeContext(any(), isNull())).thenAnswer(inv -> inv.getArgument(0));
|
|
|
+ ReflectionTestUtils.setField(gateway, "piisService", piisService);
|
|
|
+ gateway.init();
|
|
|
+ }
|
|
|
+
|
|
|
+ @AfterEach
|
|
|
+ void tearDown() {
|
|
|
+ if (server != null) server.stop(0);
|
|
|
+ if (gateway != null) gateway = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void generateQuestionnaire_upstreamHangs_returnsNullWithinTimeout() throws Exception {
|
|
|
+ startHungServer();
|
|
|
+ int timeoutMs = 500;
|
|
|
+ buildGateway(timeoutMs);
|
|
|
+
|
|
|
+ Map<String, Object> inputs = new HashMap<>();
|
|
|
+ inputs.put("member_name", "测试");
|
|
|
+ inputs.put("relationship_type", "child");
|
|
|
+
|
|
|
+ long start = System.currentTimeMillis();
|
|
|
+ Map<String, Object> result = gateway.generateQuestionnaire(inputs);
|
|
|
+ long elapsed = System.currentTimeMillis() - start;
|
|
|
+
|
|
|
+ assertNull(result, "上游挂起时应返回 null 触发 mock 兜底");
|
|
|
+ assertTrue(elapsed < TimeUnit.SECONDS.toMillis(5),
|
|
|
+ "应在 ~" + timeoutMs + "ms 超时返回,实际耗时 " + elapsed + "ms");
|
|
|
+ }
|
|
|
+}
|