v0.2.0 80c61e3

routes_test.go RawHistoryFolder

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}

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