chore: directory migration - gateway→server, web→client/electron
This commit is contained in:
108
server/internal/sse/e2e_test.go
Normal file
108
server/internal/sse/e2e_test.go
Normal file
@ -0,0 +1,108 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user