Files
HomeAgent/internal/plugins/webui/test/handler_sse_test.go
JianFeeeee 1d38353498 refactor(webui): SSE 事件环判据迁到 test/ —— 导出环类型与记录字段
handler_sse_test.go → test/          (6 条判据)
    webui/       167 → 161
    webui/test/   35 →  41
    合计 202 不变

## 原来那 6 条判据读的是私有字段

    len(r.buf)
    r.buf[0].ID / .EventType / .Data

`sseEventRing` 的对外语义就三条:容量上限、按 Last-Event-ID 重放、
未知 ID 回退全量。它们全部可以从**导出接口**断言:

    NewSSEEventRing(cap)        构造
    Append(id, type, data)      写入
    After(id) []SSEEventRecord  重放

所以导出 `sseEventRing → SSEEventRing`、`SSEEventRecord` 的字段、
以及构造入口 `NewSSEEventRing`。

**为什么这不是"为测试导出内部"**:`After` 返回的东西就是客户端断线
重连时实际收到的内容 —— id、event、payload 三样。它们是外部可见的输出,
把字段藏起来只让包内测试能读,才是反过来的怪事。

测试侧的 `r.buf[i]` 统一换成 `r.After("")[i]`(空 ID 命中不了,按约定
回退全量),语义等价且不再触碰内部状态。

## 判据有效性(变异验证)

把容量裁剪短路掉(`if false && len(r.buf) > r.cap`):

    --- FAIL: TestSSEEventRing_Append_Capacity
        expected 3, got 5

环不再丢最旧条目 ⇒ 断线重连会重放超过窗口的历史,客户端 Last-Event-ID
语义失效。恢复后全绿。

## 验证

    go build ./...                  PASS
    go vet ./...                    PASS
    gofmt -l                        干净
    go test ./... -count=1          67 包全过,0 FAIL
2026-11-04 22:21:54 +08:00

98 lines
2.3 KiB
Go
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.

package test
import (
"encoding/json"
"github.com/JianFeeeee/HologramAgent/internal/plugins/webui"
"reflect"
"testing"
)
func TestSSEEventRing_Append_Capacity(t *testing.T) {
r := webui.NewSSEEventRing(3)
data := json.RawMessage(`{"a":1}`)
for i := 0; i < 5; i++ {
r.Append("id-"+string(rune('0'+i)), "agent_output", data)
}
// cap=3,只保留 id-2,id-3,id-4
if len(r.After("")) != 3 {
t.Fatalf("expected 3, got %d", len(r.After("")))
}
if r.After("")[0].ID != "id-2" {
t.Fatalf("expected id-2, got %s", r.After("")[0].ID)
}
}
func TestSSEEventRing_After_Mid(t *testing.T) {
r := webui.NewSSEEventRing(10)
data := json.RawMessage(`{"x":"y"}`)
r.Append("id-1", "reasoning", data)
r.Append("id-2", "agent_output", data)
r.Append("id-3", "tool_call", data)
after := r.After("id-2")
if len(after) != 1 || after[0].ID != "id-3" {
t.Fatalf("expected [id-3], got %v", after)
}
after = r.After("id-3")
if len(after) != 0 {
t.Fatalf("expected 0, got %d", len(after))
}
}
func TestSSEEventRing_After_NotFound_ReturnsAll(t *testing.T) {
r := webui.NewSSEEventRing(3)
data := json.RawMessage(`{}`)
r.Append("id-1", "agent_output", data)
r.Append("id-2", "agent_output", data)
after := r.After("id-0")
if len(after) != 2 {
t.Fatalf("expected 2 (fallback to all), got %d", len(after))
}
}
func TestSSEEventRing_After_Last_Tip(t *testing.T) {
r := webui.NewSSEEventRing(5)
data := json.RawMessage(`{"a":1}`)
for i := 0; i < 5; i++ {
r.Append("id-"+string(rune('0'+i)), "agent_output", data)
}
after := r.After("id-4")
if len(after) != 0 {
t.Fatalf("expected 0 after tip, got %d", len(after))
}
}
func TestSSEEventRing_Concurrent(t *testing.T) {
r := webui.NewSSEEventRing(100)
done := make(chan struct{})
for i := 0; i < 8; i++ {
go func(n int) {
defer func() { done <- struct{}{} }()
data := json.RawMessage(`{}`)
for j := 0; j < 100; j++ {
r.Append("w"+string(rune('0'+n))+"/"+string(rune('0'+j)), "agent_output", data)
}
}(i)
}
for i := 0; i < 8; i++ {
go func() {
defer func() { done <- struct{}{} }()
for j := 0; j < 100; j++ {
_ = r.After("w0/0")
}
}()
}
for i := 0; i < 16; i++ {
<-done
}
}
func TestSSEEventRecord_Type(t *testing.T) {
var got interface{} = webui.SSEEventRecord{}
if reflect.TypeOf(got).Kind() != reflect.Struct {
t.Fatal("webui.SSEEventRecord should be a struct")
}
}