package harness import ( "context" "net" "os" "path/filepath" "strings" "testing" "time" "golang.org/x/crypto/ssh" ) func TestEnabled(t *testing.T) { // os.Unsetenv would leak into later tests in the same process; // t.Setenv restores the original value after this test. t.Setenv("RSMON_TEST_DOCKER", "") if Enabled() { t.Fatal("Enabled() true without RSMON_TEST_DOCKER") } for _, v := range []string{"1", "true", "TRUE", "yes", "on"} { t.Setenv("RSMON_TEST_DOCKER", v) if !Enabled() { t.Fatalf("Enabled() false for RSMON_TEST_DOCKER=%q", v) } } for _, v := range []string{"0", "false", "no", "off", " "} { t.Setenv("RSMON_TEST_DOCKER", v) if Enabled() { t.Fatalf("Enabled() true for RSMON_TEST_DOCKER=%q", v) } } } func TestFixtureDefaults(t *testing.T) { fixtures := Fixtures() if len(fixtures) != 3 { t.Fatalf("Fixtures() = %d, want 3", len(fixtures)) } byName := map[string]Fixture{} for _, f := range fixtures { byName[f.Name] = f } if f := byName["alpine"]; f.ImageRef() != "reg.rsxx.ru/library/alpine:3" || f.Distro != "alpine" { t.Fatalf("alpine fixture wrong: %+v", f) } if f := byName["ubuntu"]; f.ImageRef() != "ubuntu:24.04" || f.Distro != "ubuntu" { t.Fatalf("ubuntu fixture wrong: %+v", f) } if f := byName["arch"]; f.ImageRef() != "archlinux:latest" || f.Distro != "arch" { t.Fatalf("arch fixture wrong: %+v", f) } if f := byName["alpine"]; f.UserOrDefault() != "root" { t.Fatalf("default user = %q, want root", f.UserOrDefault()) } } func TestFixtureImageEnvOverride(t *testing.T) { f := Fixture{Name: "ubuntu"} t.Setenv("RSMON_TEST_IMAGE_UBUNTU", "reg.rsxx.ru/library/ubuntu:24.04") if got := f.ImageRef(); got != "reg.rsxx.ru/library/ubuntu:24.04" { t.Fatalf("env override not applied: %q", got) } t.Setenv("RSMON_TEST_IMAGE_UBUNTU", "") if got := f.ImageRef(); got != "ubuntu:24.04" { t.Fatalf("default image changed after env cleared: %q", got) } override := Fixture{Name: "arch", Image: "archlinux:2026.01.01"} if got := override.ImageRef(); got != "archlinux:2026.01.01" { t.Fatalf("fixture image override not applied: %q", got) } } func TestFixturesHaveDockerfiles(t *testing.T) { for _, f := range Fixtures() { path := f.DockerfilePath() if _, err := os.Stat(path); err != nil { t.Fatalf("fixture %s missing Dockerfile at %s: %v", f.Name, path, err) } data, err := os.ReadFile(path) if err != nil { t.Fatal(err) } if !strings.Contains(string(data), "FROM ") { t.Fatalf("fixture %s Dockerfile has no FROM", f.Name) } } } func TestNewValidatesName(t *testing.T) { for _, bad := range []string{"", "with space", "UPPER", "semi;colon", "back`tick", strings.Repeat("a", 65)} { if _, err := New(bad, Fixtures()[0]); err == nil { t.Fatalf("New(%q) succeeded", bad) } } h, err := New("fixture-alpine", Fixtures()[0]) if err != nil { t.Fatal(err) } if h.ContainerName() == "" || h.NetworkName() == "" { t.Fatalf("empty container/network names: %+v", h) } if h.Addr() != "" { t.Fatalf("Addr() = %q before Start, want empty", h.Addr()) } } func TestStartDisabled(t *testing.T) { t.Setenv("RSMON_TEST_DOCKER", "") h, err := New("unit-start", Fixtures()[0]) if err != nil { t.Fatal(err) } if err := h.Start(context.Background()); err == nil { t.Fatal("Start succeeded with integration disabled") } else if !strings.Contains(err.Error(), "RSMON_TEST_DOCKER") { t.Fatalf("disabled Start error = %v, want opt-in hint", err) } } func TestDialBeforeStart(t *testing.T) { h, err := New("unit-dial", Fixtures()[0]) if err != nil { t.Fatal(err) } if _, err := h.Dial(); err == nil { t.Fatal("Dial succeeded before Start") } } func TestParsePublishedPort(t *testing.T) { for _, tc := range []struct { in string want int }{ {in: "127.0.0.1:49153\n", want: 49153}, {in: "0.0.0.0:2222", want: 2222}, {in: "127.0.0.1:0\n", want: 0}, {in: "::1:32768\n", want: 32768}, {in: "127.0.0.1:notaport\n", want: 0}, {in: "", want: 0}, } { got, err := parsePublishedPort(tc.in) if tc.want == 0 { if err == nil { t.Fatalf("parsePublishedPort(%q) succeeded with %d", tc.in, got) } continue } if err != nil || got != tc.want { t.Fatalf("parsePublishedPort(%q) = %d, %v; want %d", tc.in, got, err, tc.want) } } } // TestWaitForPort exercises the readiness probe against a real local // listener so the polling loop is covered without Docker. func TestWaitForPort(t *testing.T) { ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) } defer ln.Close() //nolint:errcheck ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := waitForPort(ctx, ln.Addr().String(), 2*time.Second); err != nil { t.Fatalf("waitForPort on open listener: %v", err) } // A port that never opens must time out. closed, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) } addr := closed.Addr().String() closed.Close() //nolint:errcheck short, shortCancel := context.WithTimeout(context.Background(), 3*time.Second) defer shortCancel() if err := waitForPort(short, addr, 1500*time.Millisecond); err == nil { t.Fatal("waitForPort on closed port succeeded") } } func TestWriteKnownHostsRoundTrip(t *testing.T) { // Parsing a known_hosts entry requires a real key; use the bundled // public key so the format is exercised. raw, err := os.ReadFile(filepath.Join("testdata", "keys", "rsmon_test_ed25519.pub")) if err != nil { t.Fatal(err) } key, _, _, _, err := ssh.ParseAuthorizedKey(raw) if err != nil { t.Fatal(err) } path, err := writeKnownHosts(t.TempDir(), "127.0.0.1:49153", key) if err != nil { t.Fatal(err) } data, err := os.ReadFile(path) if err != nil { t.Fatal(err) } if !strings.Contains(string(data), "[127.0.0.1]:49153") { t.Fatalf("known_hosts entry %q missing bracketed address", data) } } func TestValidateName(t *testing.T) { for _, ok := range []string{"alpine", "fixture-1", "a", strings.Repeat("x", 64)} { if err := validateName(ok); err != nil { t.Fatalf("validateName(%q): %v", ok, err) } } for _, bad := range []string{"", "A", "a b", "a/b", strings.Repeat("x", 65)} { if err := validateName(bad); err == nil { t.Fatalf("validateName(%q) succeeded", bad) } } } func TestSetDockerBin(t *testing.T) { SetDockerBin("docker") SetDockerBin("") if dockerBin != "docker" { t.Fatalf("dockerBin = %q after reset", dockerBin) } SetDockerBin("/stub/docker") if dockerBin != "/stub/docker" { t.Fatalf("dockerBin = %q after set", dockerBin) } SetDockerBin("") } func TestDockerDNSOverride(t *testing.T) { t.Setenv("RSMON_TEST_DOCKER_DNS", "") if got := dockerDNS(); len(got) != 0 { t.Fatalf("dockerDNS() with empty env = %v, want none", got) } t.Setenv("RSMON_TEST_DOCKER_DNS", "8.8.8.8, 1.1.1.1") if got := dockerDNS(); len(got) != 4 || got[0] != "--dns" || got[1] != "8.8.8.8" || got[3] != "1.1.1.1" { t.Fatalf("dockerDNS() = %v", got) } t.Setenv("RSMON_TEST_DOCKER_DNS", " ,,") if got := dockerDNS(); len(got) != 0 { t.Fatalf("dockerDNS() with blank entries = %v", got) } } // writeStubDocker installs a fake docker binary that records its argv to // logPath and returns the recorded path. The stub succeeds for build, // network, run, and teardown calls; `port` fails so Start fails after // the container and network exist. Setting STUB_DOCKER_FAIL_BUILD=1 makes // the build step fail instead. Real Docker is never touched. func writeStubDocker(t *testing.T, logPath string) string { t.Helper() dir := t.TempDir() stub := filepath.Join(dir, "docker") script := `#!/bin/sh echo "$*" >> "$STUB_DOCKER_LOG" case "$1" in build) if [ "${STUB_DOCKER_FAIL_BUILD:-0}" = "1" ]; then echo "stub build failure" >&2 exit 1 fi echo "sha256:stub-image-id" ;; network) echo "stub-network-id" ;; run) echo "stub-container-id" ;; port) echo "stub port failure" >&2; exit 1 ;; rm|-r|image|ps) exit 0 ;; *) exit 0 ;; esac ` if err := os.WriteFile(stub, []byte(script), 0o700); err != nil { t.Fatal(err) } t.Setenv("STUB_DOCKER_LOG", logPath) SetDockerBin(stub) t.Cleanup(func() { SetDockerBin("") }) return stub } func readDockerCalls(t *testing.T, logPath string) string { t.Helper() calls, err := os.ReadFile(logPath) if err != nil { t.Fatal(err) } return string(calls) } // TestStartFailureCleansUpResources drives Start to failure after the // container and network were created (the stub `docker port` fails) and // asserts the failure path removed every resource: container, network, // fixture image, and no temp known_hosts dir leaked. func TestStartFailureCleansUpResources(t *testing.T) { t.Setenv("RSMON_TEST_DOCKER", "1") logPath := filepath.Join(t.TempDir(), "docker-calls.log") writeStubDocker(t, logPath) h, err := New("fail-cleanup", Fixtures()[0]) if err != nil { t.Fatal(err) } if err := h.Start(context.Background()); err == nil { t.Fatal("Start succeeded despite stub docker port failure") } calls := readDockerCalls(t, logPath) for _, want := range []string{"build", "network create", "run -d", "rm -f", "network rm", "image rm"} { if !strings.Contains(calls, want) { t.Fatalf("failed-start cleanup missing docker call %q; calls:\n%s", want, calls) } } if h.KnownHostsPath() != "" { t.Fatalf("known_hosts path set after failed start: %q", h.KnownHostsPath()) } } // TestStartBuildFailureIsSafe drives Start to fail at the very first // step (build) and asserts teardown stays idempotent and harmless: no // container or network was ever created, and the failure path leaves // nothing behind. func TestStartBuildFailureIsSafe(t *testing.T) { t.Setenv("RSMON_TEST_DOCKER", "1") t.Setenv("STUB_DOCKER_FAIL_BUILD", "1") logPath := filepath.Join(t.TempDir(), "docker-calls.log") writeStubDocker(t, logPath) h, err := New("fail-build", Fixtures()[0]) if err != nil { t.Fatal(err) } if err := h.Start(context.Background()); err == nil { t.Fatal("Start succeeded despite stub docker build failure") } if h.ContainerID() != "" { t.Fatalf("container id set after build failure: %q", h.ContainerID()) } if h.KnownHostsPath() != "" { t.Fatalf("known_hosts path set after build failure: %q", h.KnownHostsPath()) } if err := h.Stop(); err != nil { t.Fatalf("Stop after failed build: %v", err) } } // TestStopRemovesKnownHostsDir verifies Stop deletes the temp known_hosts // directory even when no container was ever started (the failed-start // path registers the dir before the final write). func TestStopRemovesKnownHostsDir(t *testing.T) { logPath := filepath.Join(t.TempDir(), "docker-calls.log") writeStubDocker(t, logPath) h, err := New("stop-dir", Fixtures()[0]) if err != nil { t.Fatal(err) } dir := t.TempDir() h.knownHosts = filepath.Join(dir, "known_hosts") if err := os.WriteFile(h.knownHosts, []byte("placeholder"), 0o600); err != nil { t.Fatal(err) } if err := h.Stop(); err != nil { t.Fatalf("Stop: %v", err) } if _, err := os.Stat(dir); !os.IsNotExist(err) { t.Fatalf("known_hosts temp dir still exists after Stop: %v", err) } if h.KnownHostsPath() != "" { t.Fatalf("KnownHostsPath = %q after Stop, want empty", h.KnownHostsPath()) } } // TestStopIdempotent verifies repeated Stop calls do not error. func TestStopIdempotent(t *testing.T) { logPath := filepath.Join(t.TempDir(), "docker-calls.log") writeStubDocker(t, logPath) h, err := New("stop-again", Fixtures()[0]) if err != nil { t.Fatal(err) } h.container = "rsmon-worker-test-none" h.network = "rsmon-worker-test-none" h.imageTag = "rsmon-worker-test/none:local" h.knownHosts = filepath.Join(t.TempDir(), "known_hosts") if err := h.Stop(); err != nil { t.Fatalf("first Stop: %v", err) } if err := h.Stop(); err != nil { t.Fatalf("second Stop: %v", err) } } // TestImageTagUnique verifies every harness instance gets its own image // tag so teardown can never delete another instance's image. func TestImageTagUnique(t *testing.T) { a, err := New("img-a", Fixtures()[0]) if err != nil { t.Fatal(err) } b, err := New("img-b", Fixtures()[0]) if err != nil { t.Fatal(err) } if a.ImageTag() == b.ImageTag() { t.Fatalf("image tags collide: %q", a.ImageTag()) } if !strings.HasPrefix(a.ImageTag(), "rsmon-worker-test/alpine-") { t.Fatalf("unexpected image tag: %q", a.ImageTag()) } }