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

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