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 TestDaemonOwnedNoExactTargetRejectsNewAndEphemeral(t *testing.T) {
14 for _, tt := range []struct {
15 name string
16 intent uint8
17 }{
18 {name: "new", intent: ports.IntentNew},
19 {name: "ephemeral", intent: ports.IntentEphemeral},
20 } {
21 t.Run(tt.name, func(t *testing.T) {
22 d := newTestDaemon(t, nil, stubClock{})
23 hello := ports.Hello{
24 Version: ports.ProtocolVersion, Intent: tt.intent, Name: "work",
25 Size: domain.Size{Cols: 80, Rows: 24}, Cwd: "/untrusted/cwd",
26 Env: []string{"UNTRUSTED=client"}, EnvironmentPolicy: ports.EnvironmentPolicyDaemonOwned,
27 }
28 _, _, err := d.route(hello, &closeTrackingTransport{})
29 var protocol *protoErr
30 require.ErrorAs(t, err, &protocol)
31 require.Equal(t, ports.ErrNoSuchTarget, protocol.code)
32 d.mu.Lock()
33 sessions := len(d.sessions)
34 d.mu.Unlock()
35 require.Zero(t, sessions)
36 })
37 }
38}
39
40func TestLegacyDaemonOwnedStoppedAttachUsesDaemonEnvironment(t *testing.T) {
41 d := newTestDaemon(t, newFactory(t, newQuietPTY()), stubClock{})
42 d.baseEnv = []string{"DAEMON=owned"}
43 d.mu.Lock()
44 d.stopped["work"] = stoppedSession{name: "work", cwd: "/remote/work", tabNames: []string{"main"}, state: ports.SessionDown}
45 d.mu.Unlock()
46
47 hello := ports.Hello{
48 Version: ports.ProtocolVersion, Intent: ports.IntentAttach, Name: "work",
49 Size: domain.Size{Cols: 80, Rows: 24}, Cwd: "/untrusted/cwd",
50 Env: []string{"UNTRUSTED=client"}, EnvironmentPolicy: ports.EnvironmentPolicyDaemonOwned,
51 }
52 sess, ac, err := d.routeWithContext(context.Background(), hello, &closeTrackingTransport{})
53 require.NoError(t, err)
54 require.NotNil(t, ac)
55 sess.mu.Lock()
56 require.Equal(t, []string{"DAEMON=owned"}, sess.env)
57 sess.mu.Unlock()
58 require.NoError(t, d.killSession(sess, ports.ReasonSessionKilled, true))
59}
60
61func TestRouteRemoteTargetRestoresStoppedStableTabAndOwnsEnvironment(t *testing.T) {
62 d := newTestDaemon(t, newFactory(t, newQuietPTY()), stubClock{})
63 lifecycle := remoteLifecycleForTest()
64 d.mu.Lock()
65 d.stopped["work"] = stoppedSession{
66 name: "work", cwd: "/remote/work", incarnation: lifecycle,
67 tabNames: []string{"alpha", "beta"},
68 tabRecords: []domain.CatalogueTabRecord{
69 {StableID: "tab-a", Name: "alpha"},
70 {StableID: "tab-b", Name: "beta"},
71 },
72 }
73 d.mu.Unlock()
74
75 target := domain.RemoteSessionTarget{
76 Endpoint: "arch", DisplayOrigin: "arch", LifecycleID: lifecycle,
77 SessionName: "work", Stopped: true,
78 StoppedTab: domain.NewStableTabSelector("tab-b"),
79 }
80 tr, _ := newCapturingTransport(t)
81 hello := ports.Hello{
82 Version: ports.ProtocolVersion, Intent: ports.IntentAttach, Name: "work",
83 Size: domain.Size{Cols: 80, Rows: 24}, RemoteTarget: &target,
84 EnvironmentPolicy: ports.EnvironmentPolicyDaemonOwned,
85 Env: []string{"VEV_REMOTE_PICKER_SENTINEL=untrusted"},
86 }
87 sess, ac, err := d.routeWithContext(context.Background(), hello, tr)
88 require.NoError(t, err)
89 require.NotNil(t, sess)
90 require.NotNil(t, ac)
91 require.Equal(t, lifecycle, sess.incarnation)
92 require.Equal(t, domain.TabStableID("tab-b"), ac.viewSnapshot().tabID)
93 require.NotContains(t, sess.env, "VEV_REMOTE_PICKER_SENTINEL=untrusted")
94 d.clientGone(sess, ac, tr, false)
95}