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