v0.3.0 99dc266

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 TestRouteAttentionSubscriptionCodecIsStrict(t *testing.T) {
166	want := RouteAttentionSubscription{Targets: []RouteAttentionTarget{{
167		Ref:    RouteRef{Key: 7, Generation: 3},
168		Target: testExactTarget(),
169	}}}
170	encoded, err := MarshalRouteAttentionSubscription(want)
171	require.NoError(t, err)
172	require.Equal(t, "0100000000000000070000000000000003010203000000000000000000000000000004776f726b", hex.EncodeToString(encoded))
173	assertAllPrefixesFail(t, encoded, UnmarshalRouteAttentionSubscription)
174	_, err = UnmarshalRouteAttentionSubscription(append(append([]byte(nil), encoded...), 0))
175	require.Error(t, err)
176
177	got, err := UnmarshalRouteAttentionSubscription(encoded)
178	require.NoError(t, err)
179	require.Equal(t, want, got)
180
181	_, err = MarshalRouteAttentionSubscription(RouteAttentionSubscription{Targets: []RouteAttentionTarget{
182		want.Targets[0], want.Targets[0],
183	}})
184	require.ErrorIs(t, err, ErrInvalidRouteWire)
185}
186
187func TestRecentRouteSnapshotRejectsOversizedFrameBeforeParsing(t *testing.T) {
188	_, err := UnmarshalRecentRouteSnapshot(make([]byte, MaxFrameLen))
189	require.ErrorIs(t, err, ErrInvalidRouteWire)
190}
191
192func TestRouteLabelsRejectTerminalUnsafeText(t *testing.T) {
193	values := []string{"line\nfeed", "\u2028line-separator", "\u2029paragraph-separator", "\x1b[31mred", "\u202eoverride", string([]byte{0xff, 0xfe})}
194	for _, value := range values {
195		t.Run("name/"+value, func(t *testing.T) {
196			_, err := MarshalRecentRouteSnapshot(RecentRouteSnapshot{
197				Generation: 1,
198				Entries:    []RecentRouteEntry{{Key: 1, Generation: 1, Name: value, Kind: RouteKindLocal}},
199			})
200			require.Error(t, err)
201		})
202		t.Run("host/"+value, func(t *testing.T) {
203			_, err := MarshalRecentRouteSnapshot(RecentRouteSnapshot{
204				Generation: 1,
205				Entries:    []RecentRouteEntry{{Key: 1, Generation: 1, Name: "work", HostLabel: value, Kind: RouteKindRemote}},
206			})
207			require.Error(t, err)
208		})
209	}
210}
211
212func TestRouteActionAndFailureCodecsAreBounded(t *testing.T) {
213	action := RouteNavigationAction{SnapshotGeneration: 9, Key: 7, Generation: 8}
214	encoded, err := MarshalRouteNavigationAction(action)
215	require.NoError(t, err)
216	require.Equal(t, "000000000000000900000000000000070000000000000008", hex.EncodeToString(encoded))
217	assertAllPrefixesFail(t, encoded, UnmarshalRouteNavigationAction)
218	_, err = UnmarshalRouteNavigationAction(append(append([]byte(nil), encoded...), 0))
219	require.Error(t, err)
220	got, err := UnmarshalRouteNavigationAction(encoded)
221	require.NoError(t, err)
222	require.Equal(t, action, got)
223
224	failure := RouteNavigationFailure{Key: 7, Generation: 8, Code: RouteFailureStaleSelection}
225	encoded, err = MarshalRouteNavigationFailure(failure)
226	require.NoError(t, err)
227	require.Equal(t, "0000000000000007000000000000000801", hex.EncodeToString(encoded))
228	assertAllPrefixesFail(t, encoded, UnmarshalRouteNavigationFailure)
229	_, err = UnmarshalRouteNavigationFailure(append(append([]byte(nil), encoded...), 0))
230	require.Error(t, err)
231	gotFailure, err := UnmarshalRouteNavigationFailure(encoded)
232	require.NoError(t, err)
233	require.Equal(t, failure, gotFailure)
234
235	for _, invalid := range []RouteNavigationAction{{}, {SnapshotGeneration: 1, Key: 1}, {SnapshotGeneration: 1, Generation: 1}, {Key: 1, Generation: 1}} {
236		_, err := MarshalRouteNavigationAction(invalid)
237		require.Error(t, err)
238	}
239	for _, invalid := range []RouteNavigationFailure{{}, {Key: 1, Generation: 1}, {Key: 1, Generation: 1, Code: 255}} {
240		_, err := MarshalRouteNavigationFailure(invalid)
241		require.Error(t, err)
242	}
243}

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