Eric Bower
·
2025-06-23
server_test.go
1package pssh_test
2
3import (
4 "context"
5 "errors"
6 "log/slog"
7 "net"
8 "testing"
9 "time"
10
11 "github.com/picosh/pico/pkg/pssh"
12 "golang.org/x/crypto/ssh"
13)
14
15func TestNewSSHServer(t *testing.T) {
16 ctx := context.Background()
17 logger := slog.Default()
18 server := pssh.NewSSHServer(ctx, logger, &pssh.SSHServerConfig{})
19
20 if server == nil { //nolint:all
21 t.Fatal("expected non-nil server")
22 }
23
24 if server.Ctx == nil { //nolint:all
25 t.Error("expected non-nil context")
26 }
27
28 if server.Logger == nil { //nolint:all
29 t.Error("expected non-nil logger")
30 }
31
32 if server.Config == nil { //nolint:all
33 t.Error("expected non-nil config")
34 }
35
36 if server.Conns == nil { //nolint:all
37 t.Error("expected non-nil connections map")
38 }
39}
40
41func TestNewSSHServerConn(t *testing.T) {
42 ctx := context.Background()
43 logger := slog.Default()
44 server := pssh.NewSSHServer(ctx, logger, &pssh.SSHServerConfig{})
45 conn := &ssh.ServerConn{}
46
47 serverConn := pssh.NewSSHServerConn(ctx, logger, conn, server)
48
49 if serverConn == nil { //nolint:all
50 t.Fatal("expected non-nil server connection")
51 }
52
53 if serverConn.Ctx == nil { //nolint:all
54 t.Error("expected non-nil context")
55 }
56
57 if serverConn.Logger == nil { //nolint:all
58 t.Error("expected non-nil logger")
59 }
60
61 if serverConn.Conn != conn { //nolint:all
62 t.Error("expected conn to match")
63 }
64
65 if serverConn.SSHServer != server { //nolint:all
66 t.Error("expected server to match")
67 }
68}
69
70func TestSSHServerConnClose(t *testing.T) {
71 ctx := context.Background()
72 logger := slog.Default()
73 server := pssh.NewSSHServer(ctx, logger, &pssh.SSHServerConfig{})
74 conn := &ssh.ServerConn{}
75
76 serverConn := pssh.NewSSHServerConn(ctx, logger, conn, server)
77 err := serverConn.Close()
78
79 if err != nil {
80 t.Errorf("unexpected error: %v", err)
81 }
82
83 // Should be canceled after close
84 select {
85 case <-serverConn.Ctx.Done():
86 // Context was canceled as expected
87 default:
88 t.Error("context was not canceled after Close()")
89 }
90}
91
92func TestSSHServerClose(t *testing.T) {
93 ctx := context.Background()
94 logger := slog.Default()
95 server := pssh.NewSSHServer(ctx, logger, &pssh.SSHServerConfig{})
96
97 // Create a mock listener to test Close()
98 listener, err := net.Listen("tcp", "127.0.0.1:0")
99 if err != nil {
100 t.Fatalf("failed to create listener: %v", err)
101 }
102
103 server.Listener = listener
104 err = server.Close()
105
106 if err != nil {
107 t.Errorf("unexpected error: %v", err)
108 }
109
110 // Should be canceled after close
111 select {
112 case <-server.Ctx.Done():
113 // Context was canceled as expected
114 default:
115 t.Error("context was not canceled after Close()")
116 }
117}
118
119func TestSSHServerNilParams(t *testing.T) {
120 // Test with nil context and logger
121 //nolint:staticcheck // SA1012 ignores nil check
122 //lint:ignore SA1012 ignores nil check
123 server := pssh.NewSSHServer(nil, nil, nil)
124
125 if server == nil { //nolint:all
126 t.Fatal("expected non-nil server")
127 }
128
129 if server.Ctx == nil { //nolint:all
130 t.Error("expected non-nil context even when nil is passed")
131 }
132
133 if server.Logger == nil { //nolint:all
134 t.Error("expected non-nil logger even when nil is passed")
135 }
136
137 // Test with nil context and logger for connection
138 //nolint:staticcheck // SA1012 ignores nil check
139 //lint:ignore SA1012 ignores nil check
140 conn := pssh.NewSSHServerConn(nil, nil, &ssh.ServerConn{}, server)
141
142 if conn == nil { //nolint:all
143 t.Fatal("expected non-nil server connection")
144 }
145
146 if conn.Ctx == nil { //nolint:all
147 t.Error("expected non-nil context even when nil is passed")
148 }
149
150 if conn.Logger == nil { //nolint:all
151 t.Error("expected non-nil logger even when nil is passed")
152 }
153}
154
155func TestSSHServerHandleConn(t *testing.T) {
156 ctx, cancel := context.WithCancel(context.Background())
157 defer cancel()
158 logger := slog.Default()
159 server := pssh.NewSSHServer(ctx, logger, &pssh.SSHServerConfig{})
160
161 // Setup a basic SSH server config
162 config := &ssh.ServerConfig{
163 NoClientAuth: true,
164 }
165
166 server.Config.ServerConfig = config
167
168 // Create a mock connection
169 client, server_conn := net.Pipe()
170 defer func() {
171 _ = client.Close()
172 }()
173
174 // Start HandleConn in a goroutine
175 errChan := make(chan error, 1)
176 go func() {
177 errChan <- server.HandleConn(server_conn)
178 }()
179
180 // Configure SSH client
181 clientConfig := &ssh.ClientConfig{
182 User: "testuser",
183 HostKeyCallback: ssh.InsecureIgnoreHostKey(),
184 }
185
186 // Try to establish SSH connection
187 _, _, _, err := ssh.NewClientConn(client, "", clientConfig)
188
189 // It should fail since we're using a pipe and not a proper SSH handshake
190 if err == nil {
191 t.Error("expected SSH handshake to fail with test pipe")
192 }
193
194 // Close connections to ensure HandleConn returns
195 _ = client.Close()
196 _ = server_conn.Close()
197
198 // Wait for HandleConn to return
199 select {
200 case <-errChan:
201 // Expected HandleConn to return
202 case <-time.After(2 * time.Second):
203 t.Error("HandleConn did not return after connection closed")
204 }
205}
206
207func TestSSHServerListenAndServe(t *testing.T) {
208 ctx, cancel := context.WithCancel(context.Background())
209 defer cancel()
210 logger := slog.Default()
211 server := pssh.NewSSHServer(ctx, logger, &pssh.SSHServerConfig{})
212
213 config := &ssh.ServerConfig{
214 NoClientAuth: true,
215 }
216
217 // Set a random port
218 port := "127.0.0.1:0"
219 server.Config.ListenAddr = port
220 server.Config.ServerConfig = config
221
222 // Start server in a goroutine
223 errChan := make(chan error, 1)
224 go func() {
225 err := server.ListenAndServe()
226 errChan <- err
227 }()
228
229 // Wait a bit for the server to start
230 time.Sleep(100 * time.Millisecond)
231
232 // Trigger cancellation to stop the server
233 cancel()
234
235 // Wait for server to stop
236 select {
237 case err := <-errChan:
238 if err != nil && !errors.Is(err, net.ErrClosed) {
239 t.Errorf("unexpected error: %v", err)
240 }
241 case <-time.After(2 * time.Second):
242 t.Error("server did not shut down in time")
243 }
244}
245
246func TestSSHServerConnHandle(t *testing.T) {
247 ctx, cancel := context.WithCancel(context.Background())
248 defer cancel()
249 logger := slog.Default()
250 server := pssh.NewSSHServer(ctx, logger, &pssh.SSHServerConfig{})
251 conn := &ssh.ServerConn{}
252
253 serverConn := pssh.NewSSHServerConn(ctx, logger, conn, server)
254
255 // Create channels for testing
256 chans := make(chan ssh.NewChannel)
257 reqs := make(chan *ssh.Request)
258
259 // Start handle in a goroutine
260 errChan := make(chan error, 1)
261 go func() {
262 errChan <- serverConn.Handle(chans, reqs)
263 }()
264
265 // Ensure handle returns when context is canceled
266 cancel()
267
268 // Wait for handle to return
269 select {
270 case err := <-errChan:
271 if err != nil {
272 t.Errorf("unexpected error: %v", err)
273 }
274 case <-time.After(2 * time.Second):
275 t.Error("Handle did not return after context canceled")
276 }
277}