1package sessionwire
2
3import (
4 "testing"
5 "time"
6
7 "github.com/stretchr/testify/require"
8
9 "github.com/bnema/vev/internal/domain"
10 "github.com/bnema/vev/internal/ports"
11 "github.com/bnema/vev/internal/protocol"
12)
13
14func wireTestPolicy() ports.BrokerPolicy {
15 return ports.BrokerPolicy{
16 ProtocolVersion: protocol.Version,
17 CatalogSchemaVersion: 3,
18 EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned,
19 Transport: "unix-mux",
20 Trust: "same-user",
21 Launch: "explicit",
22 Isolation: "per-user",
23 }
24}
25
26func wireTestAdmission() ports.SessionAdmission {
27 return ports.SessionAdmission{
28 Origin: ports.SessionOriginLocal,
29 Policy: wireTestPolicy(),
30 Purpose: ports.BrokerStreamAttachment,
31 Admission: ports.BrokerAdmissionExact,
32 Target: protocol.ExactSessionTarget{LifecycleID: domain.SessionLifecycleID{4, 5, 6}, SessionName: "work"},
33 Env: []string{"TERM=xterm-256color"},
34 }
35}
36
37// TestServerConnectionAdmissionProviderPreservedWithoutWireChange proves the
38// admission-aware constructor stores the provisioned metadata and exposes it
39// through the optional provider, while a successful typed round trip over the
40// same connection is byte-for-byte the ordinary session traffic: the admission
41// never leaks onto the session wire.
42func TestServerConnectionAdmissionProviderPreservedWithoutWireChange(t *testing.T) {
43 raw := &scriptedTransport{recv: mustServerPreambleQueue(t, mustEncodeClient(t, protocol.Ping{}))}
44 deadline := time.Now().Add(protocol.HandshakeTimeout)
45 connection := NewServerConnectionWithAdmission(raw, deadline, wireTestAdmission())
46
47 provider, ok := connection.(ports.SessionAdmissionProvider)
48 require.True(t, ok, "the admission-aware connection implements the optional provider")
49 admission, ok := provider.SessionAdmission()
50 require.True(t, ok)
51 require.Equal(t, wireTestAdmission(), admission)
52 require.NoError(t, admission.Validate())
53
54 // A typed exchange on the same connection succeeds and the sent application
55 // frame is the ordinary Pong encoding: the admission is not part of the
56 // session wire. (The preamble response precedes it.)
57 message, err := connection.ReceiveClient()
58 require.NoError(t, err)
59 require.Equal(t, protocol.Ping{}, message)
60 require.NoError(t, connection.SendServer(protocol.Pong{}))
61 require.Equal(t, 2, raw.sentLen())
62 got, err := DecodeServerEnvelope(raw.sentPayload(1))
63 require.NoError(t, err)
64 require.Equal(t, protocol.Pong{}, got)
65}
66
67// TestServerConnectionAdmissionProviderIsDefensiveCopy proves the provider hands
68// out an independent copy on every read: a consumer that mutates what it holds
69// never alters the connection's stored admission or a sibling's view.
70func TestServerConnectionAdmissionProviderIsDefensiveCopy(t *testing.T) {
71 raw := &scriptedTransport{}
72 connection := NewServerConnectionWithAdmission(raw, time.Now().Add(protocol.HandshakeTimeout), wireTestAdmission())
73 provider := connection.(ports.SessionAdmissionProvider)
74
75 mutated, ok := provider.SessionAdmission()
76 require.True(t, ok)
77 mutated.Env[0] = "TERM=mutated"
78 mutated.Policy.Trust = "mutated"
79 mutated.Target.SessionName = "mutated"
80
81 reread, ok := provider.SessionAdmission()
82 require.True(t, ok)
83 require.Equal(t, wireTestAdmission(), reread)
84
85 left, _ := provider.SessionAdmission()
86 right, _ := provider.SessionAdmission()
87 left.Env[0] = "TERM=left"
88 require.Equal(t, "TERM=xterm-256color", right.Env[0])
89}
90
91// TestServerConnectionAdmissionAbsentForLegacyConstructors proves the legacy
92// constructors carry no admission metadata: a consumer that asserts the
93// provider is told ok=false rather than trusted with a zero admission.
94func TestServerConnectionAdmissionAbsentForLegacyConstructors(t *testing.T) {
95 for _, tc := range []struct {
96 name string
97 build func() ports.ServerConnection
98 }{
99 {name: "NewServerConnection", build: func() ports.ServerConnection { return NewServerConnection(&scriptedTransport{}) }},
100 {name: "NewServerConnectionWithDeadline", build: func() ports.ServerConnection {
101 return NewServerConnectionWithDeadline(&scriptedTransport{}, time.Now().Add(protocol.HandshakeTimeout))
102 }},
103 {name: "NewServerConnectionWithAdmission zero", build: func() ports.ServerConnection {
104 return NewServerConnectionWithAdmission(&scriptedTransport{}, time.Now().Add(protocol.HandshakeTimeout), ports.SessionAdmission{})
105 }},
106 } {
107 t.Run(tc.name, func(t *testing.T) {
108 provider, ok := tc.build().(ports.SessionAdmissionProvider)
109 require.True(t, ok)
110 admission, present := provider.SessionAdmission()
111 require.False(t, present)
112 require.Equal(t, ports.SessionAdmission{}, admission)
113 })
114 }
115}