-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsession_test.go
More file actions
491 lines (411 loc) · 11.9 KB
/
session_test.go
File metadata and controls
491 lines (411 loc) · 11.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
package websocket
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/gorilla/websocket"
)
// TestNewSession 测试创建 Session
func TestNewSession(t *testing.T) {
hub := NewHubRun()
defer hub.Close()
session := NewSession(hub)
if session == nil {
t.Fatal("NewSession returned nil")
}
// 验证 ID 已生成
if session.ID() == "" {
t.Error("Session ID should be generated")
}
}
// TestNewSessionWithOptions 测试带选项创建 Session
func TestNewSessionWithOptions(t *testing.T) {
hub := NewHubRun()
defer hub.Close()
session := NewSession(hub,
WithSessionID("custom-id"),
WithSessionBufSize(512),
)
if session.ID() != "custom-id" {
t.Errorf("ID = %v, want custom-id", session.ID())
}
}
// TestSessionInheritsHubConfig 测试 Session 继承 Hub 配置
func TestSessionInheritsHubConfig(t *testing.T) {
hub := NewHubRunWithConfig(
WithWriteWait(5*time.Second),
WithPongWait(30*time.Second),
WithPingPeriod(27*time.Second), // PingPeriod 必须小于 PongWait
)
defer hub.Close()
session := NewSession(hub)
cfg := session.config
if cfg.WriteWait != 5*time.Second {
t.Errorf("WriteWait = %v, want 5s", cfg.WriteWait)
}
if cfg.PongWait != 30*time.Second {
t.Errorf("PongWait = %v, want 30s", cfg.PongWait)
}
}
// TestSessionConfigOverride 测试 Session 配置覆盖
func TestSessionConfigOverride(t *testing.T) {
hub := NewHubRunWithConfig(WithWriteWait(10 * time.Second))
defer hub.Close()
session := NewSession(hub,
WithSessionWriteWait(3*time.Second),
)
cfg := session.config
if cfg.WriteWait != 3*time.Second {
t.Errorf("WriteWait = %v, want 3s (override)", cfg.WriteWait)
}
// Hub 配置不应被修改
hubCfg := hub.Config()
if hubCfg.WriteWait != 10*time.Second {
t.Errorf("Hub WriteWait = %v, want 10s (unchanged)", hubCfg.WriteWait)
}
}
// TestSessionStandaloneMode 测试单节点模式(无 storage)
func TestSessionStandaloneMode(t *testing.T) {
hub := NewHubRun()
defer hub.Close()
session := NewSession(hub, WithSessionID("standalone-test"))
// 验证 storage 为 nil
if session.storage != nil {
t.Error("Standalone mode should have nil storage")
}
}
// TestSessionDistributedMode 测试分布式模式(有 storage)
func TestSessionDistributedMode(t *testing.T) {
hub := NewHubRun()
defer hub.Close()
storage := NewMockStorage()
session := NewSession(hub,
WithStorage(storage),
WithAddr("192.168.1.1:8080"),
WithSessionID("dist-test"),
)
// 验证 storage 已设置
if session.storage != storage {
t.Error("Distributed mode should have storage")
}
if session.addr != "192.168.1.1:8080" {
t.Errorf("addr = %v, want 192.168.1.1:8080", session.addr)
}
}
// TestSessionConn 测试 Session.Conn 建立 WebSocket 连接
func TestSessionConn(t *testing.T) {
hub := NewHubRun()
defer hub.Close()
// 创建测试服务器
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
conn, err := hub.Upgrader().Upgrade(w, r, nil)
if err != nil {
t.Logf("Upgrade error: %v", err)
return
}
defer conn.Close()
for {
_, _, err := conn.ReadMessage()
if err != nil {
return
}
}
}))
defer server.Close()
session := NewSession(hub, WithSessionID("conn-test"))
// 创建 HTTP 请求
wsURL := "ws" + strings.TrimPrefix(server.URL, "http")
// 需要用 websocket dialer 来测试 Conn
// 这里测试 Session.Conn 的基本流程
wsConn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
if err != nil {
t.Fatalf("Failed to dial: %v", err)
}
defer wsConn.Close()
// 手动设置连接(模拟 Conn 方法)
session.client.conn = wsConn
hub.register <- session.client
// 等待注册
time.Sleep(50 * time.Millisecond)
// 验证客户端已注册
if _, ok := hub.Client(session.ID()); !ok {
t.Error("Session client was not registered")
}
}
// TestSessionOnConnect 测试 OnConnect 回调
func TestSessionOnConnect(t *testing.T) {
hub := NewHubRun()
defer hub.Close()
connectCalled := false
session := NewSession(hub)
session.OnConnect(func(conn *Client) {
connectCalled = true
})
// 手动触发回调
if session.client.onConnect != nil {
session.client.onConnect(session.client)
}
if !connectCalled {
t.Error("OnConnect callback was not called")
}
}
// TestSessionOnDisconnect 测试 OnDisconnect 回调
func TestSessionOnDisconnect(t *testing.T) {
hub := NewHubRun()
defer hub.Close()
storage := NewMockStorage()
_ = storage.Set("disconnect-test", "192.168.1.1:8080")
session := NewSession(hub,
WithStorage(storage),
WithAddr("192.168.1.1:8080"),
WithSessionID("disconnect-test"),
)
disconnectCalled := false
session.OnDisconnect(func(id string) {
disconnectCalled = true
})
// 触发断开回调
if session.client.onDisconnect != nil {
session.client.onDisconnect("disconnect-test")
}
if !disconnectCalled {
t.Error("OnDisconnect callback was not called")
}
// 验证存储中的条目被删除
val, _ := storage.Get("disconnect-test")
if val != "" {
t.Error("Storage entry should be deleted on disconnect")
}
}
// TestSessionEmit 测试 Session.Emit
func TestSessionEmit(t *testing.T) {
hub := NewHubRun()
defer hub.Close()
session := NewSession(hub)
// 创建测试服务器
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
conn, err := hub.Upgrader().Upgrade(w, r, nil)
if err != nil {
return
}
defer conn.Close()
for {
_, _, err := conn.ReadMessage()
if err != nil {
return
}
}
}))
defer server.Close()
wsURL := "ws" + strings.TrimPrefix(server.URL, "http")
wsConn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
if err != nil {
t.Fatalf("Failed to dial: %v", err)
}
defer wsConn.Close()
session.client.conn = wsConn
// 测试发送
if !session.Emit([]byte("test message")) {
t.Error("Emit should return true")
}
}
// TestSessionBroadcast 测试 Session.Broadcast
func TestSessionBroadcast(t *testing.T) {
hub := NewHubRun()
defer hub.Close()
session := NewSession(hub)
// Broadcast 应该调用 Hub.Broadcast
session.Broadcast([]byte("broadcast message"))
// 验证通过 - 没有 panic 即可
}
// TestSessionClient 测试 Session.Client()
func TestSessionClient(t *testing.T) {
hub := NewHubRun()
defer hub.Close()
session := NewSession(hub)
client := session.Client()
if client == nil {
t.Fatal("Client() returned nil")
}
if client != session.client {
t.Error("Client() should return the internal client")
}
}
// TestNewClientWithDefaultBufSize 测试 newClient 使用默认 bufSize
// 当 bufSize <= 0 时,应使用 Hub 的默认配置
func TestNewClientWithDefaultBufSize(t *testing.T) {
hub := NewHubRunWithConfig(WithHubBufSize(128))
defer hub.Close()
// 测试 bufSize = 0 时使用 Hub 配置
client := newClient(hub, "test-zero-bufsize", 0)
if client == nil {
t.Fatal("newClient returned nil")
}
if client.hub != hub {
t.Error("client hub mismatch")
}
if client.id != "test-zero-bufsize" {
t.Errorf("client id = %s, want test-zero-bufsize", client.id)
}
// 验证 send channel 已创建(使用 Hub 的 BufSize)
if client.send == nil {
t.Error("client send channel should not be nil")
}
// 测试 bufSize = -1 时使用 Hub 配置
client2 := newClient(hub, "test-negative-bufsize", -1)
if client2 == nil {
t.Fatal("newClient with negative bufSize returned nil")
}
if client2.send == nil {
t.Error("client2 send channel should not be nil")
}
// 测试自定义 bufSize
client3 := newClient(hub, "test-custom-bufsize", 64)
if client3 == nil {
t.Fatal("newClient with custom bufSize returned nil")
}
if client3.send == nil {
t.Error("client3 send channel should not be nil")
}
}
// TestSessionWithZeroBufSize 测试 Session 使用零 bufSize 配置
func TestSessionWithZeroBufSize(t *testing.T) {
hub := NewHubRunWithConfig(WithHubBufSize(100))
defer hub.Close()
// 使用零 bufSize(会使用 Hub 的配置)
session := NewSession(hub, WithSessionBufSize(0))
if session == nil {
t.Fatal("NewSession returned nil")
}
if session.client == nil {
t.Fatal("session.client is nil")
}
// 验证 client 的 send channel 已创建
if session.client.send == nil {
t.Error("client send channel should not be nil")
}
}
// TestSessionConnUpgradeError 测试 Session.Conn 升级失败
func TestSessionConnUpgradeError(t *testing.T) {
hub := NewHubRun()
defer hub.Close()
session := NewSession(hub, WithSessionID("upgrade-error-test"))
// 创建无效的 HTTP 请求(不是 WebSocket 升级请求)
req := httptest.NewRequest("GET", "http://example.com", nil)
w := httptest.NewRecorder()
// Conn 应该返回升级错误
err := session.Conn(w, req)
if err == nil {
t.Error("Conn() should return error for non-websocket request")
}
if !strings.Contains(err.Error(), "websocket upgrade") {
t.Errorf("Conn() error should contain 'websocket upgrade', got: %v", err)
}
}
// TestSessionConnDistributedModeStorageError 测试 Session.Conn 分布式模式下存储失败
func TestSessionConnDistributedModeStorageError(t *testing.T) {
hub := NewHubRun()
defer hub.Close()
// 使用会返回错误的 storage
storage := &errorStorage{}
session := NewSession(hub,
WithStorage(storage),
WithAddr("192.168.1.1:8080"),
WithSessionID("storage-error-test"),
)
errorCalled := make(chan error, 1)
session.OnError(func(id string, err error) {
errorCalled <- err
})
// 创建测试服务器
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
conn, err := hub.Upgrader().Upgrade(w, r, nil)
if err != nil {
return
}
defer conn.Close()
// 保持连接
for {
_, _, err := conn.ReadMessage()
if err != nil {
return
}
}
}))
defer server.Close()
wsURL := "ws" + strings.TrimPrefix(server.URL, "http")
wsConn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
if err != nil {
t.Fatalf("Failed to dial: %v", err)
}
defer wsConn.Close()
// 手动设置连接(模拟 Conn 方法的内部逻辑)
session.client.conn = wsConn
session.client.onConnect = func(c *Client) {
// 分布式模式:存储失败应该触发 OnError 回调
if session.storage != nil {
if err := session.storage.Set(c.id, session.addr); err != nil {
c.error(err)
}
}
}
hub.register <- session.client
// 手动触发 OnConnect 来测试存储错误处理
if session.client.onConnect != nil {
session.client.onConnect(session.client)
}
// 验证 OnError 回调被调用(存储失败)
select {
case err := <-errorCalled:
if err == nil {
t.Error("OnError should be called with storage error")
}
case <-time.After(500 * time.Millisecond):
t.Error("OnError should be called when storage fails")
}
}
// TestSessionOnDisconnectStorageError 测试 OnDisconnect 存储错误处理
func TestSessionOnDisconnectStorageError(t *testing.T) {
hub := NewHubRun()
defer hub.Close()
// 使用会返回错误的 storage
storage := &errorStorage{}
session := NewSession(hub,
WithStorage(storage),
WithAddr("192.168.1.1:8080"),
WithSessionID("disconnect-storage-error-test"),
)
errorCalled := make(chan error, 1)
session.OnError(func(id string, err error) {
errorCalled <- err
})
disconnectCalled := make(chan string, 1)
session.OnDisconnect(func(id string) {
disconnectCalled <- id
})
// 触发断开回调
if session.client.onDisconnect != nil {
session.client.onDisconnect("disconnect-storage-error-test")
}
// 验证 OnDisconnect 回调被调用
select {
case id := <-disconnectCalled:
if id != "disconnect-storage-error-test" {
t.Errorf("disconnect id = %v, want 'disconnect-storage-error-test'", id)
}
case <-time.After(500 * time.Millisecond):
t.Error("OnDisconnect callback was not called")
}
// 验证 OnError 回调被调用(存储删除失败)
select {
case err := <-errorCalled:
if err == nil {
t.Error("OnError should be called with storage delete error")
}
case <-time.After(500 * time.Millisecond):
// 存储错误应该被记录
t.Log("OnError may or may not be called depending on errorStorage behavior")
}
}