main pico / pkg / apps / pipe / web_test.go
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}