detached 4be85cc

test(routes): cover exact local handoffs

commit
4be85cc9fd0732689d780eaca25d2676273852e4
author
brice · · 2026-08-11 08:15 UTC
parent
9db7f4b
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +44 -0
M internal/ports/wire_final_test.go +23 -0
@@ -211,6 +211,29 @@ func TestFinalAttachTargetGoldenStrict(t *testing.T) {
211211 }
212212 }
213213
214+func TestAttachTargetExactTargetWireStrict(t *testing.T) {
215+ var lifecycle domain.SessionLifecycleID
216+ lifecycle[0] = 1
217+ target := AttachTarget{Session: "work", Intent: IntentAttach, ExactTarget: &ExactSessionTarget{LifecycleID: lifecycle, SessionName: "work"}}
218+ payload := MarshalAttachTarget(target)
219+ want := append([]byte{0, 0, 0, 4, 'w', 'o', 'r', 'k', IntentAttach, 0, 0, 1, 1}, make([]byte, 15)...)
220+ want = append(want, 0, 4, 'w', 'o', 'r', 'k')
221+ if !bytes.Equal(payload, want) {
222+ t.Fatalf("exact target bytes = %x, want %x", payload, want)
223+ }
224+ back, err := UnmarshalAttachTarget(payload)
225+ if err != nil || !reflect.DeepEqual(back, target) {
226+ t.Fatalf("exact target = %+v, error %v, want %+v", back, err, target)
227+ }
228+ assertAllPrefixesFail(t, payload, UnmarshalAttachTarget)
229+ assertTrailingGarbageFails(t, payload, UnmarshalAttachTarget)
230+ mismatched := target
231+ mismatched.ExactTarget = &ExactSessionTarget{LifecycleID: lifecycle, SessionName: "other"}
232+ if got := MarshalAttachTarget(mismatched); got != nil {
233+ t.Fatalf("MarshalAttachTarget accepted mismatched target: %x", got)
234+ }
235+}
236+
214237 func TestFinalClosedWireValuesRejectUnknownEnumsAndBooleans(t *testing.T) {
215238 tests := []struct {
216239 name string
@@ -211,6 +211,29 @@ func TestFinalAttachTargetGoldenStrict(t *testing.T) {
211 }211 }
212 }212 }
213 213
214+func TestAttachTargetExactTargetWireStrict(t *testing.T) {
215+ var lifecycle domain.SessionLifecycleID
216+ lifecycle[0] = 1
217+ target := AttachTarget{Session: "work", Intent: IntentAttach, ExactTarget: &ExactSessionTarget{LifecycleID: lifecycle, SessionName: "work"}}
218+ payload := MarshalAttachTarget(target)
219+ want := append([]byte{0, 0, 0, 4, 'w', 'o', 'r', 'k', IntentAttach, 0, 0, 1, 1}, make([]byte, 15)...)
220+ want = append(want, 0, 4, 'w', 'o', 'r', 'k')
221+ if !bytes.Equal(payload, want) {
222+ t.Fatalf("exact target bytes = %x, want %x", payload, want)
223+ }
224+ back, err := UnmarshalAttachTarget(payload)
225+ if err != nil || !reflect.DeepEqual(back, target) {
226+ t.Fatalf("exact target = %+v, error %v, want %+v", back, err, target)
227+ }
228+ assertAllPrefixesFail(t, payload, UnmarshalAttachTarget)
229+ assertTrailingGarbageFails(t, payload, UnmarshalAttachTarget)
230+ mismatched := target
231+ mismatched.ExactTarget = &ExactSessionTarget{LifecycleID: lifecycle, SessionName: "other"}
232+ if got := MarshalAttachTarget(mismatched); got != nil {
233+ t.Fatalf("MarshalAttachTarget accepted mismatched target: %x", got)
234+ }
235+}
236+
214 func TestFinalClosedWireValuesRejectUnknownEnumsAndBooleans(t *testing.T) {237 func TestFinalClosedWireValuesRejectUnknownEnumsAndBooleans(t *testing.T) {
215 tests := []struct {238 tests := []struct {
216 name string239 name string
M internal/usecase/daemon/remote_attach_test.go +21 -0
@@ -17,6 +17,27 @@ func remoteLifecycleForTest() domain.SessionLifecycleID {
1717 return id
1818 }
1919
20+func TestLocalPickerHandoffCarriesExactLifecycle(t *testing.T) {
21+ d := newRemotePickerDaemon(nil)
22+ source, ac, sends := addRemoteRefreshPickerOwner(t, d, "source")
23+ target, _, _ := addRemoteRefreshPickerOwner(t, d, "target")
24+ target.incarnation = remoteLifecycleForTest()
25+ token := source.attachmentToken(ac, ac.transport())
26+ effect, admitted := ac.beginAttachmentEffect(token)
27+ require.True(t, admitted)
28+ token.effect = effect
29+ defer effect.End()
30+
31+ require.NoError(t, d.sendLocalAttachTargetForAttachment(token, picker.Target{Session: target.id}, sessionHandoffGuard{}, "picker-select"))
32+ frame := receiveRemotePicker(t, sends, "local attach target")
33+ got, err := ports.UnmarshalAttachTarget(frame.Payload)
34+ require.NoError(t, err)
35+ require.Empty(t, got.Endpoint)
36+ require.Equal(t, ports.IntentAttach, got.Intent)
37+ require.Equal(t, &ports.ExactSessionTarget{LifecycleID: target.incarnation, SessionName: target.name}, got.ExactTarget)
38+ require.Nil(t, ac.currentAttachmentSession())
39+}
40+
2041 func TestRemotePickerRichHandoffCarriesLifecycleTabAndPolicy(t *testing.T) {
2142 d := newRemotePickerDaemon(nil)
2243 lifecycle := remoteLifecycleForTest()
@@ -17,6 +17,27 @@ func remoteLifecycleForTest() domain.SessionLifecycleID {
17 return id17 return id
18 }18 }
19 19
20+func TestLocalPickerHandoffCarriesExactLifecycle(t *testing.T) {
21+ d := newRemotePickerDaemon(nil)
22+ source, ac, sends := addRemoteRefreshPickerOwner(t, d, "source")
23+ target, _, _ := addRemoteRefreshPickerOwner(t, d, "target")
24+ target.incarnation = remoteLifecycleForTest()
25+ token := source.attachmentToken(ac, ac.transport())
26+ effect, admitted := ac.beginAttachmentEffect(token)
27+ require.True(t, admitted)
28+ token.effect = effect
29+ defer effect.End()
30+
31+ require.NoError(t, d.sendLocalAttachTargetForAttachment(token, picker.Target{Session: target.id}, sessionHandoffGuard{}, "picker-select"))
32+ frame := receiveRemotePicker(t, sends, "local attach target")
33+ got, err := ports.UnmarshalAttachTarget(frame.Payload)
34+ require.NoError(t, err)
35+ require.Empty(t, got.Endpoint)
36+ require.Equal(t, ports.IntentAttach, got.Intent)
37+ require.Equal(t, &ports.ExactSessionTarget{LifecycleID: target.incarnation, SessionName: target.name}, got.ExactTarget)
38+ require.Nil(t, ac.currentAttachmentSession())
39+}
40+
20 func TestRemotePickerRichHandoffCarriesLifecycleTabAndPolicy(t *testing.T) {41 func TestRemotePickerRichHandoffCarriesLifecycleTabAndPolicy(t *testing.T) {
21 d := newRemotePickerDaemon(nil)42 d := newRemotePickerDaemon(nil)
22 lifecycle := remoteLifecycleForTest()43 lifecycle := remoteLifecycleForTest()

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