1package sessionwire
2
3import (
4 "testing"
5 "time"
6
7 "github.com/stretchr/testify/require"
8 "google.golang.org/protobuf/proto"
9
10 "github.com/bnema/vev/internal/domain"
11 "github.com/bnema/vev/internal/protocol"
12 "github.com/bnema/vev/internal/protocol/wire"
13)
14
15func testRemotePreviewWatch() protocol.RemotePreviewWatch {
16 return protocol.RemotePreviewWatch{
17 Request: protocol.RemotePreviewRequest{
18 Version: protocol.RemotePreviewSchemaVersion,
19 Target: domain.RemoteSessionTarget{
20 Endpoint: "local", DisplayOrigin: "local", LifecycleID: domain.SessionLifecycleID{7},
21 SessionName: "work", LiveTabID: "tab-2",
22 },
23 Width: 80, Height: 24,
24 },
25 MinInterval: 125 * time.Millisecond,
26 }
27}
28
29// TestRemotePreviewWatchByteRoundTrip pins the watch as a client first
30// message: it re-encodes byte-for-byte, rides field 32, and carries its
31// interval as whole milliseconds.
32func TestRemotePreviewWatchByteRoundTrip(t *testing.T) {
33 message := testRemotePreviewWatch()
34 raw, err := EncodeClientMessage(message)
35 require.NoError(t, err)
36 require.NoError(t, wire.ScanEnvelope(&wire.ClientEnvelope{}, raw))
37
38 var envelope wire.ClientEnvelope
39 require.NoError(t, proto.Unmarshal(raw, &envelope))
40 payload, ok := envelope.Payload.(*wire.ClientEnvelope_RemotePreviewWatch)
41 require.True(t, ok, "got %T", envelope.Payload)
42 require.Equal(t, uint32(125), payload.RemotePreviewWatch.GetMinIntervalMs())
43
44 decoded, err := DecodeClientEnvelope(raw)
45 require.NoError(t, err)
46 require.Equal(t, message, decoded)
47 reencoded, err := EncodeClientMessage(decoded)
48 require.NoError(t, err)
49 require.Equal(t, raw, reencoded, "RemotePreviewWatch must re-encode byte-for-byte")
50 require.Equal(t, protocol.DecodeMessageRemotePreview, ClientVariantKindForTest(raw))
51}
52
53func TestRemotePreviewWatchStrictScan(t *testing.T) {
54 valid, err := EncodeClientMessage(testRemotePreviewWatch())
55 require.NoError(t, err)
56 tests := []struct {
57 name string
58 raw []byte
59 }{
60 {name: "trailing garbage", raw: append(append([]byte(nil), valid...), 0xFF)},
61 {name: "duplicate variant", raw: append(append([]byte(nil), valid...), valid...)},
62 }
63 for cut := 1; cut < len(valid); cut++ {
64 tests = append(tests, struct {
65 name string
66 raw []byte
67 }{name: "truncated prefix", raw: valid[:cut]})
68 }
69 for _, tt := range tests {
70 t.Run(tt.name, func(t *testing.T) {
71 _, err := DecodeClientEnvelope(tt.raw)
72 require.Error(t, err)
73 })
74 }
75}
76
77func TestRemotePreviewWatchRejectsInvalidValues(t *testing.T) {
78 request, err := EncodeClientMessage(testRemotePreviewWatch())
79 require.NoError(t, err)
80 var envelope wire.ClientEnvelope
81 require.NoError(t, proto.Unmarshal(request, &envelope))
82 valid := envelope.Payload.(*wire.ClientEnvelope_RemotePreviewWatch).RemotePreviewWatch
83
84 tests := []struct {
85 name string
86 mutate func(*wire.RemotePreviewWatch)
87 }{
88 {name: "zero interval", mutate: func(w *wire.RemotePreviewWatch) { w.MinIntervalMs = 0 }},
89 {name: "interval below bound", mutate: func(w *wire.RemotePreviewWatch) { w.MinIntervalMs = 15 }},
90 {name: "interval above bound", mutate: func(w *wire.RemotePreviewWatch) { w.MinIntervalMs = 1001 }},
91 {name: "oversized interval", mutate: func(w *wire.RemotePreviewWatch) { w.MinIntervalMs = ^uint32(0) }},
92 {name: "missing request", mutate: func(w *wire.RemotePreviewWatch) { w.Request = nil }},
93 {name: "oversized width", mutate: func(w *wire.RemotePreviewWatch) { w.Request.Width = protocol.RemotePreviewMaxWidth + 1 }},
94 {name: "zero height", mutate: func(w *wire.RemotePreviewWatch) { w.Request.Height = 0 }},
95 }
96 for _, tt := range tests {
97 t.Run(tt.name, func(t *testing.T) {
98 message := proto.Clone(valid).(*wire.RemotePreviewWatch)
99 tt.mutate(message)
100 _, err := remotePreviewWatchFromWire(message)
101 require.ErrorIs(t, err, protocol.ErrInvalidRemotePreviewWatch)
102 })
103 }
104
105 encodeTests := []struct {
106 name string
107 mutate func(*protocol.RemotePreviewWatch)
108 }{
109 {name: "sub-millisecond interval", mutate: func(w *protocol.RemotePreviewWatch) { w.MinInterval += time.Microsecond }},
110 {name: "interval above bound", mutate: func(w *protocol.RemotePreviewWatch) { w.MinInterval = 2 * time.Second }},
111 {name: "stopped target", mutate: func(w *protocol.RemotePreviewWatch) { w.Request.Target.Stopped, w.Request.Target.LiveTabID = true, "" }},
112 }
113 for _, tt := range encodeTests {
114 t.Run("encode "+tt.name, func(t *testing.T) {
115 message := testRemotePreviewWatch()
116 tt.mutate(&message)
117 _, err := EncodeClientMessage(message)
118 require.Error(t, err)
119 })
120 }
121}