v0.3.0 99dc266

remote_target_wire_test.go RawHistoryFolder

1package ports
2
3import (
4	"bytes"
5	"errors"
6	"testing"
7
8	"github.com/bnema/vev/internal/domain"
9)
10
11func testRemoteTarget(stopped bool) *domain.RemoteSessionTarget {
12	var lifecycle domain.SessionLifecycleID
13	for i := range lifecycle {
14		lifecycle[i] = byte(i + 1)
15	}
16	target := &domain.RemoteSessionTarget{
17		Endpoint:      "build@mule:2222",
18		DisplayOrigin: "mule:2222",
19		LifecycleID:   lifecycle,
20		SessionName:   "work",
21		Stopped:       stopped,
22	}
23	if stopped {
24		target.StoppedTab = domain.NewOrdinalTabSelector(2, "", 4)
25	} else {
26		target.LiveTabID = "tab-3"
27	}
28	return target
29}
30
31func TestRemoteTargetWireRoundTripPreservesExactSelector(t *testing.T) {
32	for _, stopped := range []bool{false, true} {
33		name := "live"
34		if stopped {
35			name = "stopped"
36		}
37		t.Run(name, func(t *testing.T) {
38			target := testRemoteTarget(stopped)
39			msg := Hello{
40				Version:           ProtocolVersion,
41				Intent:            IntentAttach,
42				Name:              target.SessionName,
43				Size:              domain.Size{Cols: 80, Rows: 24},
44				RemoteTarget:      target,
45				EnvironmentPolicy: EnvironmentPolicyDaemonOwned,
46			}
47			payload := MarshalHello(msg)
48			if payload == nil {
49				t.Fatal("MarshalHello returned nil")
50			}
51			got, err := UnmarshalHello(payload)
52			if err != nil {
53				t.Fatal(err)
54			}
55			if got.RemoteTarget == nil || *got.RemoteTarget != *target {
56				t.Fatalf("remote target = %#v, want %#v", got.RemoteTarget, target)
57			}
58			if got.EnvironmentPolicy != EnvironmentPolicyDaemonOwned {
59				t.Fatalf("environment policy = %d, want daemon-owned", got.EnvironmentPolicy)
60			}
61
62			attachPayload := MarshalAttachTarget(AttachTarget{
63				Endpoint:          target.Endpoint,
64				Session:           target.SessionName,
65				Intent:            IntentAttach,
66				RemoteTarget:      target,
67				EnvironmentPolicy: EnvironmentPolicyDaemonOwned,
68			})
69			if attachPayload == nil {
70				t.Fatal("MarshalAttachTarget returned nil")
71			}
72			attach, err := UnmarshalAttachTarget(attachPayload)
73			if err != nil {
74				t.Fatal(err)
75			}
76			if attach.RemoteTarget == nil || *attach.RemoteTarget != *target {
77				t.Fatalf("attach target = %#v, want %#v", attach.RemoteTarget, target)
78			}
79		})
80	}
81}
82
83func TestRemoteTargetWireRejectsClientOwnedEnvironmentPolicy(t *testing.T) {
84	target := testRemoteTarget(false)
85	tests := []struct {
86		name         string
87		validate     func() error
88		marshalBad   func() []byte
89		marshalGood  func() []byte
90		unmarshal    func([]byte) error
91		policyOffset int
92		wantErr      error
93	}{
94		{
95			name: "hello",
96			validate: func() error {
97				return ValidateHello(Hello{
98					Version: ProtocolVersion, Intent: IntentAttach,
99					Name: target.SessionName, Size: domain.Size{Cols: 80, Rows: 24},
100					RemoteTarget: target, EnvironmentPolicy: EnvironmentPolicyClientOwned,
101				})
102			},
103			marshalBad: func() []byte {
104				return MarshalHello(Hello{
105					Version: ProtocolVersion, Intent: IntentAttach,
106					Name: target.SessionName, Size: domain.Size{Cols: 80, Rows: 24},
107					RemoteTarget: target, EnvironmentPolicy: EnvironmentPolicyClientOwned,
108				})
109			},
110			marshalGood: func() []byte {
111				return MarshalHello(Hello{
112					Version: ProtocolVersion, Intent: IntentAttach,
113					Name: target.SessionName, Size: domain.Size{Cols: 80, Rows: 24},
114					RemoteTarget: target, EnvironmentPolicy: EnvironmentPolicyDaemonOwned,
115				})
116			},
117			unmarshal:    func(payload []byte) error { _, err := UnmarshalHello(payload); return err },
118			policyOffset: 8,
119			wantErr:      ErrInvalidHello,
120		},
121		{
122			name: "attach target",
123			validate: func() error {
124				return ValidateAttachTarget(AttachTarget{
125					Endpoint: target.Endpoint, Session: target.SessionName, Intent: IntentAttach,
126					RemoteTarget: target, EnvironmentPolicy: EnvironmentPolicyClientOwned,
127				})
128			},
129			marshalBad: func() []byte {
130				return MarshalAttachTarget(AttachTarget{
131					Endpoint: target.Endpoint, Session: target.SessionName, Intent: IntentAttach,
132					RemoteTarget: target, EnvironmentPolicy: EnvironmentPolicyClientOwned,
133				})
134			},
135			marshalGood: func() []byte {
136				return MarshalAttachTarget(AttachTarget{
137					Endpoint: target.Endpoint, Session: target.SessionName, Intent: IntentAttach,
138					RemoteTarget: target, EnvironmentPolicy: EnvironmentPolicyDaemonOwned,
139				})
140			},
141			unmarshal:    func(payload []byte) error { _, err := UnmarshalAttachTarget(payload); return err },
142			policyOffset: 2,
143			wantErr:      ErrInvalidAttachTarget,
144		},
145	}
146
147	for _, tt := range tests {
148		t.Run(tt.name, func(t *testing.T) {
149			if err := tt.validate(); !errors.Is(err, tt.wantErr) {
150				t.Fatalf("direct validation error = %v, want %v", err, tt.wantErr)
151			}
152			if payload := tt.marshalBad(); payload != nil {
153				t.Fatalf("marshal accepted client-owned policy: %x", payload)
154			}
155			payload := tt.marshalGood()
156			if payload == nil {
157				t.Fatal("failed to marshal daemon-owned control payload")
158			}
159			for i := range len(payload) {
160				if err := tt.unmarshal(payload[:i]); err == nil {
161					t.Fatalf("prefix %d unexpectedly decoded", i)
162				}
163			}
164			trailing := append(append([]byte(nil), payload...), 0xff)
165			if err := tt.unmarshal(trailing); err == nil {
166				t.Fatal("trailing garbage unexpectedly decoded")
167			}
168			payload[len(payload)-tt.policyOffset] = byte(EnvironmentPolicyClientOwned)
169			if err := tt.unmarshal(payload); !errors.Is(err, tt.wantErr) {
170				t.Fatalf("mutated policy error = %v, want %v", err, tt.wantErr)
171			}
172		})
173	}
174}
175
176func TestAttachTargetRejectsResumeIntent(t *testing.T) {
177	tests := []AttachTarget{
178		{Endpoint: "host", Session: "work", Intent: IntentResume},
179		{
180			Endpoint:          "build@mule:2222",
181			Session:           "work",
182			Intent:            IntentResume,
183			RemoteTarget:      testRemoteTarget(false),
184			EnvironmentPolicy: EnvironmentPolicyDaemonOwned,
185		},
186	}
187	for _, target := range tests {
188		if err := ValidateAttachTarget(target); !errors.Is(err, ErrInvalidAttachTarget) {
189			t.Fatalf("ValidateAttachTarget(%#v) error = %v, want %v", target, err, ErrInvalidAttachTarget)
190		}
191		if got := MarshalAttachTarget(target); got != nil {
192			t.Fatalf("MarshalAttachTarget(%#v) = %x, want nil", target, got)
193		}
194	}
195}
196
197func TestTargetWireIncludesRequiredV27Section(t *testing.T) {
198	want := []byte{0, 4, 'h', 'o', 's', 't', 0, 4, 'w', 'o', 'r', 'k', IntentAttach, 0, 0, 0}
199	got := MarshalAttachTarget(AttachTarget{Endpoint: "host", Session: "work", Intent: IntentAttach})
200	if !bytes.Equal(got, want) {
201		t.Fatalf("target bytes = %x, want %x", got, want)
202	}
203}

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