v0.2.0 80c61e3

wire_final_test.go RawHistoryFolder

1package ports
2
3import (
4	"bytes"
5	"encoding/binary"
6	"errors"
7	"reflect"
8	"testing"
9
10	"github.com/bnema/vev/internal/domain"
11)
12
13func TestFinalProtocolVersionAndNoV21Hello(t *testing.T) {
14	if ProtocolVersion != 29 {
15		t.Fatalf("ProtocolVersion = %d, want 29", ProtocolVersion)
16	}
17	payload := MarshalHello(Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}})
18	if len(payload) < 2 {
19		t.Fatal("MarshalHello produced a truncated valid payload")
20	}
21	binary.BigEndian.PutUint16(payload, 21)
22	if _, err := UnmarshalHello(payload); !errors.Is(err, ErrInvalidHello) {
23		t.Fatalf("UnmarshalHello(v21) error = %v, want ErrInvalidHello", err)
24	}
25}
26
27func TestFinalHelloGoldenStrict(t *testing.T) {
28	msg := Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}}
29	want := append([]byte{0, 29, 2}, make([]byte, 16+8)...)
30	want = append(want, 0, 0, 0, 80, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
31	got := MarshalHello(msg)
32	if !bytes.Equal(got, want) {
33		t.Fatalf("Hello bytes = %x, want %x", got, want)
34	}
35	back, err := UnmarshalHello(got)
36	if err != nil {
37		t.Fatalf("UnmarshalHello() error = %v", err)
38	}
39	if !reflect.DeepEqual(back, msg) {
40		t.Fatalf("Hello = %+v, want %+v", back, msg)
41	}
42	assertAllPrefixesFail(t, got, UnmarshalHello)
43	assertTrailingGarbageFails(t, got, UnmarshalHello)
44}
45
46func TestFinalHelloSemanticValidation(t *testing.T) {
47	valid := Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}}
48	for _, tt := range []struct {
49		name   string
50		mutate func(*Hello)
51	}{
52		{name: "intent", mutate: func(h *Hello) { h.Intent = 99 }},
53		{name: "zero columns", mutate: func(h *Hello) { h.Size.Cols = 0 }},
54		{name: "zero rows", mutate: func(h *Hello) { h.Size.Rows = 0 }},
55		{name: "area", mutate: func(h *Hello) { h.Size = domain.Size{Cols: 513, Rows: 512} }},
56	} {
57		t.Run(tt.name, func(t *testing.T) {
58			msg := valid
59			tt.mutate(&msg)
60			if err := ValidateHello(msg); err == nil {
61				t.Fatal("ValidateHello accepted malformed semantics")
62			}
63		})
64	}
65}
66
67func TestFinalResizeGoldenStrict(t *testing.T) {
68	msg := Resize{Size: domain.Size{Cols: 80, Rows: 24}}
69	want := []byte{0, 80, 0, 24}
70	got, err := MarshalResize(msg)
71	if err != nil {
72		t.Fatalf("MarshalResize() error = %v", err)
73	}
74	if !bytes.Equal(got, want) {
75		t.Fatalf("Resize bytes = %x, want %x", got, want)
76	}
77	back, err := UnmarshalResize(got)
78	if err != nil || back != msg {
79		t.Fatalf("Resize = %+v, error %v, want %+v", back, err, msg)
80	}
81	assertAllPrefixesFail(t, got, UnmarshalResize)
82	assertTrailingGarbageFails(t, got, UnmarshalResize)
83	for _, size := range []domain.Size{{}, {Cols: 513, Rows: 512}} {
84		if got, err := MarshalResize(Resize{Size: size}); err == nil || got != nil {
85			t.Fatalf("MarshalResize(%+v) = (%x, %v), want nil payload and error", size, got, err)
86		}
87	}
88}
89
90func TestFinalOutputGoldenStrict(t *testing.T) {
91	msg := Output{
92		Epoch: 1, Base: 0, New: 7, Echo: 8, ViewRevision: 9,
93		Size: domain.Size{Cols: 80, Rows: 24}, Full: true, Data: []byte("ok"),
94	}
95	want := []byte{
96		0, 0, 0, 0, 0, 0, 0, 1,
97		0, 0, 0, 0, 0, 0, 0, 0,
98		0, 0, 0, 0, 0, 0, 0, 7,
99		0, 0, 0, 0, 0, 0, 0, 8,
100		0, 0, 0, 0, 0, 0, 0, 9,
101		0, 80, 0, 24, 1,
102		0, 0, 0, 2, 'o', 'k',
103	}
104	got, err := MarshalOutput(msg)
105	if err != nil {
106		t.Fatalf("MarshalOutput() error = %v", err)
107	}
108	if !bytes.Equal(got, want) {
109		t.Fatalf("Output bytes = %x, want %x", got, want)
110	}
111	back, err := UnmarshalOutput(got)
112	if err != nil || back.Epoch != msg.Epoch || back.Base != msg.Base || back.New != msg.New || back.Echo != msg.Echo || back.ViewRevision != msg.ViewRevision || back.Size != msg.Size || back.Full != msg.Full || !bytes.Equal(back.Data, msg.Data) {
113		t.Fatalf("Output = %+v, error %v, want %+v", back, err, msg)
114	}
115	assertAllPrefixesFail(t, got, UnmarshalOutput)
116	assertTrailingGarbageFails(t, got, UnmarshalOutput)
117}
118
119func TestFinalOutputSemanticValidationBeforeDataAllocation(t *testing.T) {
120	valid := Output{Epoch: 1, Base: 0, New: 1, Size: domain.Size{Cols: 80, Rows: 24}, Full: true}
121	for _, bad := range []Output{
122		{},
123		{Epoch: 1, Base: 0, New: 1, Size: valid.Size},
124		{Epoch: 1, Base: 0, New: 1, Full: true},
125	} {
126		if got, err := MarshalOutput(bad); err == nil || got != nil {
127			t.Fatalf("MarshalOutput(%+v) = (%x, %v), want nil payload and error", bad, got, err)
128		}
129	}
130	payload, err := MarshalOutput(valid)
131	if err != nil {
132		t.Fatal(err)
133	}
134	for _, tt := range []struct {
135		name   string
136		mutate func([]byte)
137	}{
138		{name: "zero epoch", mutate: func(b []byte) { b[7] = 0 }},
139		{name: "new does not advance", mutate: func(b []byte) { binary.BigEndian.PutUint64(b[16:24], 0) }},
140		{name: "size zero", mutate: func(b []byte) { binary.BigEndian.PutUint16(b[40:42], 0) }},
141		{name: "full flag false for reset", mutate: func(b []byte) { b[44] = 0 }},
142		{name: "invalid bool", mutate: func(b []byte) { b[44] = 2 }},
143		{name: "impossible data length", mutate: func(b []byte) { binary.BigEndian.PutUint32(b[45:49], ^uint32(0)) }},
144	} {
145		t.Run(tt.name, func(t *testing.T) {
146			bad := append([]byte(nil), payload...)
147			tt.mutate(bad)
148			if _, err := UnmarshalOutput(bad); err == nil {
149				t.Fatal("UnmarshalOutput accepted malformed semantics")
150			}
151		})
152	}
153}
154
155func TestFinalAckGoldenStrict(t *testing.T) {
156	msg := Ack{Epoch: 2, State: 7}
157	want := []byte{0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 7}
158	got, err := MarshalAck(msg)
159	if err != nil {
160		t.Fatalf("MarshalAck() error = %v", err)
161	}
162	if !bytes.Equal(got, want) {
163		t.Fatalf("Ack bytes = %x, want %x", got, want)
164	}
165	back, err := UnmarshalAck(got)
166	if err != nil || back != (Ack{Epoch: 2, State: 7}) {
167		t.Fatalf("Ack = %+v, error %v", back, err)
168	}
169	assertAllPrefixesFail(t, got, UnmarshalAck)
170	assertTrailingGarbageFails(t, got, UnmarshalAck)
171	if _, err := UnmarshalAck(make([]byte, 16)); err == nil {
172		t.Fatal("UnmarshalAck accepted zero epoch")
173	}
174	if got, err := MarshalAck(Ack{State: 7}); err == nil || got != nil {
175		t.Fatalf("MarshalAck accepted zero epoch: payload=%x err=%v", got, err)
176	}
177}
178
179func TestFinalAttachTargetGoldenStrict(t *testing.T) {
180	msg := AttachTarget{Endpoint: "host", Session: "work", Intent: IntentAttach}
181	want := []byte{0, 4, 'h', 'o', 's', 't', 0, 4, 'w', 'o', 'r', 'k', 2, 0, 0}
182	got := MarshalAttachTarget(msg)
183	if !bytes.Equal(got, want) {
184		t.Fatalf("AttachTarget bytes = %x, want %x", got, want)
185	}
186	back, err := UnmarshalAttachTarget(got)
187	if err != nil || back != msg {
188		t.Fatalf("AttachTarget = %+v, error %v, want %+v", back, err, msg)
189	}
190	assertAllPrefixesFail(t, got, UnmarshalAttachTarget)
191	assertTrailingGarbageFails(t, got, UnmarshalAttachTarget)
192	for _, bad := range []AttachTarget{{Session: "work", Intent: IntentAttach}, {Endpoint: "host", Intent: IntentAttach}, {Endpoint: "host", Session: "work", Intent: 99}} {
193		if got := MarshalAttachTarget(bad); got != nil {
194			t.Fatalf("MarshalAttachTarget(%+v) = %x, want nil", bad, got)
195		}
196	}
197}
198
199func TestFinalClosedWireValuesRejectUnknownEnumsAndBooleans(t *testing.T) {
200	tests := []struct {
201		name    string
202		payload []byte
203		decode  func([]byte) error
204	}{
205		{
206			name: "hello boolean",
207			payload: func() []byte {
208				b := MarshalHello(Hello{Version: ProtocolVersion, Size: domain.Size{Cols: 1, Rows: 1}})
209				b[len(b)-8] = 2
210				return b
211			}(),
212			decode: func(b []byte) error { _, err := UnmarshalHello(b); return err },
213		},
214		{
215			name: "welcome boolean",
216			payload: func() []byte {
217				b := MarshalWelcome(Welcome{SessionID: "id"})
218				b[6] = 2
219				return b
220			}(),
221			decode: func(b []byte) error { _, err := UnmarshalWelcome(b); return err },
222		},
223		{name: "detached reason", payload: []byte{0xff}, decode: func(b []byte) error { _, err := UnmarshalDetached(b); return err }},
224		{
225			name: "kill boolean",
226			payload: func() []byte {
227				b := MarshalKill(Kill{All: true})
228				b[len(b)-1] = 2
229				return b
230			}(),
231			decode: func(b []byte) error { _, err := UnmarshalKill(b); return err },
232		},
233		{
234			name: "sessions ephemeral boolean",
235			payload: func() []byte {
236				b := MarshalSessions(Sessions{Sessions: []SessionInfo{{State: SessionUp}}})
237				b[6] = 2
238				return b
239			}(),
240			decode: func(b []byte) error { _, err := UnmarshalSessions(b); return err },
241		},
242		{
243			name: "sessions attached boolean",
244			payload: func() []byte {
245				b := MarshalSessions(Sessions{Sessions: []SessionInfo{{State: SessionUp}}})
246				b[9] = 2
247				return b
248			}(),
249			decode: func(b []byte) error { _, err := UnmarshalSessions(b); return err },
250		},
251		{name: "theme flags", payload: func() []byte { b := make([]byte, 57); b[0] = 0x20; return b }(), decode: func(b []byte) error { _, err := UnmarshalTheme(b); return err }},
252	}
253	for _, tt := range tests {
254		t.Run(tt.name, func(t *testing.T) {
255			if err := tt.decode(tt.payload); err == nil {
256				t.Fatal("decoder accepted unknown closed wire value")
257			}
258		})
259	}
260}

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