1package ports
2
3import (
4 "encoding/hex"
5 "testing"
6
7 "github.com/bnema/vev/internal/domain"
8 "github.com/stretchr/testify/require"
9)
10
11func testExactTarget() ExactSessionTarget {
12 return ExactSessionTarget{LifecycleID: domain.SessionLifecycleID{1, 2, 3}, SessionName: "work"}
13}
14
15func TestExactSessionTargetCodecIsStrict(t *testing.T) {
16 want := testExactTarget()
17 encoded, err := MarshalExactSessionTarget(want)
18 require.NoError(t, err)
19 require.Equal(t, "010203000000000000000000000000000004776f726b", hex.EncodeToString(encoded))
20 assertAllPrefixesFail(t, encoded, UnmarshalExactSessionTarget)
21 _, err = UnmarshalExactSessionTarget(append(append([]byte(nil), encoded...), 0))
22 require.Error(t, err)
23
24 got, err := UnmarshalExactSessionTarget(encoded)
25 require.NoError(t, err)
26 require.Equal(t, want, got)
27
28 _, err = MarshalExactSessionTarget(ExactSessionTarget{SessionName: "work"})
29 require.ErrorIs(t, err, ErrInvalidRouteWire)
30}
31
32func TestHelloExactTargetRoundTrip(t *testing.T) {
33 target := testExactTarget()
34 msg := Hello{
35 Version: ProtocolVersion,
36 Intent: IntentAttach,
37 Name: target.SessionName,
38 Size: domain.Size{Cols: 80, Rows: 24},
39 ExactTarget: &target,
40 PreferredTabID: "tab-2",
41 }
42
43 encoded := MarshalHello(msg)
44 require.NotEmpty(t, encoded)
45 got, err := UnmarshalHello(encoded)
46 require.NoError(t, err)
47 require.Equal(t, msg, got)
48
49 invalid := msg
50 invalid.Intent = IntentNew
51 require.Error(t, ValidateHello(invalid))
52
53 invalid = msg
54 invalid.PreferredTabID = "bad tab"
55 require.Error(t, ValidateHello(invalid))
56}
57
58func TestRoutePositionCodecIsStrict(t *testing.T) {
59 want := RoutePosition{Target: testExactTarget(), ActiveTabID: "tab-2"}
60 encoded, err := MarshalRoutePosition(want)
61 require.NoError(t, err)
62 require.Equal(t, "010203000000000000000000000000000004776f726b00057461622d32", hex.EncodeToString(encoded))
63 assertAllPrefixesFail(t, encoded, UnmarshalRoutePosition)
64 _, err = UnmarshalRoutePosition(append(append([]byte(nil), encoded...), 0))
65 require.Error(t, err)
66
67 got, err := UnmarshalRoutePosition(encoded)
68 require.NoError(t, err)
69 require.Equal(t, want, got)
70
71 _, err = MarshalRoutePosition(RoutePosition{Target: testExactTarget()})
72 require.ErrorIs(t, err, ErrInvalidRouteWire)
73 _, err = MarshalRoutePosition(RoutePosition{Target: testExactTarget(), ActiveTabID: "bad tab"})
74 require.ErrorIs(t, err, ErrInvalidRouteWire)
75}
76
77func TestWelcomeCarriesCommittedRouteIdentity(t *testing.T) {
78 identity := &CommittedRouteIdentity{Target: testExactTarget(), Ephemeral: true}
79 want := Welcome{SessionID: "daemon-session", SessionName: "work", Ephemeral: true, CommittedIdentity: identity}
80
81 got, err := UnmarshalWelcome(MarshalWelcome(want))
82 require.NoError(t, err)
83 require.Equal(t, want, got)
84}
85
86func TestWelcomeRejectsMismatchedCommittedIdentity(t *testing.T) {
87 identity := &CommittedRouteIdentity{Target: testExactTarget()}
88 payload := MarshalWelcome(Welcome{SessionID: "daemon-session", SessionName: "other", CommittedIdentity: identity})
89
90 require.Nil(t, payload)
91}
92
93func TestCommittedRouteIdentityCodec(t *testing.T) {
94 want := CommittedRouteIdentity{Target: testExactTarget(), Ephemeral: true}
95 encoded, err := MarshalCommittedRouteIdentity(want)
96 require.NoError(t, err)
97
98 require.Equal(t, "010203000000000000000000000000000004776f726b01", hex.EncodeToString(encoded))
99 assertAllPrefixesFail(t, encoded, UnmarshalCommittedRouteIdentity)
100 _, err = UnmarshalCommittedRouteIdentity(append(append([]byte(nil), encoded...), 0))
101 require.Error(t, err)
102
103 got, err := UnmarshalCommittedRouteIdentity(encoded)
104 require.NoError(t, err)
105 require.Equal(t, want, got)
106
107 mutated := append([]byte(nil), encoded...)
108 mutated[len(mutated)-1] = 2
109 _, err = UnmarshalCommittedRouteIdentity(mutated)
110 require.Error(t, err)
111}
112
113func testRouteSnapshot() RecentRouteSnapshot {
114 return RecentRouteSnapshot{
115 Generation: 9,
116 Active: RouteRef{Key: 11, Generation: 3},
117 Previous: RouteRef{Key: 12, Generation: 4},
118 Home: RouteRef{Key: 11, Generation: 3},
119 Entries: []RecentRouteEntry{
120 {Key: 12, Generation: 4, Name: "logs", HostLabel: "edge", Kind: RouteKindRemote, Attention: true, Reachability: RouteReachabilityUnknown},
121 },
122 }
123}
124
125func TestRecentRouteSnapshotCodecRoundTripAndBounds(t *testing.T) {
126 want := testRouteSnapshot()
127 encoded, err := MarshalRecentRouteSnapshot(want)
128 require.NoError(t, err)
129
130 got, err := UnmarshalRecentRouteSnapshot(encoded)
131 require.NoError(t, err)
132 require.Equal(t, want, got)
133
134 require.Equal(t, "0000000000000009000000000000000b0000000000000003000000000000000c0000000000000004000000000000000b000000000000000301000000000000000c000000000000000400046c6f677300046564676502000100", hex.EncodeToString(encoded))
135 assertAllPrefixesFail(t, encoded, UnmarshalRecentRouteSnapshot)
136 _, err = UnmarshalRecentRouteSnapshot(append(append([]byte(nil), encoded...), 0))
137 require.Error(t, err)
138
139 invalidCases := []struct {
140 name string
141 mutate func(*RecentRouteSnapshot)
142 }{
143 {name: "missing previous reference", mutate: func(snapshot *RecentRouteSnapshot) { snapshot.Previous = RouteRef{Key: 99, Generation: 99} }},
144 {name: "entry is active", mutate: func(snapshot *RecentRouteSnapshot) {
145 snapshot.Entries = append(snapshot.Entries, RecentRouteEntry{Key: 11, Generation: 3, Name: "work", Kind: RouteKindLocal})
146 }},
147 {name: "duplicate entry", mutate: func(snapshot *RecentRouteSnapshot) { snapshot.Entries = append(snapshot.Entries, snapshot.Entries[0]) }},
148 {name: "too many entries", mutate: func(snapshot *RecentRouteSnapshot) {
149 snapshot.Entries = make([]RecentRouteEntry, RouteSnapshotMaxEntries+1)
150 for i := range snapshot.Entries {
151 snapshot.Entries[i] = RecentRouteEntry{Key: uint64(i + 1), Generation: uint64(i + 1), Name: "x", Kind: RouteKindLocal}
152 }
153 }},
154 }
155 for _, tc := range invalidCases {
156 t.Run(tc.name, func(t *testing.T) {
157 invalid := want
158 tc.mutate(&invalid)
159 _, err := MarshalRecentRouteSnapshot(invalid)
160 require.ErrorIs(t, err, ErrInvalidRouteWire)
161 })
162 }
163}
164
165func TestRecentRouteSnapshotRejectsOversizedFrameBeforeParsing(t *testing.T) {
166 _, err := UnmarshalRecentRouteSnapshot(make([]byte, MaxFrameLen))
167 require.ErrorIs(t, err, ErrInvalidRouteWire)
168}
169
170func TestRouteLabelsRejectTerminalUnsafeText(t *testing.T) {
171 values := []string{"line\nfeed", "\u2028line-separator", "\u2029paragraph-separator", "\x1b[31mred", "\u202eoverride", string([]byte{0xff, 0xfe})}
172 for _, value := range values {
173 t.Run("name/"+value, func(t *testing.T) {
174 _, err := MarshalRecentRouteSnapshot(RecentRouteSnapshot{
175 Generation: 1,
176 Entries: []RecentRouteEntry{{Key: 1, Generation: 1, Name: value, Kind: RouteKindLocal}},
177 })
178 require.Error(t, err)
179 })
180 t.Run("host/"+value, func(t *testing.T) {
181 _, err := MarshalRecentRouteSnapshot(RecentRouteSnapshot{
182 Generation: 1,
183 Entries: []RecentRouteEntry{{Key: 1, Generation: 1, Name: "work", HostLabel: value, Kind: RouteKindRemote}},
184 })
185 require.Error(t, err)
186 })
187 }
188}
189
190func TestRouteActionAndFailureCodecsAreBounded(t *testing.T) {
191 action := RouteNavigationAction{SnapshotGeneration: 9, Key: 7, Generation: 8}
192 encoded, err := MarshalRouteNavigationAction(action)
193 require.NoError(t, err)
194 require.Equal(t, "000000000000000900000000000000070000000000000008", hex.EncodeToString(encoded))
195 assertAllPrefixesFail(t, encoded, UnmarshalRouteNavigationAction)
196 _, err = UnmarshalRouteNavigationAction(append(append([]byte(nil), encoded...), 0))
197 require.Error(t, err)
198 got, err := UnmarshalRouteNavigationAction(encoded)
199 require.NoError(t, err)
200 require.Equal(t, action, got)
201
202 failure := RouteNavigationFailure{Key: 7, Generation: 8, Code: RouteFailureStaleSelection}
203 encoded, err = MarshalRouteNavigationFailure(failure)
204 require.NoError(t, err)
205 require.Equal(t, "0000000000000007000000000000000801", hex.EncodeToString(encoded))
206 assertAllPrefixesFail(t, encoded, UnmarshalRouteNavigationFailure)
207 _, err = UnmarshalRouteNavigationFailure(append(append([]byte(nil), encoded...), 0))
208 require.Error(t, err)
209 gotFailure, err := UnmarshalRouteNavigationFailure(encoded)
210 require.NoError(t, err)
211 require.Equal(t, failure, gotFailure)
212
213 for _, invalid := range []RouteNavigationAction{{}, {SnapshotGeneration: 1, Key: 1}, {SnapshotGeneration: 1, Generation: 1}, {Key: 1, Generation: 1}} {
214 _, err := MarshalRouteNavigationAction(invalid)
215 require.Error(t, err)
216 }
217 for _, invalid := range []RouteNavigationFailure{{}, {Key: 1, Generation: 1}, {Key: 1, Generation: 1, Code: 255}} {
218 _, err := MarshalRouteNavigationFailure(invalid)
219 require.Error(t, err)
220 }
221}