repos
/ orchard main

orchard

mirror

Every site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.

blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics

2.2 KB · 78 lines · Go Raw History
 1package main
 2
 3import (
 4	"bufio"
 5	"context"
 6	"net/http"
 7	"net/http/httptest"
 8	"strings"
 9	"testing"
10	"time"
11)
12
13// The meta stream is what makes a turn started on the desktop show up on the
14// phone. It has to open immediately, since a proxy that buffers until it sees
15// output would otherwise hold the whole stream, and it has to stop when the tab
16// goes away rather than leaking a subscriber per reload.
17func TestEventsStreamsAndCleansUp(t *testing.T) {
18	s := &site{hub: NewHub()}
19	srv := httptest.NewServer(http.HandlerFunc(s.events))
20	defer srv.Close()
21
22	ctx, cancel := context.WithCancel(context.Background())
23	req, _ := http.NewRequestWithContext(ctx, http.MethodGet, srv.URL, nil)
24	resp, err := http.DefaultClient.Do(req)
25	if err != nil {
26		t.Fatal(err)
27	}
28	defer resp.Body.Close()
29
30	if got := resp.Header.Get("Content-Type"); got != "text/event-stream" {
31		t.Errorf("content type = %q", got)
32	}
33
34	br := bufio.NewReader(resp.Body)
35	// The opening comment, so a buffering proxy lets the stream through.
36	line, err := br.ReadString('\n')
37	if err != nil || !strings.HasPrefix(line, ":") {
38		t.Fatalf("first line = %q, %v, want a comment", line, err)
39	}
40
41	// A subscriber has to be registered by now, or a publish would go nowhere.
42	deadline := time.Now().Add(2 * time.Second)
43	for s.hub.Len() == 0 && time.Now().Before(deadline) {
44		time.Sleep(5 * time.Millisecond)
45	}
46	if s.hub.Len() != 1 {
47		t.Fatalf("%d subscribers, want 1", s.hub.Len())
48	}
49
50	s.hub.Publish(HubEvent{Kind: "finished", ConvID: "c9", Title: "Named"})
51	var got string
52	for {
53		line, err := br.ReadString('\n')
54		if err != nil {
55			t.Fatalf("reading the stream: %v", err)
56		}
57		if strings.HasPrefix(line, "data: ") {
58			got = strings.TrimSpace(strings.TrimPrefix(line, "data: "))
59			break
60		}
61	}
62	if !strings.Contains(got, `"conversation_id":"c9"`) || !strings.Contains(got, `"kind":"finished"`) {
63		t.Errorf("frame = %s", got)
64	}
65
66	// A tab that goes away drops its subscriber rather than leaving one behind
67	// for every reload the phone does.
68	cancel()
69	resp.Body.Close()
70	for time.Now().Before(deadline) {
71		if s.hub.Len() == 0 {
72			return
73		}
74		time.Sleep(5 * time.Millisecond)
75	}
76	t.Errorf("%d subscribers left after the reader went away", s.hub.Len())
77}