1package daemon
2
3import (
4 "context"
5 "testing"
6
7 "github.com/stretchr/testify/require"
8
9 "github.com/bnema/vev/internal/domain"
10 "github.com/bnema/vev/internal/ports"
11)
12
13func TestRouteRemoteTargetSelectsExactLiveTab(t *testing.T) {
14 d := newTestDaemon(t, nil, stubClock{})
15 sess := addControlSession(d, "work", "tab-1", "pane-1")
16 sess.ephemeral = false
17 sess.incarnation = remoteLifecycleForTest()
18 tr, _ := newCapturingTransport(t)
19 target := domain.RemoteSessionTarget{Endpoint: "arch", DisplayOrigin: "arch", LifecycleID: sess.incarnation, SessionName: "work", LiveTabID: "tab-1"}
20 hello := ports.Hello{Version: ports.ProtocolVersion, Intent: ports.IntentAttach, Name: "work", Size: domain.Size{Cols: 80, Rows: 24}, RemoteTarget: &target, EnvironmentPolicy: ports.EnvironmentPolicyDaemonOwned}
21 _, ac, err := d.routeWithContext(context.Background(), hello, tr)
22 require.NoError(t, err)
23 require.NotNil(t, ac)
24 require.Same(t, sess, ac.currentAttachmentSession())
25 require.Equal(t, domain.TabStableID("tab-1"), ac.viewSnapshot().tabID)
26 d.clientGone(sess, ac, tr, false)
27}
28
29func TestFinishRouteAttachRollsBackCreatedSession(t *testing.T) {
30 pty, release := newBlockingPTY(t)
31 defer release()
32 d := newTestDaemon(t, newFactory(t, pty), stubClock{})
33 sess, err := createSessionForTest(d, "work", false, "/tmp/work", defaultSize, terminalEnv{}, d.baseEnv)
34 require.NoError(t, err)
35 target := domain.RemoteSessionTarget{LifecycleID: sess.incarnation, SessionName: "work", LiveTabID: "missing-tab"}
36 hello := ports.Hello{
37 Version: ports.ProtocolVersion, Intent: ports.IntentAttach, Name: "work", Size: defaultSize,
38 RemoteTarget: &target, EnvironmentPolicy: ports.EnvironmentPolicyDaemonOwned,
39 }
40 // The caller must hold d.mu on entry; finishRouteAttach releases it on
41 // both success and error paths before returning.
42 d.mu.Lock()
43 _, err = d.finishRouteAttach(sess, &closeTrackingTransport{}, defaultSize, terminalEnv{}, hello, true, true)
44 var protocol *protoErr
45 require.ErrorAs(t, err, &protocol)
46 require.Equal(t, ports.ErrNoSuchTarget, protocol.code)
47 d.mu.Lock()
48 _, retained := d.sessions[sess.id]
49 d.mu.Unlock()
50 require.False(t, retained, "failed route attach must remove its newly created session")
51}
52
53func TestRouteRemoteTargetRejectsSameNameReplacement(t *testing.T) {
54 d := newTestDaemon(t, nil, stubClock{})
55 sess := addControlSession(d, "work", "tab-new", "pane-new")
56 sess.ephemeral = false
57 sess.incarnation = remoteLifecycleForTest()
58 tr, _ := newCapturingTransport(t)
59 var old domain.SessionLifecycleID
60 old[0] = 8
61 target := domain.RemoteSessionTarget{Endpoint: "arch", DisplayOrigin: "arch", LifecycleID: old, SessionName: "work", LiveTabID: "tab-new"}
62 hello := ports.Hello{Version: ports.ProtocolVersion, Intent: ports.IntentAttach, Name: "work", Size: domain.Size{Cols: 80, Rows: 24}, RemoteTarget: &target, EnvironmentPolicy: ports.EnvironmentPolicyDaemonOwned}
63 _, _, err := d.routeWithContext(context.Background(), hello, tr)
64 var protocol *protoErr
65 require.ErrorAs(t, err, &protocol)
66 require.Equal(t, ports.ErrNoSuchTarget, protocol.code)
67}