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}