109 lines
3.1 KiB
Go
109 lines
3.1 KiB
Go
package sse
|
||
|
||
import (
|
||
"net/http/httptest"
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
// 一个可 Flush 的 ResponseWriter,供集成测试用。
|
||
type flushWriter struct {
|
||
*httptest.ResponseRecorder
|
||
flushed chan bool
|
||
}
|
||
|
||
func (f *flushWriter) Flush() {
|
||
select {
|
||
case f.flushed <- true:
|
||
default:
|
||
}
|
||
}
|
||
|
||
// 端到端验证:Manager 完整走一遍「事件入缓冲区 → 新连接带 Last-Event-ID 重连 → 补投」。
|
||
// 这是生产里最关键的可靠性路径 —— 断线期间收的邮件,重连后必须能看到。
|
||
func TestManagerReplayOnReconnect(t *testing.T) {
|
||
m := &Manager{
|
||
clients: make(map[string]*Client),
|
||
eventBuffer: make(map[string]*eventRing),
|
||
}
|
||
|
||
// 1) 用户 alice 发来一封信(无人连接也入缓冲区)
|
||
m.SendToUser("alice", "new_mail", map[string]string{"mail_id": "m1"})
|
||
m.SendToUser("alice", "new_mail", map[string]string{"mail_id": "m2"})
|
||
m.SendToUser("alice", "new_mail", map[string]string{"mail_id": "m3"})
|
||
|
||
ring := m.eventBuffer["u:alice"]
|
||
if ring == nil {
|
||
t.Fatal("alice 的缓冲区应该已创建")
|
||
}
|
||
|
||
// 2) 带 Last-Event-ID=1 重连,应补投 m2、m3(跳过 m1)
|
||
req := httptest.NewRequest("GET", "/events/stream", nil)
|
||
req.Header.Set("Last-Event-ID", "1")
|
||
w := &flushWriter{httptest.NewRecorder(), make(chan bool, 10)}
|
||
|
||
client := m.AddClient(w, req, "", "alice")
|
||
if client == nil {
|
||
t.Fatal("AddClient 返回 nil(flushWriter 应支持 Flush)")
|
||
}
|
||
defer m.RemoveClient(client.ID)
|
||
|
||
body := w.Body.String()
|
||
if !strings.Contains(body, `"mail_id":"m2"`) {
|
||
t.Error("重连后应补投 m2,实际 body:", body)
|
||
}
|
||
if !strings.Contains(body, `"mail_id":"m3"`) {
|
||
t.Error("重连后应补投 m3,实际 body:", body)
|
||
}
|
||
if strings.Contains(body, `"mail_id":"m1"`) {
|
||
t.Error("已确认的 m1 不应重放(Last-Event-ID=1),实际 body:", body)
|
||
}
|
||
|
||
// 3) 连接期间新来一封信,实时推送
|
||
m.SendToUser("alice", "new_mail", map[string]string{"mail_id": "m4"})
|
||
body = w.Body.String()
|
||
if !strings.Contains(body, `"mail_id":"m4"`) {
|
||
t.Error("在线连接应实时收到 m4,实际 body:", body)
|
||
}
|
||
}
|
||
|
||
// 序列号全局递增,两条不同事件不同 ID。
|
||
func TestEventIDMonotonic(t *testing.T) {
|
||
m := &Manager{
|
||
clients: make(map[string]*Client),
|
||
eventBuffer: make(map[string]*eventRing),
|
||
}
|
||
a := m.nextEventID()
|
||
b := m.nextEventID()
|
||
if a == b {
|
||
t.Fatalf("两个连续事件 ID 相同: %q", a)
|
||
}
|
||
if a > b {
|
||
t.Fatalf("事件 ID 应递增: %q > %q", a, b)
|
||
}
|
||
}
|
||
|
||
// 确保事件 ID 写进了 SSE 帧(EventSource 靠 id: 行记住位置)
|
||
func TestSendWritesIDField(t *testing.T) {
|
||
fw := &flushWriter{httptest.NewRecorder(), make(chan bool, 5)}
|
||
c := &Client{
|
||
ID: "c1",
|
||
UserName: "alice",
|
||
Res: fw,
|
||
Flusher: fw,
|
||
done: make(chan struct{}),
|
||
}
|
||
c.SendWithID("42", "new_mail", map[string]string{"x": "y"})
|
||
|
||
body := fw.Body.String()
|
||
if !strings.Contains(body, "id: 42\n") {
|
||
t.Error("帧里应有 id: 42 行,实际:", body)
|
||
}
|
||
if !strings.Contains(body, "event: new_mail") {
|
||
t.Error("帧里应有 event: new_mail,实际:", body)
|
||
}
|
||
if !strings.Contains(body, `data: {"x":"y"}`) {
|
||
t.Error("帧里应有 data,实际:", body)
|
||
}
|
||
}
|