chore: Improve SSH unit tests#381
Conversation
Wire concrete types directly instead of the factory/interface seams (sshConnFactory, netDialerFactory, connPairFactory, ConnPair, ChannelPairFactory, ChannelPair, Request, RequestHandler). Request handling uses *ssh.Request directly, dropping the wrapper goroutine per SSH channel. Delete mocks.go and the mock-based tests; real-SSH behavior tests replace them in follow-up commits.
Rewrite the channel-layer tests (TestChannelPair_*) against real SSH connections instead of mocks; conn_pair_test.go carries the shared transport-base helpers. Bundle the enabling refactor: channel/connection value types replace the positional trios across the sshhandler package, and the copier/session timeouts move from package vars to struct fields with defaults so tests configure them per instance rather than mutating globals.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #381 +/- ##
==========================================
- Coverage 86.06% 85.37% -0.69%
==========================================
Files 40 39 -1
Lines 2828 2640 -188
==========================================
- Hits 2434 2254 -180
+ Misses 269 261 -8
Partials 125 125
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR refactors the SSH handler internals to use concrete *ssh.Request / connection/channel structs instead of test-focused interfaces, and replaces several mock-heavy unit tests with more realistic SSH integration-style tests that exercise request forwarding, session gating, teardown, and recording behaviors end-to-end.
Changes:
- Refactors request and channel handling to use concrete
*ssh.Requestplus newconnection/channelstructs, simplifying forwarding and plumbing. - Makes SSH channel copy/teardown timeouts configurable via per-pair fields (with defaults) rather than package-level vars.
- Reworks SSH channel-pair tests to use real SSH connections over loopback TCP, and removes older mock-based tests/mocks.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| internal/sshhandler/request_handler.go | Removes request abstraction and operates directly on *ssh.Request for parsing/logging/forwarding. |
| internal/sshhandler/request_handler_test.go | Removes prior mock-based request handler unit tests. |
| internal/sshhandler/proxy.go | Simplifies proxy connection setup (direct ssh.NewServerConn/ssh.NewClientConn and net.DialTimeout) and tightens types. |
| internal/sshhandler/proxy_test.go | Removes prior proxy unit tests that relied on injectable factories/mocks. |
| internal/sshhandler/mocks.go | Removes mock implementations previously used by SSH handler tests. |
| internal/sshhandler/conn_pair.go | Introduces connection struct and removes channel-pair factory indirection; uses concrete SSHChannelPair. |
| internal/sshhandler/conn_pair_test.go | Converts to shared SSH test helpers (net+SSH pipe helpers) used by other tests. |
| internal/sshhandler/channel_pair.go | Introduces channel struct, adds configurable timeouts on SSHChannelPair, and wires through copier timeouts. |
| internal/sshhandler/channel_pair_test.go | Adds comprehensive integration-style tests using real SSH channels for forwarding/gating/recording/teardown. |
| internal/sshhandler/channel_copier.go | Replaces global timeout vars with defaults and per-copier timeout fields. |
| internal/sshhandler/channel_copier_test.go | Removes prior mock-based copier unit tests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -214,6 +155,8 @@ func (p *SSHProxy) serveConn(ctx context.Context, conn connect.Conn) error { | |||
| return err | |||
| } | |||
|
|
|||
| downstreamConn := connection{conn: downstreamSSHConn, channels: downstreamChannels, requests: downstreamRequests} | |||
|
|
|||
| sshCtx := &sshContext{ | |||
| id: hex.EncodeToString(downstreamSSHConn.SessionID()), | |||
| username: upstream.username, | |||
| @@ -222,40 +165,40 @@ func (p *SSHProxy) serveConn(ctx context.Context, conn connect.Conn) error { | |||
|
|
|||
| upstreamConfig, err := p.config.GetUpstreamConfig(ctx, upstream) | |||
| if err != nil { | |||
| closeDownstreamSSH(downstreamSSHConn, downstreamSSHChannelsChan, logger, sshCtx) | |||
| closeDownstreamSSH(downstreamConn, logger, sshCtx) | |||
|
|
|||
| return err | |||
| } | |||
|
|
|||
| // Start connection to upstream SSH server | |||
| upstreamConn, err := p.netDialer.DialTimeout("tcp", upstream.address, upstreamConnTimeout) | |||
| upstreamNetConn, err := net.DialTimeout("tcp", upstream.address, upstreamConnTimeout) | |||
| if err != nil { | |||
| logger.Error("Failed to connect to upstream SSH server", zap.Error(err)) | |||
|
|
|||
| closeDownstreamSSH(downstreamSSHConn, downstreamSSHChannelsChan, logger, sshCtx) | |||
| closeDownstreamSSH(downstreamConn, logger, sshCtx) | |||
|
|
|||
| return err | |||
| } | |||
|
|
|||
| // Open the SSH connection to the upstream server | |||
| upstreamSSHConn, upstreamSSHChannelsChan, upstreamSSHRequestsChan, err := p.sshConnFactory.NewClientConn(upstreamConn, upstream.address, upstreamConfig) | |||
| upstreamSSHConn, upstreamChannels, upstreamRequests, err := ssh.NewClientConn(upstreamNetConn, upstream.address, upstreamConfig) | |||
| if err != nil { | |||
Changes
TBD