main ece7682

proto_convert_range_test.go RawHistoryFolder

1package sessionwire
2
3import (
4	"testing"
5
6	"github.com/bnema/vev/internal/domain"
7	"github.com/bnema/vev/internal/domain/terminalcap"
8	"github.com/bnema/vev/internal/protocol"
9	"github.com/bnema/vev/internal/protocol/wire"
10	"github.com/stretchr/testify/require"
11)
12
13func testWireHello(t *testing.T) *wire.Hello {
14	t.Helper()
15	hello, err := helloToWire(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}})
16	require.NoError(t, err)
17	return hello
18}
19
20func testExactWireTarget() *wire.ExactTarget {
21	lifecycle := domain.SessionLifecycleID{1}
22	return &wire.ExactTarget{LifecycleId: &wire.LifecycleID{Value: lifecycle[:]}, SessionName: "work"}
23}
24
25// TestNarrowingConversionsRejectOverflow proves every wire uint32 -> smaller
26// semantic conversion is validated before the cast: hostile high bits can no
27// longer truncate into a valid enum or geometry range.
28func TestNarrowingConversionsRejectOverflow(t *testing.T) {
29	tests := []struct {
30		name    string
31		run     func(t *testing.T) error
32		wantErr error
33	}{
34		{
35			name: "hello environment policy",
36			run: func(t *testing.T) error {
37				hello := testWireHello(t)
38				hello.EnvironmentPolicy = 256
39				_, err := helloFromWire(hello)
40				return err
41			},
42			wantErr: errProtoConvertRange,
43		},
44		{
45			name: "hello navigation capabilities",
46			run: func(t *testing.T) error {
47				hello := testWireHello(t)
48				hello.NavigationCapabilities = 256
49				_, err := helloFromWire(hello)
50				return err
51			},
52			wantErr: errProtoConvertRange,
53		},
54		{
55			name: "hello output window",
56			run: func(t *testing.T) error {
57				hello := testWireHello(t)
58				hello.MaxOutputInFlight = 256
59				_, err := helloFromWire(hello)
60				return err
61			},
62			wantErr: errProtoConvertRange,
63		},
64		{
65			name: "hello terminal focus range",
66			run: func(t *testing.T) error {
67				hello := testWireHello(t)
68				hello.TerminalFocus = 256
69				_, err := helloFromWire(hello)
70				return err
71			},
72			wantErr: errProtoConvertRange,
73		},
74		{
75			name: "hello terminal focus taxonomy",
76			run: func(t *testing.T) error {
77				hello := testWireHello(t)
78				hello.TerminalFocus = 3
79				_, err := helloFromWire(hello)
80				return err
81			},
82			wantErr: protocol.ErrInvalidHello,
83		},
84		{
85			name: "hello color missing",
86			run: func(t *testing.T) error {
87				hello := testWireHello(t)
88				hello.Color = nil
89				_, err := helloFromWire(hello)
90				return err
91			},
92			wantErr: errProtoConvertRange,
93		},
94		{
95			name: "hello color mode unspecified",
96			run: func(t *testing.T) error {
97				hello := testWireHello(t)
98				hello.Color.Mode = wire.ColorMode_COLOR_MODE_UNSPECIFIED
99				_, err := helloFromWire(hello)
100				return err
101			},
102			wantErr: errProtoConvertRange,
103		},
104		{
105			name: "hello color mode unknown",
106			run: func(t *testing.T) error {
107				hello := testWireHello(t)
108				hello.Color.Mode = wire.ColorMode(5)
109				_, err := helloFromWire(hello)
110				return err
111			},
112			wantErr: errProtoConvertRange,
113		},
114		{
115			name: "hello color source unknown",
116			run: func(t *testing.T) error {
117				hello := testWireHello(t)
118				hello.Color.Source = wire.ColorSource(4)
119				_, err := helloFromWire(hello)
120				return err
121			},
122			wantErr: errProtoConvertRange,
123		},
124		{
125			name: "hello color mode not encodable",
126			run: func(t *testing.T) error {
127				_, err := helloToWire(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, Color: terminalcap.ColorCapabilities{Mode: terminalcap.ColorMode(9)}})
128				return err
129			},
130			wantErr: errProtoConvertRange,
131		},
132		{
133			name: "hello color source not encodable",
134			run: func(t *testing.T) error {
135				_, err := helloToWire(protocol.Hello{Version: protocol.Version, Intent: protocol.IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, Color: terminalcap.ColorCapabilities{Source: terminalcap.Source(9)}})
136				return err
137			},
138			wantErr: errProtoConvertRange,
139		},
140		{
141			name: "hello geometry",
142			run: func(t *testing.T) error {
143				hello := testWireHello(t)
144				hello.Cols = 70000
145				_, err := helloFromWire(hello)
146				return err
147			},
148			wantErr: errProtoConvertRange,
149		},
150		{
151			name: "attach target environment policy",
152			run: func(t *testing.T) error {
153				_, err := attachTargetFromWire(&wire.AttachTarget{Session: "work", Intent: uint32(protocol.IntentAttach), EnvironmentPolicy: 256})
154				return err
155			},
156			wantErr: protocol.ErrInvalidAttachTarget,
157		},
158		{
159			name: "attach target intent",
160			run: func(t *testing.T) error {
161				_, err := attachTargetFromWire(&wire.AttachTarget{Session: "work", EnvironmentPolicy: uint32(protocol.EnvironmentPolicyClientOwned), Intent: 256})
162				return err
163			},
164			wantErr: protocol.ErrInvalidAttachTarget,
165		},
166		{
167			name:    "kill scope aliasing",
168			run:     func(t *testing.T) error { _, err := killFromWire(&wire.Kill{Name: "work", Scope: 257}); return err },
169			wantErr: errProtoConvertRange,
170		},
171		{
172			name: "session state",
173			run: func(t *testing.T) error {
174				_, err := sessionsFromWire(&wire.Sessions{Sessions: []*wire.SessionInfo{{State: 256}}})
175				return err
176			},
177			wantErr: errProtoConvertRange,
178		},
179		{
180			name: "session tab count",
181			run: func(t *testing.T) error {
182				_, err := sessionsFromWire(&wire.Sessions{Sessions: []*wire.SessionInfo{{Tabs: 70000}}})
183				return err
184			},
185			wantErr: errProtoConvertRange,
186		},
187		{
188			name:    "ui receipt outcome aliasing",
189			run:     func(t *testing.T) error { _, err := uiReceiptFromWire(&wire.UIReceipt{Outcome: 257}); return err },
190			wantErr: errProtoConvertRange,
191		},
192		{
193			name: "recent route kind",
194			run: func(t *testing.T) error {
195				_, err := recentRouteEntryFromWire(&wire.RecentRouteEntry{Target: testExactWireTarget(), Kind: 256})
196				return err
197			},
198			wantErr: errProtoConvertRange,
199		},
200		{
201			name: "recent route reachability",
202			run: func(t *testing.T) error {
203				_, err := recentRouteEntryFromWire(&wire.RecentRouteEntry{Target: testExactWireTarget(), Reachability: 257})
204				return err
205			},
206			wantErr: errProtoConvertRange,
207		},
208		{
209			name: "route failure code",
210			run: func(t *testing.T) error {
211				_, err := navigationFailureFromWire(&wire.RouteNavigationFailure{Code: 256})
212				return err
213			},
214			wantErr: errProtoConvertRange,
215		},
216		{
217			name: "route failure decode identity",
218			run: func(t *testing.T) error {
219				_, err := navigationFailureFromWire(&wire.RouteNavigationFailure{Code: uint32(protocol.RouteFailureUnavailable)})
220				return err
221			},
222			wantErr: protocol.ErrInvalidRouteWire,
223		},
224		{
225			name: "route failure encode identity",
226			run: func(t *testing.T) error {
227				_, err := navigationFailureToWire(protocol.RouteNavigationFailure{Code: protocol.RouteFailureUnavailable})
228				return err
229			},
230			wantErr: protocol.ErrInvalidRouteWire,
231		},
232		{
233			name: "same peer switch failure code",
234			run: func(t *testing.T) error {
235				_, err := samePeerSwitchFailureFromWire(&wire.SamePeerSwitchFailure{Code: 256})
236				return err
237			},
238			wantErr: protocol.ErrInvalidRouteWire,
239		},
240		{
241			name: "session creation failure code",
242			run: func(t *testing.T) error {
243				_, err := sessionCreationFailureFromWire(&wire.SessionCreationFailure{Code: 256})
244				return err
245			},
246			wantErr: protocol.ErrInvalidRouteWire,
247		},
248		{
249			name: "inventory request operation",
250			run: func(t *testing.T) error {
251				_, err := inventoryRequestFromWire(&wire.NavigationInventoryRequest{Operation: 257})
252				return err
253			},
254			wantErr: protocol.ErrInvalidNavigation,
255		},
256		{
257			name: "inventory response status",
258			run: func(t *testing.T) error {
259				_, err := inventoryResponseFromWire(&wire.NavigationInventoryResponse{Operation: 1, Status: 257})
260				return err
261			},
262			wantErr: protocol.ErrInvalidNavigation,
263		},
264		{
265			name: "inventory failure code",
266			run: func(t *testing.T) error {
267				_, err := inventoryFailureFromWire(&wire.NavigationInventoryFailure{Code: 256})
268				return err
269			},
270			wantErr: protocol.ErrInvalidNavigation,
271		},
272		{
273			name: "inventory source status",
274			run: func(t *testing.T) error {
275				_, err := inventoryGroupsFromWire([]*wire.InventorySourceGroup{{Status: 257}})
276				return err
277			},
278			wantErr: errProtoConvertRange,
279		},
280		{
281			name:    "picker line kind",
282			run:     func(t *testing.T) error { _, err := pickerLineFromWire(&wire.PickerLine{Kind: 256}); return err },
283			wantErr: protocol.ErrInvalidNavigation,
284		},
285		{
286			name: "picker selection action",
287			run: func(t *testing.T) error {
288				_, err := pickerSelectionFromWire(&wire.PickerSelection{Action: 257})
289				return err
290			},
291			wantErr: errProtoConvertRange,
292		},
293		{
294			name: "remote preview status",
295			run: func(t *testing.T) error {
296				_, err := remotePreviewFromWire(&wire.RemotePreview{Status: 257})
297				return err
298			},
299			wantErr: protocol.ErrInvalidRemotePreview,
300		},
301	}
302	for _, tt := range tests {
303		t.Run(tt.name, func(t *testing.T) {
304			require.ErrorIs(t, tt.run(t), tt.wantErr)
305		})
306	}
307}
308
309func testWireRouteEntry(key, generation uint64) *wire.RecentRouteEntry {
310	return &wire.RecentRouteEntry{
311		Key:          key,
312		Generation:   generation,
313		Target:       testExactWireTarget(),
314		Name:         "work",
315		Kind:         uint32(protocol.RouteKindLocal),
316		Reachability: uint32(protocol.RouteReachabilityReachable),
317	}
318}
319
320func testSemanticRouteEntry(key, generation uint64) protocol.RecentRouteEntry {
321	lifecycle := domain.SessionLifecycleID{1}
322	return protocol.RecentRouteEntry{
323		Key:          key,
324		Generation:   generation,
325		Target:       protocol.ExactSessionTarget{LifecycleID: lifecycle, SessionName: "work"},
326		Name:         "work",
327		Kind:         protocol.RouteKindLocal,
328		Reachability: protocol.RouteReachabilityReachable,
329	}
330}
331
332// TestRouteSemanticBoundsRestored proves the old strict route codec bounds
333// and invariants are restored on both conversion directions: encode refuses
334// to emit a malformed value, decode refuses to accept one, and valid bounded
335// values still round-trip unchanged.
336func TestRouteSemanticBoundsRestored(t *testing.T) {
337	entry := testSemanticRouteEntry(1, 1)
338	ref := protocol.RouteRef{Key: 1, Generation: 1}
339	validSnapshot := protocol.RecentRouteSnapshot{Generation: 1, Entries: []protocol.RecentRouteEntry{entry}}
340	validSubscription := protocol.RouteAttentionSubscription{Targets: []protocol.RouteAttentionTarget{{Ref: ref, Target: entry.Target}}}
341	validAction := protocol.RouteNavigationAction{SnapshotGeneration: 1, Key: 1, Generation: 1}
342
343	oversized := validSnapshot
344	for len(oversized.Entries) <= protocol.RouteSnapshotMaxEntries {
345		next := uint64(len(oversized.Entries) + 1)
346		oversized.Entries = append(oversized.Entries, testSemanticRouteEntry(next, next))
347	}
348	mismatched := protocol.RecentRouteSnapshot{
349		Generation:  1,
350		Active:      protocol.RouteRef{Key: 2, Generation: 2},
351		ActiveEntry: entry,
352	}
353	duplicateSubscription := protocol.RouteAttentionSubscription{Targets: []protocol.RouteAttentionTarget{
354		{Ref: ref, Target: entry.Target},
355		{Ref: ref, Target: entry.Target},
356	}}
357
358	decodedEntries := make([]*wire.RecentRouteEntry, 0, protocol.RouteSnapshotMaxEntries+1)
359	for i := 1; i <= protocol.RouteSnapshotMaxEntries+1; i++ {
360		decodedEntries = append(decodedEntries, testWireRouteEntry(uint64(i), uint64(i)))
361	}
362
363	tests := []struct {
364		name    string
365		run     func(t *testing.T) error
366		wantErr error
367	}{
368		{
369			name: "encode oversized snapshot",
370			run: func(t *testing.T) error {
371				_, err := recentRouteSnapshotToWire(oversized)
372				return err
373			},
374			wantErr: protocol.ErrInvalidRouteWire,
375		},
376		{
377			name: "encode mismatched active presentation",
378			run: func(t *testing.T) error {
379				_, err := recentRouteSnapshotToWire(mismatched)
380				return err
381			},
382			wantErr: protocol.ErrInvalidRouteWire,
383		},
384		{
385			name: "encode snapshot missing home reference",
386			run: func(t *testing.T) error {
387				broken := validSnapshot
388				broken.Home = protocol.RouteRef{Key: 9, Generation: 9}
389				_, err := recentRouteSnapshotToWire(broken)
390				return err
391			},
392			wantErr: protocol.ErrInvalidRouteWire,
393		},
394		{
395			name: "encode duplicate attention reference",
396			run: func(t *testing.T) error {
397				_, err := attentionSubscriptionToWire(duplicateSubscription)
398				return err
399			},
400			wantErr: protocol.ErrInvalidRouteWire,
401		},
402		{
403			name: "encode zero navigation action",
404			run: func(t *testing.T) error {
405				_, err := routeNavigationActionToWire(protocol.RouteNavigationAction{})
406				return err
407			},
408			wantErr: protocol.ErrInvalidRouteWire,
409		},
410		{
411			name: "decode oversized snapshot",
412			run: func(t *testing.T) error {
413				_, err := recentRouteSnapshotFromWire(&wire.RecentRouteSnapshot{Generation: 1, Entries: decodedEntries})
414				return err
415			},
416			wantErr: protocol.ErrInvalidRouteWire,
417		},
418		{
419			name: "decode mismatched active presentation",
420			run: func(t *testing.T) error {
421				_, err := recentRouteSnapshotFromWire(&wire.RecentRouteSnapshot{
422					Generation:  1,
423					Active:      &wire.RouteRef{Key: 2, Generation: 2},
424					ActiveEntry: testWireRouteEntry(1, 1),
425				})
426				return err
427			},
428			wantErr: protocol.ErrInvalidRouteWire,
429		},
430		{
431			name: "decode duplicate attention reference",
432			run: func(t *testing.T) error {
433				_, err := attentionSubscriptionFromWire(&wire.RouteAttentionSubscription{Targets: []*wire.RouteAttentionTarget{
434					{Ref: &wire.RouteRef{Key: 1, Generation: 1}, Target: testExactWireTarget()},
435					{Ref: &wire.RouteRef{Key: 1, Generation: 1}, Target: testExactWireTarget()},
436				}})
437				return err
438			},
439			wantErr: protocol.ErrInvalidRouteWire,
440		},
441		{
442			name: "decode zero navigation action",
443			run: func(t *testing.T) error {
444				_, err := routeNavigationActionFromWire(&wire.RouteNavigationAction{})
445				return err
446			},
447			wantErr: protocol.ErrInvalidRouteWire,
448		},
449	}
450	for _, tt := range tests {
451		t.Run(tt.name, func(t *testing.T) {
452			require.ErrorIs(t, tt.run(t), tt.wantErr)
453		})
454	}
455
456	t.Run("valid snapshot round trip", func(t *testing.T) {
457		converted, err := recentRouteSnapshotToWire(validSnapshot)
458		require.NoError(t, err)
459		decoded, err := recentRouteSnapshotFromWire(converted)
460		require.NoError(t, err)
461		require.Equal(t, validSnapshot, decoded)
462	})
463	t.Run("valid attention subscription round trip", func(t *testing.T) {
464		converted, err := attentionSubscriptionToWire(validSubscription)
465		require.NoError(t, err)
466		decoded, err := attentionSubscriptionFromWire(converted)
467		require.NoError(t, err)
468		require.Equal(t, validSubscription, decoded)
469	})
470	t.Run("valid navigation action round trip", func(t *testing.T) {
471		converted, err := routeNavigationActionToWire(validAction)
472		require.NoError(t, err)
473		decoded, err := routeNavigationActionFromWire(converted)
474		require.NoError(t, err)
475		require.Equal(t, validAction, decoded)
476	})
477}

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