32c6cd5bc9
Address recurring review feedback on the docker reconnect path: - reconnectToContainer was 98 lines (over the 80-line repo limit). Split into ensureContainerRunning (inspect + start + re-inspect) and bringupAgentctl (health-check, find-or-create instance, transparent re-handshake). The orchestration body now reads top-down at 49 lines. - resolveDockerEndpoint and waitForAgentctlHealth grew real branches in this PR (host-port lookup with container-IP fallback; 240×500ms retry loop) but had no direct tests. Introduce narrow hostPortLookup / healthChecker interfaces, satisfied by *docker.Client and *agentctl.ControlClient, so both functions are now driven by stubs that exercise happy-path, fallback, retry-until-healthy, exhaustion, and context-cancellation branches. - Add direct unit tests for the docker package's pure helpers (normalizeDockerHostIP, buildDockerPortBindings) and for dockerAgentctlPortBindings — a regression in the published port range or host IP would silently break container reconnect.
77 lines
2.1 KiB
Go
77 lines
2.1 KiB
Go
package docker
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/docker/go-connections/nat"
|
|
)
|
|
|
|
func TestNormalizeDockerHostIP(t *testing.T) {
|
|
cases := []struct {
|
|
in string
|
|
want string
|
|
}{
|
|
{in: "", want: "127.0.0.1"},
|
|
{in: "0.0.0.0", want: "127.0.0.1"},
|
|
{in: "::", want: "127.0.0.1"},
|
|
{in: "127.0.0.1", want: "127.0.0.1"},
|
|
{in: "10.0.0.5", want: "10.0.0.5"},
|
|
{in: "::1", want: "::1"},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.in, func(t *testing.T) {
|
|
if got := normalizeDockerHostIP(tc.in); got != tc.want {
|
|
t.Errorf("normalizeDockerHostIP(%q) = %q, want %q", tc.in, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuildDockerPortBindings_EmptyReturnsNil(t *testing.T) {
|
|
exposed, bindings := buildDockerPortBindings(nil)
|
|
if exposed != nil {
|
|
t.Errorf("expected nil exposed ports, got %v", exposed)
|
|
}
|
|
if bindings != nil {
|
|
t.Errorf("expected nil bindings, got %v", bindings)
|
|
}
|
|
}
|
|
|
|
func TestBuildDockerPortBindings_AssignsContainerAndHost(t *testing.T) {
|
|
in := []PortBindingConfig{
|
|
{ContainerPort: 8080, HostIP: "127.0.0.1", HostPort: "0"},
|
|
{ContainerPort: 9000, HostIP: "0.0.0.0", HostPort: "9001"},
|
|
}
|
|
exposed, bindings := buildDockerPortBindings(in)
|
|
|
|
if got := len(exposed); got != 2 {
|
|
t.Fatalf("exposed ports = %d, want 2", got)
|
|
}
|
|
for _, b := range in {
|
|
key := nat.Port(fmt.Sprintf("%d/tcp", b.ContainerPort))
|
|
if _, ok := exposed[key]; !ok {
|
|
t.Errorf("exposed missing %s", key)
|
|
}
|
|
got := bindings[key]
|
|
if len(got) != 1 {
|
|
t.Fatalf("bindings[%s] = %d entries, want 1", key, len(got))
|
|
}
|
|
if got[0].HostIP != b.HostIP || got[0].HostPort != b.HostPort {
|
|
t.Errorf("bindings[%s] = %+v, want host_ip=%q host_port=%q", key, got[0], b.HostIP, b.HostPort)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildDockerPortBindings_DeduplicatesContainerPort(t *testing.T) {
|
|
in := []PortBindingConfig{
|
|
{ContainerPort: 7000, HostIP: "127.0.0.1", HostPort: "0"},
|
|
{ContainerPort: 7000, HostIP: "10.0.0.5", HostPort: "7000"},
|
|
}
|
|
_, bindings := buildDockerPortBindings(in)
|
|
got := bindings[nat.Port("7000/tcp")]
|
|
if len(got) != 2 {
|
|
t.Fatalf("want both bindings on port 7000, got %d", len(got))
|
|
}
|
|
}
|