238 lines
8.2 KiB
Plaintext
238 lines
8.2 KiB
Plaintext
/*
|
||||
|
|
* simcu::websocket.tests —— 端到端集成测试(cjpm test)。
|
|||
|
|
* 覆盖:文本回声 / 广播 / 关闭握手(客户端主动、服务端主动)/ terminate /
|
|||
|
|
* ping 心跳后连接仍可用 / 路径过滤 / maxPayload 超限(1009)。
|
|||
|
|
*
|
|||
|
|
* 运行:在 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.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()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Test
|
|||
|
|
public class WebSocketTest {
|
|||
|
|
/// 1. 文本回声:client.sendText → server 收到后回发 → client 收到
|
|||
|
|
@TestCase
|
|||
|
|
public func echoText(): Unit {
|
|||
|
|
let h = WsTestHarness()
|
|||
|
|
let server = WebSocketServer(bindAt: 0)
|
|||
|
|
server.on("connection", { conn: WebSocketConnection =>
|
|||
|
|
h.serverConn = Some(conn)
|
|||
|
|
conn.on("message", { m: WebSocketMessage =>
|
|||
|
|
h.connMessage = m.text.getOrThrow()
|
|||
|
|
conn.sendText("echo: ${m.text.getOrThrow()}")
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
server.listen()
|
|||
|
|
let client = WebSocketClient("127.0.0.1", server.localPort)
|
|||
|
|
client.onMessage = { m: WebSocketMessage =>
|
|||
|
|
h.clientMessage = m.text.getOrThrow()
|
|||
|
|
}
|
|||
|
|
client.connect()
|
|||
|
|
client.sendText("hello")
|
|||
|
|
|
|||
|
|
@Expect(waitUntil(5000) { => h.connMessage == Some("hello") }, true)
|
|||
|
|
@Expect(waitUntil(5000) { => h.clientMessage == Some("echo: hello") }, true)
|
|||
|
|
client.close()
|
|||
|
|
server.close()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 2. 广播:server.broadcastText 两个客户端都收到
|
|||
|
|
@TestCase
|
|||
|
|
public func broadcast(): Unit {
|
|||
|
|
let h1 = WsTestHarness()
|
|||
|
|
let h2 = WsTestHarness()
|
|||
|
|
let server = WebSocketServer(bindAt: 0)
|
|||
|
|
server.listen()
|
|||
|
|
|
|||
|
|
let c1 = WebSocketClient("127.0.0.1", server.localPort)
|
|||
|
|
c1.onMessage = { m: WebSocketMessage =>
|
|||
|
|
h1.clientMessage = m.text.getOrThrow()
|
|||
|
|
}
|
|||
|
|
c1.connect()
|
|||
|
|
let c2 = WebSocketClient("127.0.0.1", server.localPort)
|
|||
|
|
c2.onMessage = { m: WebSocketMessage =>
|
|||
|
|
h2.clientMessage = m.text.getOrThrow()
|
|||
|
|
}
|
|||
|
|
c2.connect()
|
|||
|
|
@Expect(waitUntil(5000) { => server.connectionCount == 2 }, true)
|
|||
|
|
|
|||
|
|
server.broadcastText("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(bindAt: 0)
|
|||
|
|
server.on("connection", { conn: WebSocketConnection =>
|
|||
|
|
conn.on("close", { code: Int64, reason: String =>
|
|||
|
|
h.connCloseCode = code
|
|||
|
|
h.connCloseReason = reason
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
server.listen()
|
|||
|
|
let client = WebSocketClient("127.0.0.1", server.localPort)
|
|||
|
|
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(bindAt: 0)
|
|||
|
|
server.on("connection", { conn: WebSocketConnection =>
|
|||
|
|
h.serverConn = Some(conn)
|
|||
|
|
conn.close(code: 4000, reason: "svr")
|
|||
|
|
})
|
|||
|
|
server.listen()
|
|||
|
|
let client = WebSocketClient("127.0.0.1", server.localPort)
|
|||
|
|
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. terminate:client.terminate() 立即断开 → server conn onClose 收 (1006, "")
|
|||
|
|
@TestCase
|
|||
|
|
public func terminate(): Unit {
|
|||
|
|
let h = WsTestHarness()
|
|||
|
|
let server = WebSocketServer(bindAt: 0)
|
|||
|
|
server.on("connection", { conn: WebSocketConnection =>
|
|||
|
|
conn.on("close", { code: Int64, reason: String =>
|
|||
|
|
h.connCloseCode = code
|
|||
|
|
h.connCloseReason = reason
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
server.listen()
|
|||
|
|
let client = WebSocketClient("127.0.0.1", server.localPort)
|
|||
|
|
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(bindAt: 0)
|
|||
|
|
server.on("connection", { conn: WebSocketConnection =>
|
|||
|
|
conn.on("message", { m: WebSocketMessage =>
|
|||
|
|
h.connMessage = m.text.getOrThrow()
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
server.listen()
|
|||
|
|
let client = WebSocketClient("127.0.0.1", server.localPort)
|
|||
|
|
client.connect()
|
|||
|
|
|
|||
|
|
client.ping()
|
|||
|
|
sleep(Duration.millisecond * 100)
|
|||
|
|
client.sendText("after-ping")
|
|||
|
|
@Expect(waitUntil(5000) { => h.connMessage == Some("after-ping") }, true)
|
|||
|
|
client.close()
|
|||
|
|
server.close()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 7. 路径过滤:path=Some("/ws") 时 /ws 握手成功,/other 被拒(connect 抛异常)
|
|||
|
|
@TestCase
|
|||
|
|
public func pathFilter(): Unit {
|
|||
|
|
let server = WebSocketServer(bindAt: 0, path: Some("/ws"))
|
|||
|
|
server.listen()
|
|||
|
|
|
|||
|
|
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. maxPayload:server 限制 10 字节,client 发长文本 → server 连接 onError 收 code=1009
|
|||
|
|
@TestCase
|
|||
|
|
public func maxPayload(): Unit {
|
|||
|
|
let h = WsTestHarness()
|
|||
|
|
let server = WebSocketServer(bindAt: 0, maxPayload: 10)
|
|||
|
|
server.on("connection", { conn: WebSocketConnection =>
|
|||
|
|
conn.on("error", { e: Exception =>
|
|||
|
|
if (let we: WebSocketException <- e) {
|
|||
|
|
h.connErrorCode = we.code
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
server.listen()
|
|||
|
|
let client = WebSocketClient("127.0.0.1", server.localPort)
|
|||
|
|
client.onError = { e: Exception =>
|
|||
|
|
h.clientErrorCount += 1
|
|||
|
|
}
|
|||
|
|
client.connect()
|
|||
|
|
client.sendText("this message is way too long")
|
|||
|
|
|
|||
|
|
@Expect(waitUntil(5000) { => h.connErrorCode == 1009 }, true)
|
|||
|
|
client.terminate()
|
|||
|
|
server.close()
|
|||
|
|
}
|
|||
|
|
}
|