nandi/oripublic Fork 0
00224b167266e7d496672adfe1e60fcffeab7e63
Commits
Clone
git clone https://git.rickub.com/nandi/ori.git
git clone ssh://git@rickub.com/nandi/ori.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

forked from bots-garden/ori

terminal_test.go · 124 lines · 3.4 KBGo Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package terminal_test

import (
	"context"
	"net/http/httptest"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/coder/websocket"
	"github.com/coder/websocket/wsjson"

	"rickub.com/bots-garden/ori/internal/terminal"
)

// dialTerminal starts the service on a test server, with plain sh for
// determinism, and connects a WebSocket client to it.
func dialTerminal(t *testing.T, cwd string) (*websocket.Conn, context.Context) {
	t.Helper()
	service := terminal.New(cwd, nil)
	service.Shell = []string{"/bin/sh"}

	server := httptest.NewServer(service.WebSocketHandler())
	t.Cleanup(server.Close)

	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	t.Cleanup(cancel)

	conn, _, err := websocket.Dial(ctx, "ws"+strings.TrimPrefix(server.URL, "http"), nil)
	if err != nil {
		t.Fatalf("websocket dial failed: %v", err)
	}
	t.Cleanup(func() { _ = conn.CloseNow() })
	return conn, ctx
}

// readOutputUntil accumulates binary frames until the marker shows up.
func readOutputUntil(t *testing.T, ctx context.Context, conn *websocket.Conn, marker string) string {
	t.Helper()
	var output strings.Builder
	for !strings.Contains(output.String(), marker) {
		kind, data, err := conn.Read(ctx)
		if err != nil {
			t.Fatalf("read failed while waiting for %q; got so far: %q (%v)", marker, output.String(), err)
		}
		if kind == websocket.MessageBinary {
			output.Write(data)
		}
	}
	return output.String()
}

func send(t *testing.T, ctx context.Context, conn *websocket.Conn, msg map[string]any) {
	t.Helper()
	if err := wsjson.Write(ctx, conn, msg); err != nil {
		t.Fatalf("websocket write failed: %v", err)
	}
}

func TestShellRunsCommandsAndStreamsOutput(t *testing.T) {
	conn, ctx := dialTerminal(t, t.TempDir())

	send(t, ctx, conn, map[string]any{"type": "input", "data": "echo ori-$((20+22))\n"})
	output := readOutputUntil(t, ctx, conn, "ori-42")
	if output == "" {
		t.Fatal("no terminal output received")
	}
}

func TestShellStartsInTheConfiguredDirectory(t *testing.T) {
	cwd := t.TempDir()
	resolved, err := filepath.EvalSymlinks(cwd)
	if err != nil {
		t.Fatal(err)
	}
	conn, ctx := dialTerminal(t, cwd)

	send(t, ctx, conn, map[string]any{"type": "input", "data": "pwd\n"})
	readOutputUntil(t, ctx, conn, resolved)
}

func TestResizeIsAcceptedAndSessionKeepsWorking(t *testing.T) {
	conn, ctx := dialTerminal(t, t.TempDir())

	send(t, ctx, conn, map[string]any{"type": "resize", "cols": 120, "rows": 40})
	send(t, ctx, conn, map[string]any{"type": "input", "data": "stty size\n"})
	readOutputUntil(t, ctx, conn, "40 120")
}

func TestMalformedFramesDoNotKillTheSession(t *testing.T) {
	conn, ctx := dialTerminal(t, t.TempDir())

	if err := conn.Write(ctx, websocket.MessageText, []byte("{not json")); err != nil {
		t.Fatalf("write failed: %v", err)
	}
	send(t, ctx, conn, map[string]any{"type": "input", "data": "echo still-alive\n"})
	readOutputUntil(t, ctx, conn, "still-alive")
}

func TestShellExitClosesTheSocket(t *testing.T) {
	conn, ctx := dialTerminal(t, t.TempDir())

	send(t, ctx, conn, map[string]any{"type": "input", "data": "exit\n"})

	deadline := time.Now().Add(10 * time.Second)
	for {
		if _, _, err := conn.Read(ctx); err != nil {
			return // closed, as expected
		}
		if time.Now().After(deadline) {
			t.Fatal("socket still open long after the shell exited")
		}
	}
}

func TestMain(m *testing.M) {
	if _, err := os.Stat("/bin/sh"); err != nil {
		// No shell, no terminal tests (should not happen on linux).
		os.Exit(0)
	}
	os.Exit(m.Run())
}