transport_replay_integration_test.go RawHistoryFolder

1package main_test
2
3import (
4	"io"
5	"net"
6	"sync"
7	"testing"
8
9	vt "github.com/bnema/vev-vt"
10	renderer "github.com/bnema/vev-vt/ansi"
11	"github.com/bnema/vev/internal/adapters/ipc"
12	"github.com/bnema/vev/internal/adapters/sessionwire"
13	"github.com/bnema/vev/internal/adapters/sshstdio"
14	"github.com/bnema/vev/internal/protocol"
15	"github.com/bnema/vev/internal/protocol/wire"
16	"github.com/bnema/vev/internal/testutil/replaytest"
17	"github.com/stretchr/testify/require"
18)
19
20// TestTransportReplayIntegration stays at the legal application boundary: it
21// observes only typed output frames and terminal bytes, never daemon internals.
22func TestTransportReplayIntegration(t *testing.T) {
23	frames := replaytest.Transcript()
24	left, right := net.Pipe()
25	t.Cleanup(func() { _ = left.Close(); _ = right.Close() })
26	sender, receiver := ipc.NewTransport(left), ipc.NewTransport(right)
27	var wg sync.WaitGroup
28	wg.Go(func() {
29		for _, frame := range frames {
30			if err := sender.Send(frame); err != nil {
31				t.Errorf("Send: %v", err)
32				return
33			}
34		}
35	})
36
37	terminal := vt.NewScreen(8, 3)
38	var bytes []byte
39	for _, want := range frames {
40		got, err := receiver.Recv()
41		require.NoError(t, err)
42		require.Equal(t, want, got)
43		decoded, err := sessionwire.DecodeServerEnvelope(got.Payload)
44		require.NoError(t, err)
45		output, ok := decoded.(protocol.Output)
46		require.True(t, ok, "decoded server message = %T, want protocol.Output", decoded)
47		bytes = append(bytes, output.Data...)
48		terminal.Write(output.Data)
49	}
50	wg.Wait()
51	require.Equal(t, "\x1b[2J\x1b[Hone\r\ntwo\x1b[2;1HTWO", string(bytes))
52	require.Equal(t, []string{"one     ", "TWO     ", "        "}, integrationFrameRows(terminal))
53}
54
55// TestThemeGenerationTransportSequences proves the generation's cleared then
56// definitive wire sequence is unchanged across the local socket and remote
57// SSH-stdio transports. The transports only carry frames; neither can infer
58// or alter the palette lifecycle.
59func TestThemeGenerationTransportSequences(t *testing.T) {
60	cleared := protocol.Theme{
61		HasForeground: true, Foreground: renderer.RGB{R: 1, G: 2, B: 3},
62		HasBackground: true, Background: renderer.RGB{R: 4, G: 5, B: 6},
63		SchemeKnown: true,
64	}
65	palette := [16]renderer.RGB{2: {R: 125, G: 181, B: 181}, 10: {R: 125, G: 181, B: 181}}
66	definitive := cleared
67	definitive.Palette = palette
68	definitive.PaletteKnown = 1<<2 | 1<<10
69	sequence := []protocol.Theme{cleared, definitive}
70
71	tests := []struct {
72		name string
73		pair func(*testing.T) (wire.Transport, wire.Transport)
74	}{
75		{
76			name: "local ipc socket",
77			pair: func(t *testing.T) (wire.Transport, wire.Transport) {
78				left, right := net.Pipe()
79				t.Cleanup(func() { _ = left.Close(); _ = right.Close() })
80				return ipc.NewTransport(left), ipc.NewTransport(right)
81			},
82		},
83		{
84			name: "remote ssh stdio",
85			pair: func(t *testing.T) (wire.Transport, wire.Transport) {
86				clientRead, serverWrite := io.Pipe()
87				serverRead, clientWrite := io.Pipe()
88				closeClient := func() error {
89					_ = clientRead.Close()
90					return clientWrite.Close()
91				}
92				closeServer := func() error {
93					_ = serverRead.Close()
94					return serverWrite.Close()
95				}
96				t.Cleanup(func() { _ = closeClient(); _ = closeServer() })
97				return sshstdio.NewTransport(clientRead, clientWrite, closeClient), sshstdio.NewTransport(serverRead, serverWrite, closeServer)
98			},
99		},
100	}
101
102	for _, tt := range tests {
103		t.Run(tt.name, func(t *testing.T) {
104			sender, receiver := tt.pair(t)
105			done := make(chan error, 1)
106			go func() {
107				for _, snapshot := range sequence {
108					payload, err := sessionwire.EncodeClientMessage(snapshot)
109					if err != nil {
110						done <- err
111						return
112					}
113					if err := sender.Send(wire.Envelope{Payload: payload}); err != nil {
114						done <- err
115						return
116					}
117				}
118				done <- nil
119			}()
120
121			for _, want := range sequence {
122				envelope, err := receiver.Recv()
123				require.NoError(t, err)
124				wantPayload, err := sessionwire.EncodeClientMessage(want)
125				require.NoError(t, err)
126				require.Equal(t, wantPayload, envelope.Payload, "theme envelope must be preserved byte-for-byte before decode")
127				decoded, err := sessionwire.DecodeClientEnvelope(envelope.Payload)
128				require.NoError(t, err)
129				got, ok := decoded.(protocol.Theme)
130				require.True(t, ok, "decoded client message = %T, want protocol.Theme", decoded)
131				require.Equal(t, want, got)
132			}
133			require.NoError(t, <-done)
134		})
135	}
136}
137
138func integrationFrameRows(frame renderer.CellSource) []string {
139	rows := make([]string, frame.Rows())
140	for y := range rows {
141		for x := range frame.Columns() {
142			rows[y] += string(frame.Cell(x, y).Rune)
143		}
144	}
145	return rows
146}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close