1package daemon
2
3import (
4 "context"
5 "testing"
6
7 "github.com/bnema/vev/internal/domain"
8 "github.com/bnema/vev/internal/ports"
9 "github.com/stretchr/testify/require"
10)
11
12func TestRouteExactSessionTargetSelectsLifecycle(t *testing.T) {
13 d := newTestDaemon(t, nil, stubClock{})
14 sess := addControlSession(d, "work", "tab-1", "pane-1")
15 sess.ephemeral = false
16 sess.incarnation = remoteLifecycleForTest()
17 tr, _ := newCapturingTransport(t)
18 target := ports.ExactSessionTarget{LifecycleID: sess.incarnation, SessionName: sess.name}
19 hello := ports.Hello{
20 Version: ports.ProtocolVersion,
21 Intent: ports.IntentAttach,
22 Name: target.SessionName,
23 Size: domain.Size{Cols: 80, Rows: 24},
24 ExactTarget: &target,
25 }
26
27 routed, ac, err := d.routeWithContext(context.Background(), hello, tr)
28 require.NoError(t, err)
29 require.Same(t, sess, routed)
30 require.Same(t, sess, ac.currentAttachmentSession())
31 d.clientGone(sess, ac, tr, false)
32}
33
34func TestRouteExactSessionTargetRejectsNameMismatch(t *testing.T) {
35 d := newTestDaemon(t, nil, stubClock{})
36 sess := addControlSession(d, "work", "tab-1", "pane-1")
37 sess.ephemeral = false
38 sess.incarnation = remoteLifecycleForTest()
39 tr, _ := newCapturingTransport(t)
40 hello := ports.Hello{
41 Version: ports.ProtocolVersion,
42 Intent: ports.IntentAttach,
43 Name: "other",
44 Size: domain.Size{Cols: 80, Rows: 24},
45 ExactTarget: &ports.ExactSessionTarget{LifecycleID: sess.incarnation, SessionName: sess.name},
46 }
47
48 _, _, err := d.routeWithContext(context.Background(), hello, tr)
49 var protocol *protoErr
50 require.ErrorAs(t, err, &protocol)
51 require.Equal(t, ports.ErrNoSuchSession, protocol.code)
52}
53
54func TestLockedExactSessionTargetRejectsReplacement(t *testing.T) {
55 d := newTestDaemon(t, nil, stubClock{})
56 sess := addControlSession(d, "work", "tab-1", "pane-1")
57 sess.ephemeral = false
58 sess.incarnation = remoteLifecycleForTest()
59 target := ports.ExactSessionTarget{LifecycleID: sess.incarnation, SessionName: sess.name}
60 replacement := sess.incarnation
61 replacement[0]++
62 sess.incarnation = replacement
63
64 d.mu.Lock()
65 err := d.validateExactSessionTargetLocked(target)
66 d.mu.Unlock()
67
68 var protocol *protoErr
69 require.ErrorAs(t, err, &protocol)
70 require.Equal(t, ports.ErrNoSuchSession, protocol.code)
71}
72
73func TestRouteExactSessionTargetRejectsLifecycleReplacement(t *testing.T) {
74 d := newTestDaemon(t, nil, stubClock{})
75 sess := addControlSession(d, "work", "tab-1", "pane-1")
76 sess.ephemeral = false
77 sess.incarnation = remoteLifecycleForTest()
78 tr, _ := newCapturingTransport(t)
79 wrong := sess.incarnation
80 wrong[0]++
81 hello := ports.Hello{
82 Version: ports.ProtocolVersion,
83 Intent: ports.IntentAttach,
84 Name: sess.name,
85 Size: domain.Size{Cols: 80, Rows: 24},
86 ExactTarget: &ports.ExactSessionTarget{LifecycleID: wrong, SessionName: sess.name},
87 }
88
89 _, _, err := d.routeWithContext(context.Background(), hello, tr)
90 var protocol *protoErr
91 require.ErrorAs(t, err, &protocol)
92 require.Equal(t, ports.ErrNoSuchSession, protocol.code)
93}