package com.gxwebsoft.ai.config; import org.junit.jupiter.api.Test; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.UUID; import java.util.function.Consumer; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; /** * HTTP 流式通道(SSE)是生产上唯一能用的推送路径:网关在 {@code /api} 前丢掉 * WebSocket 的 {@code Upgrade} 头,WS 握手永远到不了 Tomcat。 * *

因此这张登记表的几个行为是硬约束:没人接时静默、有人接时逐字转发、 * 重复登记后者覆盖前者、写失败要把死掉的 sink 摘掉 * (否则后续消息会一直往已经断开的长连接里写)。 * *

每个用例用独立 userId,避免静态表互相串味,也就不需要清理。 */ class ChatStreamRegistryTest { private static String newUser() { return "test-" + UUID.randomUUID(); } @Test void 没有登记通道时返回false() { assertFalse(ChatStreamRegistry.emit(newUser(), "{}")); } @Test void 登记后能收到原文() { String user = newUser(); List received = new ArrayList<>(); ChatStreamRegistry.bind(user, received::add); assertTrue(ChatStreamRegistry.emit(user, "{\"answer\":\"你好\"}")); assertEquals(Collections.singletonList("{\"answer\":\"你好\"}"), received); } @Test void 重复登记时后者覆盖前者() { String user = newUser(); List first = new ArrayList<>(); List second = new ArrayList<>(); ChatStreamRegistry.bind(user, first::add); ChatStreamRegistry.bind(user, second::add); ChatStreamRegistry.emit(user, "x"); assertTrue(first.isEmpty(), "被覆盖的旧连接不该再收到消息"); assertEquals(Collections.singletonList("x"), second); } @Test void 解绑只摘自己的那个通道() { String user = newUser(); List first = new ArrayList<>(); List second = new ArrayList<>(); Consumer firstSink = first::add; ChatStreamRegistry.bind(user, firstSink); ChatStreamRegistry.bind(user, second::add); // 旧连接晚到的 finally 块:不能把新连接顶掉 ChatStreamRegistry.unbind(user, firstSink); assertTrue(ChatStreamRegistry.emit(user, "still-alive")); assertEquals(Collections.singletonList("still-alive"), second); } @Test void 写入失败后摘掉通道且不向上抛() { String user = newUser(); ChatStreamRegistry.bind(user, payload -> { throw new IllegalStateException("客户端已断开"); }); assertFalse(ChatStreamRegistry.emit(user, "boom")); // 失败过的 sink 已被摘掉,下一次 emit 直接返回 false assertFalse(ChatStreamRegistry.emit(user, "boom")); } @Test void 没有WebSocket会话时推送落到流式通道() throws IOException { String user = newUser(); List received = new ArrayList<>(); ChatStreamRegistry.bind(user, received::add); // 生产就是这样:SESSIONS 里没有这个用户,send 必须改投 SSE WebSocketServer.send(user, "{\"answer\":\"fallback\"}"); assertEquals(Collections.singletonList("{\"answer\":\"fallback\"}"), received); } }