Files
DockMV/internal/sshx/dialstdio_test.go
T
kawaandClaude Haiku 4.5 9b354636bb Add SSH source support with dialstdio and UI components
- Implement SSH dial via stdio for remote connections
- Add sources API and storage layer for managing connection sources
- Add SourcePanel and SshFields web components for SSH configuration
- Update app structure to support source-based connections
- Update handlers and server for new sources endpoint

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-13 10:19:47 +02:00

80 lines
2.6 KiB
Go

package sshx
import (
"errors"
"io"
"net"
"strings"
"testing"
"time"
)
// TestDialAPICommand pins the command the tunnel runs on the source host: it is
// the same one `docker -H ssh://…` uses, and the sudo / custom binary settings
// have to reach it.
func TestDialAPICommand(t *testing.T) {
for _, tc := range []struct {
name string
rd *RemoteDocker
want string
}{
{"plain", &RemoteDocker{binary: "docker"}, "docker system dial-stdio"},
{"sudo", &RemoteDocker{binary: "docker", sudo: true}, "sudo -n docker system dial-stdio"},
{"podman", &RemoteDocker{binary: "podman"}, "podman system dial-stdio"},
{"path with a space", &RemoteDocker{binary: "/opt/my docker/bin/docker"}, "'/opt/my docker/bin/docker' system dial-stdio"},
} {
t.Run(tc.name, func(t *testing.T) {
if got := tc.rd.Cmd("system", "dial-stdio"); got != tc.want {
t.Fatalf("command = %q, want %q", got, tc.want)
}
})
}
}
// TestAPIConnSurfacesRemoteStderr covers the failure that would otherwise reach
// the Docker client as a bare EOF: the remote docker printing a reason and
// exiting.
func TestAPIConnSurfacesRemoteStderr(t *testing.T) {
errBuf := &syncBuffer{}
errBuf.Write([]byte("docker: 'system dial-stdio' is not a docker command\n"))
c := &apiConn{
stdout: strings.NewReader(""),
stdin: nopWriteCloser{io.Discard},
stderr: errBuf,
}
_, err := c.Read(make([]byte, 8))
if err == nil {
t.Fatal("a closed stream with remote stderr should be an error")
}
if !strings.Contains(err.Error(), "is not a docker command") {
t.Fatalf("error = %v, want the remote stderr in it", err)
}
// Without stderr the plain EOF must survive, or the HTTP transport cannot
// tell a finished response from a broken one.
quiet := &apiConn{stdout: strings.NewReader(""), stdin: nopWriteCloser{io.Discard}, stderr: &syncBuffer{}}
if _, err := quiet.Read(make([]byte, 8)); !errors.Is(err, io.EOF) {
t.Fatalf("error = %v, want io.EOF", err)
}
}
func TestAPIConnDeadlinesAreNoops(t *testing.T) {
var c net.Conn = &apiConn{stdout: strings.NewReader(""), stdin: nopWriteCloser{io.Discard}, stderr: &syncBuffer{}}
now := time.Now()
if err := c.SetDeadline(now); err != nil {
t.Fatalf("SetDeadline: %v", err)
}
if err := c.SetReadDeadline(now); err != nil {
t.Fatalf("SetReadDeadline: %v", err)
}
if err := c.SetWriteDeadline(now); err != nil {
t.Fatalf("SetWriteDeadline: %v", err)
}
if c.RemoteAddr().Network() != "ssh" {
t.Fatalf("network = %q, want ssh", c.RemoteAddr().Network())
}
}
type nopWriteCloser struct{ io.Writer }
func (nopWriteCloser) Close() error { return nil }