mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-04 00:03:59 +00:00
feat: SSE Last-Event-ID 断线重放 + GUI 表单防刷新
- webui handler: 新增 sseEventRing 环状缓冲区(200条),断线重连按 Last-Event-ID 重放遗漏事件 - GUI app.js: doRenderAll 检测连接表单打开时改走 refreshDataOnly,修复 15s 定时器擦掉用户输入的 bug - 附带 dashboard.html/index.html 前端调整 + handler_sse_test.go 单测
This commit is contained in:
96
internal/plugins/webui/handler_sse_test.go
Normal file
96
internal/plugins/webui/handler_sse_test.go
Normal file
@ -0,0 +1,96 @@
|
||||
package webui
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSSEEventRing_Append_Capacity(t *testing.T) {
|
||||
r := 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.buf) != 3 {
|
||||
t.Fatalf("expected 3, got %d", len(r.buf))
|
||||
}
|
||||
if r.buf[0].id != "id-2" {
|
||||
t.Fatalf("expected id-2, got %s", r.buf[0].id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSEEventRing_After_Mid(t *testing.T) {
|
||||
r := 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 := 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 := 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 := 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{} = sseEventRecord{}
|
||||
if reflect.TypeOf(got).Kind() != reflect.Struct {
|
||||
t.Fatal("sseEventRecord should be a struct")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user