Antonio Mika
·
2026-10-10
1package pipe
2
3import (
4 "context"
5 "encoding/pem"
6 "io"
7 "net/http"
8 "net/http/httptest"
9 "os"
10 "path/filepath"
11 "strings"
12 "testing"
13
14 "github.com/gorilla/websocket"
15 "github.com/picosh/utils/pipe"
16)
17
18// newWebTest runs pipe-web against a test pipe server.
19func newWebTest(t *testing.T) (*TestSSHServer, *httptest.Server) {
20 t.Helper()
21 server := NewTestSSHServer(t)
22 t.Cleanup(server.Shutdown)
23
24 user := GenerateUser("pico")
25 RegisterUserWithServer(server, user)
26 keyPath := filepath.Join(t.TempDir(), "key")
27 key := pem.EncodeToMemory(&pem.Block{Type: "OPENSSH PRIVATE KEY", Bytes: user.privateKey})
28 if err := os.WriteFile(keyPath, key, 0o600); err != nil {
29 t.Fatal(err)
30 }
31
32 conn := newPipeConn(&pipe.SSHClientInfo{
33 RemoteHost: "localhost:" + server.Cfg.Port,
34 RemoteHostname: "localhost",
35 RemoteUser: user.username,
36 KeyLocation: keyPath,
37 })
38 web := &webRouter{cfg: server.Cfg, db: server.DBPool, conn: conn}
39 ts := httptest.NewServer(web.mainMux())
40 t.Cleanup(ts.Close)
41 return server, ts
42}
43
44func TestWebSubEndsWithRequest(t *testing.T) {
45 server, ts := newWebTest(t)
46
47 ctx, cancel := context.WithCancel(context.Background())
48 req, err := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL+"/topic/quiet", nil)
49 if err != nil {
50 t.Fatal(err)
51 }
52 go func() {
53 res, err := http.DefaultClient.Do(req)
54 if err == nil {
55 _, _ = io.Copy(io.Discard, res.Body)
56 _ = res.Body.Close()
57 }
58 }()
59 server.waitForClients(t, 1)
60
61 cancel()
62 server.waitForClients(t, 0)
63}
64
65func TestWebPubReachesSub(t *testing.T) {
66 server, ts := newWebTest(t)
67
68 received := make(chan string, 1)
69 go func() {
70 res, err := http.Get(ts.URL + "/topic/news")
71 if err != nil {
72 received <- err.Error()
73 return
74 }
75 defer func() { _ = res.Body.Close() }()
76 body, _ := io.ReadAll(res.Body)
77 received <- string(body)
78 }()
79 server.waitForClients(t, 1)
80
81 res, err := http.Post(ts.URL+"/topic/news", "text/plain", strings.NewReader("hello"))
82 if err != nil {
83 t.Fatal(err)
84 }
85 _ = res.Body.Close()
86 if res.StatusCode != http.StatusOK {
87 t.Errorf("pub status = %d", res.StatusCode)
88 }
89 if got := <-received; got != "hello" {
90 t.Errorf("sub got %q, want %q", got, "hello")
91 }
92 server.waitForClients(t, 0)
93}
94
95func TestWebPipeEndsWhenClientLeaves(t *testing.T) {
96 server, ts := newWebTest(t)
97
98 c, res, err := websocket.DefaultDialer.Dial("ws"+strings.TrimPrefix(ts.URL, "http")+"/pipe/chat", nil)
99 if err != nil {
100 t.Fatal(err)
101 }
102 _ = res.Body.Close()
103 server.waitForClients(t, 1)
104
105 // Drop the connection without a close frame, as a client that crashed.
106 _ = c.NetConn().Close()
107 server.waitForClients(t, 0)
108}