repos / pico

pico services mono repo
git clone https://github.com/picosh/pico.git

pico / pkg / pssh
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}