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

379 lines
13 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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(_: WebSocketConnection, code: Int64, reason: String): Unit {
harness.connCloseCode = code
harness.connCloseReason = reason
}
public override func onError(_: 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(_: WebSocketConnection): Unit {
()
}
public override func onMessage(_: WebSocketConnection, msg: WebSocketMessage): Unit {
harness.connMessage = msg.text.getOrThrow()
}
public override func onClose(_: WebSocketConnection, _: Int64, _: String): Unit {
()
}
public override func onError(_: WebSocketConnection, _: Exception): Unit {
()
}
}
/// 服务端主动关闭场景:连接建立后立即 close。
class CloseOnConnectHandler <: IWebsocketHandler {
init() {}
public override func onConnect(conn: WebSocketConnection): Unit {
conn.close(code: 4000, reason: "svr")
}
public override func onMessage(_: WebSocketConnection, _: WebSocketMessage): Unit {
()
}
public override func onClose(_: WebSocketConnection, _: Int64, _: String): Unit {
()
}
public override func onError(_: WebSocketConnection, _: Exception): Unit {
()
}
}
/// 分组场景:连接建立后加入 "room" 组,便于验证组播/单播。
class GroupJoinHandler <: IWebsocketHandler {
public override func onConnect(conn: WebSocketConnection): Unit {
conn.joinGroup("room")
}
public override func onMessage(_: WebSocketConnection, _: WebSocketMessage): Unit {
()
}
public override func onClose(_: WebSocketConnection, _: Int64, _: String): Unit {
()
}
public override func onError(_: WebSocketConnection, _: 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()
}
}