Files
worker/internal/installer/harness/harness_test.go
Gleb Tv bd6070ee1f
Все проверки выполнены успешно
CI / test (push) Successful in 3m13s
Docker / Build and publish worker image (push) Successful in 10m35s
feat(installer): build worker source over SSH
2026-08-13 00:07:43 +03:00

418 строки
12 KiB
Go

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())
}
}