Files
websocket-cj/src/tests/websocket_test.cj
T

379 lines
13 KiB
Plaintext
Raw Normal View History

/*
* simcu::websocket.tests —— 端到端集成测试(cjpm test)。
* 覆盖:文本回声 / 广播 / 关闭握手(客户端主动、服务端主动)/ terminate /
* ping 心跳后连接仍可用 / 路径路由(多 handler 按注册路径)/ maxPayload 超限(1009) /
* 组播(group.send/ 单播(conn.send)。
*
* 运行:在 websocket-cj 目录执行 cjpm test
*/
package simcu::websocket.tests
import std.time.*
import std.unittest.*
import std.unittest.testmacro.Expect
import std.unittest.testmacro.TestCase
import std.unittest.testmacro.Test
import simcu::websocket.client.WebSocketClient
import simcu::websocket.common.MessageType
import simcu::websocket.common.WebSocketException
import simcu::websocket.common.WebSocketMessage
import simcu::websocket.server.IWebsocketHandler
import simcu::websocket.server.WebSocketConnection
import simcu::websocket.server.WebSocketServer
/// 测试回收集合:把回调中捕获的值存到独立实例字段,避免闭包捕获歧义。
class WsTestHarness {
var serverConn: ?WebSocketConnection = None
var connMessage: ?String = None
var connErrorCode: Int64 = 0
var connCloseCode: Int64 = -1
var connCloseReason: String = ""
var clientMessage: ?String = None
var clientCloseCode: Int64 = -1
var clientCloseReason: String = ""
var clientErrorCount: Int64 = 0
}
/// 轮询等待条件成立(每 50ms 检查一次)。
private func waitUntil(timeoutMs: Int64, condition: () -> Bool): Bool {
let total = timeoutMs / 50
for (_ in 0..total) {
if (condition()) {
return true
}
sleep(Duration.millisecond * 50)
}
condition()
}
/// 测试用 handler:把事件回调逻辑收进接口方法,行为写入 harness。
class TestWsHandler <: IWebsocketHandler {
let harness: WsTestHarness
init(harness: WsTestHarness) {
this.harness = harness
}
public override func onConnect(conn: WebSocketConnection): Unit {
harness.serverConn = Some(conn)
}
public override func onMessage(conn: WebSocketConnection, msg: WebSocketMessage): Unit {
harness.connMessage = msg.text.getOrThrow()
conn.send("echo: ${msg.text.getOrThrow()}")
}
public override func onClose(conn: WebSocketConnection, code: Int64, reason: String): Unit {
harness.connCloseCode = code
harness.connCloseReason = reason
}
public override func onError(conn: WebSocketConnection, e: Exception): Unit {
if (let we: WebSocketException <- e) {
harness.connErrorCode = we.code
}
}
}
/// 广播场景:harness1 收到广播后转发文本。
class EchoHandler <: IWebsocketHandler {
let harness: WsTestHarness
init(harness: WsTestHarness) {
this.harness = harness
}
public override func onConnect(conn: WebSocketConnection): Unit {
()
}
public override func onMessage(conn: WebSocketConnection, msg: WebSocketMessage): Unit {
harness.connMessage = msg.text.getOrThrow()
}
public override func onClose(conn: WebSocketConnection, code: Int64, reason: String): Unit {
()
}
public override func onError(conn: WebSocketConnection, e: Exception): Unit {
()
}
}
/// 服务端主动关闭场景:连接建立后立即 close。
class CloseOnConnectHandler <: IWebsocketHandler {
init() {}
public override func onConnect(conn: WebSocketConnection): Unit {
conn.close(code: 4000, reason: "svr")
}
public override func onMessage(conn: WebSocketConnection, msg: WebSocketMessage): Unit {
()
}
public override func onClose(conn: WebSocketConnection, code: Int64, reason: String): Unit {
()
}
public override func onError(conn: WebSocketConnection, e: Exception): Unit {
()
}
}
/// 分组场景:连接建立后加入 "room" 组,便于验证组播/单播。
class GroupJoinHandler <: IWebsocketHandler {
public override func onConnect(conn: WebSocketConnection): Unit {
conn.joinGroup("room")
}
public override func onMessage(conn: WebSocketConnection, msg: WebSocketMessage): Unit {
()
}
public override func onClose(conn: WebSocketConnection, code: Int64, reason: String): Unit {
()
}
public override func onError(conn: WebSocketConnection, e: Exception): Unit {
()
}
}
@Test
public class WebSocketTest {
/// 1. 文本回声:client.send → server 收到后回发 → client 收到
@TestCase
public func echoText(): Unit {
let h = WsTestHarness()
let server = WebSocketServer(0)
server.addHandler("/ws") { => TestWsHandler(h) }
server.start()
let client = WebSocketClient("127.0.0.1", server.localPort, path: "/ws")
client.onMessage = { m: WebSocketMessage =>
h.clientMessage = m.text.getOrThrow()
}
client.connect()
client.send("hello")
@Expect(waitUntil(5000) { => h.connMessage == Some("hello") }, true)
@Expect(waitUntil(5000) { => h.clientMessage == Some("echo: hello") }, true)
client.close()
server.close()
}
/// 2. 广播:server.broadcast 两个客户端都收到
@TestCase
public func broadcast(): Unit {
let h1 = WsTestHarness()
let h2 = WsTestHarness()
let server = WebSocketServer(0)
server.addHandler("/ws") { => EchoHandler(h1) }
server.start()
let c1 = WebSocketClient("127.0.0.1", server.localPort, path: "/ws")
c1.onMessage = { m: WebSocketMessage =>
h1.clientMessage = m.text.getOrThrow()
}
c1.connect()
let c2 = WebSocketClient("127.0.0.1", server.localPort, path: "/ws")
c2.onMessage = { m: WebSocketMessage =>
h2.clientMessage = m.text.getOrThrow()
}
c2.connect()
@Expect(waitUntil(5000) { => server.connectionCount == 2 }, true)
server.broadcast("hi-all")
@Expect(waitUntil(5000) { => h1.clientMessage == Some("hi-all") }, true)
@Expect(waitUntil(5000) { => h2.clientMessage == Some("hi-all") }, true)
c1.close()
c2.close()
server.close()
}
/// 3. 关闭握手:client.close(1000, "bye") → 双端 onClose 都收到 (1000, "bye")
@TestCase
public func closeHandshake(): Unit {
let h = WsTestHarness()
let server = WebSocketServer(0)
server.addHandler("/ws") { => TestWsHandler(h) }
server.start()
let client = WebSocketClient("127.0.0.1", server.localPort, path: "/ws")
client.onClose = { code: Int64, reason: String =>
h.clientCloseCode = code
h.clientCloseReason = reason
}
client.connect()
client.close(code: 1000, reason: "bye")
@Expect(waitUntil(5000) { => h.clientCloseCode == 1000 && h.clientCloseReason == "bye" }, true)
@Expect(waitUntil(5000) { => h.connCloseCode == 1000 && h.connCloseReason == "bye" }, true)
server.close()
}
/// 4. 服务端主动关闭:conn.close(4000, "svr") → client onClose 收 (4000, "svr")
@TestCase
public func serverClose(): Unit {
let h = WsTestHarness()
let server = WebSocketServer(0)
server.addHandler("/ws") { => CloseOnConnectHandler() }
server.start()
let client = WebSocketClient("127.0.0.1", server.localPort, path: "/ws")
client.onClose = { code: Int64, reason: String =>
h.clientCloseCode = code
h.clientCloseReason = reason
}
client.connect()
@Expect(waitUntil(5000) { => h.clientCloseCode == 4000 && h.clientCloseReason == "svr" }, true)
server.close()
}
/// 5. terminateclient.terminate() 立即断开 → server conn onClose 收 (1006, "")
@TestCase
public func terminate(): Unit {
let h = WsTestHarness()
let server = WebSocketServer(0)
server.addHandler("/ws") { => TestWsHandler(h) }
server.start()
let client = WebSocketClient("127.0.0.1", server.localPort, path: "/ws")
client.connect()
client.terminate()
@Expect(waitUntil(5000) { => h.connCloseCode == 1006 && h.connCloseReason == "" }, true)
server.close()
}
/// 6. ping:心跳后连接仍可用,后续消息正常到达
@TestCase
public func ping(): Unit {
let h = WsTestHarness()
let server = WebSocketServer(0)
server.addHandler("/ws") { => TestWsHandler(h) }
server.start()
let client = WebSocketClient("127.0.0.1", server.localPort, path: "/ws")
client.connect()
client.ping()
sleep(Duration.millisecond * 100)
client.send("after-ping")
@Expect(waitUntil(5000) { => h.connMessage == Some("after-ping") }, true)
client.close()
server.close()
}
/// 7. 路径路由:注册 /ws 与 /admin 两个 handler/ws 握手成功,/other 被拒(connect 抛异常)
@TestCase
public func pathRoute(): Unit {
let server = WebSocketServer(0)
server.addHandler("/ws") { => TestWsHandler(WsTestHarness()) }
server.addHandler("/admin") { => TestWsHandler(WsTestHarness()) }
server.start()
let ok = WebSocketClient("127.0.0.1", server.localPort, path: "/ws")
ok.connect()
ok.close()
let bad = WebSocketClient("127.0.0.1", server.localPort, path: "/other")
var rejected = false
try {
bad.connect()
} catch (e: WebSocketException) {
rejected = true
}
@Expect(rejected, true)
server.close()
}
/// 8. maxPayloadserver 限制 10 字节,client 发长文本 → server 连接 onError 收 code=1009
@TestCase
public func maxPayload(): Unit {
let h = WsTestHarness()
let server = WebSocketServer(0, maxPayload: 10)
server.addHandler("/ws") { => TestWsHandler(h) }
server.start()
let client = WebSocketClient("127.0.0.1", server.localPort, path: "/ws")
client.onError = { e: Exception =>
h.clientErrorCount += 1
}
client.connect()
client.send("this message is way too long")
@Expect(waitUntil(5000) { => h.connErrorCode == 1009 }, true)
client.terminate()
server.close()
}
/// 9. 组播:两个连接加入同组("room"),group("room").send 两连接都收到;关闭一个后组计数减一
@TestCase
public func groupMulticast(): Unit {
let h1 = WsTestHarness()
let h2 = WsTestHarness()
let server = WebSocketServer(0)
server.addHandler("/ws") { => GroupJoinHandler() }
server.start()
let c1 = WebSocketClient("127.0.0.1", server.localPort, path: "/ws")
c1.onMessage = { m: WebSocketMessage =>
h1.clientMessage = m.text.getOrThrow()
}
c1.connect()
let c2 = WebSocketClient("127.0.0.1", server.localPort, path: "/ws")
c2.onMessage = { m: WebSocketMessage =>
h2.clientMessage = m.text.getOrThrow()
}
c2.connect()
@Expect(waitUntil(5000) { => server.group("room").count() == 2 }, true)
server.group("room").send("team-msg")
@Expect(waitUntil(5000) { => h1.clientMessage == Some("team-msg") }, true)
@Expect(waitUntil(5000) { => h2.clientMessage == Some("team-msg") }, true)
// 关闭一个连接后自动退组,组计数减一
c1.close()
@Expect(waitUntil(5000) { => server.group("room").count() == 1 }, true)
server.group("room").send("after-leave")
@Expect(waitUntil(5000) { => h2.clientMessage == Some("after-leave") }, true)
c2.close()
server.close()
}
/// 10. 单播:conn(connId).send 只发到指定连接Id,另一连接收不到
@TestCase
public func unicast(): Unit {
let h1 = WsTestHarness()
let h2 = WsTestHarness()
let server = WebSocketServer(0)
server.addHandler("/ws") { => GroupJoinHandler() }
server.start()
let c1 = WebSocketClient("127.0.0.1", server.localPort, path: "/ws")
c1.onMessage = { m: WebSocketMessage =>
h1.clientMessage = m.text.getOrThrow()
}
c1.connect()
let c2 = WebSocketClient("127.0.0.1", server.localPort, path: "/ws")
c2.onMessage = { m: WebSocketMessage =>
h2.clientMessage = m.text.getOrThrow()
}
c2.connect()
@Expect(waitUntil(5000) { => server.group("room").connIds().size == 2 }, true)
h1.clientMessage = None
h2.clientMessage = None
let ids = server.group("room").connIds()
let target = ids[0]
server.conn(target).send("only-you")
@Expect(waitUntil(5000) { => h1.clientMessage == Some("only-you") || h2.clientMessage == Some("only-you") }, true)
// 稍等片刻后断言:仅目标接收,另一连接未收到
sleep(Duration.millisecond * 200)
let exactlyOne = (h1.clientMessage == Some("only-you") && h2.clientMessage != Some("only-you")) ||
(h2.clientMessage == Some("only-you") && h1.clientMessage != Some("only-you"))
@Expect(exactlyOne, true)
c1.close()
c2.close()
server.close()
}
}