Files
mcp/internal/runtime/runtime_test.go
Kyle Isom 43789dd6be Fix route-based port mapping: use hostPort as container port
allocateRoutePorts() was using the route's port field (the mc-proxy
listener port, e.g. 443) as the container internal port in the podman
port mapping. For L7 routes, apps don't listen on the mc-proxy port —
they read $PORT (set to the assigned host port) and listen on that.

The mapping host:53204 → container:443 fails because nothing listens
on 443 inside the container. Fix: use hostPort as both the host and
container port, so $PORT = host port = container port.

Broke mcdoc in production (manually fixed, now permanently fixed).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:50:48 -07:00

147 lines
3.7 KiB
Go

package runtime
import (
"slices"
"testing"
)
func requireEqualArgs(t *testing.T, got, want []string) {
t.Helper()
if !slices.Equal(got, want) {
t.Fatalf("args mismatch\ngot: %v\nwant: %v", got, want)
}
}
func TestBuildRunArgs(t *testing.T) {
p := &Podman{}
t.Run("full spec", func(t *testing.T) {
spec := ContainerSpec{
Name: "metacrypt-api",
Image: "mcr.svc.mcp.metacircular.net:8443/metacrypt:v1.0.0",
Network: "docker_default",
User: "0:0",
Restart: "unless-stopped",
Ports: []string{"127.0.0.1:18443:8443", "127.0.0.1:19443:9443"},
Volumes: []string{"/srv/metacrypt:/srv/metacrypt", "/etc/ssl:/etc/ssl:ro"},
Cmd: []string{"server", "--config", "/srv/metacrypt/metacrypt.toml"},
}
requireEqualArgs(t, p.BuildRunArgs(spec), []string{
"run", "-d", "--name", "metacrypt-api",
"--network", "docker_default",
"--user", "0:0",
"--restart", "unless-stopped",
"-p", "127.0.0.1:18443:8443",
"-p", "127.0.0.1:19443:9443",
"-v", "/srv/metacrypt:/srv/metacrypt",
"-v", "/etc/ssl:/etc/ssl:ro",
"mcr.svc.mcp.metacircular.net:8443/metacrypt:v1.0.0",
"server", "--config", "/srv/metacrypt/metacrypt.toml",
})
})
t.Run("minimal spec", func(t *testing.T) {
spec := ContainerSpec{
Name: "test-app",
Image: "img:latest",
}
requireEqualArgs(t, p.BuildRunArgs(spec), []string{
"run", "-d", "--name", "test-app", "img:latest",
})
})
t.Run("ports only", func(t *testing.T) {
spec := ContainerSpec{
Name: "test-app",
Image: "img:latest",
Ports: []string{"8080:80", "8443:443"},
}
requireEqualArgs(t, p.BuildRunArgs(spec), []string{
"run", "-d", "--name", "test-app",
"-p", "8080:80", "-p", "8443:443",
"img:latest",
})
})
t.Run("volumes only", func(t *testing.T) {
spec := ContainerSpec{
Name: "test-app",
Image: "img:latest",
Volumes: []string{"/data:/data", "/config:/config:ro"},
}
requireEqualArgs(t, p.BuildRunArgs(spec), []string{
"run", "-d", "--name", "test-app",
"-v", "/data:/data", "-v", "/config:/config:ro",
"img:latest",
})
})
t.Run("env vars", func(t *testing.T) {
spec := ContainerSpec{
Name: "test-app",
Image: "img:latest",
Env: []string{"PORT=12345", "PORT_GRPC=12346"},
}
requireEqualArgs(t, p.BuildRunArgs(spec), []string{
"run", "-d", "--name", "test-app",
"-e", "PORT=12345", "-e", "PORT_GRPC=12346",
"img:latest",
})
})
t.Run("full spec with env", func(t *testing.T) {
// Route-allocated ports: host port = container port (matches $PORT).
spec := ContainerSpec{
Name: "svc-api",
Image: "img:latest",
Network: "net",
Ports: []string{"127.0.0.1:12345:12345"},
Volumes: []string{"/srv:/srv"},
Env: []string{"PORT=12345"},
}
requireEqualArgs(t, p.BuildRunArgs(spec), []string{
"run", "-d", "--name", "svc-api",
"--network", "net",
"-p", "127.0.0.1:12345:12345",
"-v", "/srv:/srv",
"-e", "PORT=12345",
"img:latest",
})
})
t.Run("cmd after image", func(t *testing.T) {
spec := ContainerSpec{
Name: "test-app",
Image: "img:latest",
Cmd: []string{"serve", "--port", "8080"},
}
requireEqualArgs(t, p.BuildRunArgs(spec), []string{
"run", "-d", "--name", "test-app",
"img:latest",
"serve", "--port", "8080",
})
})
}
func TestExtractVersion(t *testing.T) {
tests := []struct {
image string
want string
}{
{"registry.example.com:5000/img:v1.2.0", "v1.2.0"},
{"img:latest", "latest"},
{"img", ""},
{"registry.example.com/path/img:v1", "v1"},
{"registry.example.com:5000/path/img", ""},
}
for _, tt := range tests {
t.Run(tt.image, func(t *testing.T) {
got := ExtractVersion(tt.image)
if got != tt.want {
t.Fatalf("ExtractVersion(%q) = %q, want %q", tt.image, got, tt.want)
}
})
}
}