test(webview): cover event stream failures and cleanup

This commit is contained in:
Levi committed 2026-09-18 00:31:06 +08:00
1 parent c2da22f371
commit 7e07ea31cc
1 file changed
+120
+120
View File
@@ -2,9 +2,11 @@ package rpcprovider
import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
"sync/atomic"
"testing"
"time"
@@ -85,3 +87,121 @@ func TestPageEventStream(t *testing.T) {
t.Fatal("registration after close")
}
}
func TestPageEventStreamHandshakeFailureCanRetry(t *testing.T) {
for _, tc := range []struct {
name string
status int
body string
}{
{"http_error", http.StatusUnauthorized, `{"ready":true}`},
{"invalid_json", http.StatusOK, `not json`},
{"not_ready", http.StatusOK, `{"ready":false}`},
} {
t.Run(tc.name, func(t *testing.T) {
var requests atomic.Int32
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if requests.Add(1) == 1 {
w.WriteHeader(tc.status)
_, _ = io.WriteString(w, tc.body)
return
}
_, _ = io.WriteString(w, "{\"ready\":true}\n{\"event\":\"load\",\"data\":{\"url\":\"https://example.test\"}}\n")
w.(http.Flusher).Flush()
<-r.Context().Done()
}))
defer server.Close()
p := &page{client: NewClient(wv.RPCConfig{Network: "tcp", Address: strings.TrimPrefix(server.URL, "http://")}), id: "retry-page"}
if off, err := p.On("load", func(wv.Event) { t.Error("failed registration retained its listener") }); err == nil || off != nil {
t.Fatal("failed handshake accepted")
}
got := make(chan wv.Event, 1)
off, err := p.On("load", func(e wv.Event) { got <- e })
if err != nil {
t.Fatalf("retry: %v", err)
}
defer func() {
p.eventCancel()
<-p.eventDone
}()
defer off()
select {
case e := <-got:
if e.Data["url"] != "https://example.test" {
t.Fatal(e)
}
case <-time.After(time.Second):
t.Fatal("retry did not receive load notification")
}
if requests.Load() != 2 {
t.Fatalf("unexpected request count: %d", requests.Load())
}
})
}
}
func TestPageEventStreamDisconnectAndClose(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var request wv.RPCRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
t.Error(err)
return
}
switch request.Method {
case wv.MethodPageEvents:
// End the stream without a terminal event, as if the host dropped it.
_, _ = io.WriteString(w, "{\"ready\":true}\n")
case wv.MethodPageClose:
_ = json.NewEncoder(w).Encode(wv.RPCResponse{})
default:
t.Errorf("unexpected method: %s", request.Method)
}
}))
defer server.Close()
p := &page{client: NewClient(wv.RPCConfig{Network: "tcp", Address: strings.TrimPrefix(server.URL, "http://")}), id: "disconnected-page"}
closed := make(chan wv.Event, 1)
if _, err := p.On("closed", func(e wv.Event) { closed <- e }); err != nil {
t.Fatal(err)
}
select {
case <-p.eventDone:
case <-time.After(time.Second):
t.Fatal("disconnected stream did not stop")
}
if _, err := p.On("load", func(wv.Event) {}); err == nil || !strings.Contains(err.Error(), "disconnected") {
t.Fatalf("registration hid stream failure: %v", err)
}
if err := p.Close(); err != nil {
t.Fatal(err)
}
select {
case event := <-closed:
if event.Data["reason"] != "api" {
t.Fatal(event)
}
default:
t.Fatal("API close did not release listeners after disconnect")
}
if err := p.Close(); err != nil {
t.Fatal(err)
}
select {
case event := <-closed:
t.Fatalf("duplicate close notification: %+v", event)
default:
}
}
func TestPageEventStreamConnectionFailureCanRetry(t *testing.T) {
server := httptest.NewServer(http.NotFoundHandler())
server.Close()
p := &page{client: NewClient(wv.RPCConfig{Network: "tcp", Address: strings.TrimPrefix(server.URL, "http://")}), id: "offline-page"}
for i := 0; i < 2; i++ {
if off, err := p.On("load", func(wv.Event) {}); err == nil || off != nil {
t.Fatal("registration to unavailable host succeeded")
}
}
if p.eventCancel != nil {
t.Fatal("failed connection retained an active stream")
}
}