Ctrl K
ComunidadGuías…

flags_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package main

import (
	"errors"
	"log/slog"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/sophotechlabs/spinoza/internal/debugcontainer"
	"github.com/sophotechlabs/spinoza/internal/version"
)

func TestDefaultsMatchTheDocumentedOnes(t *testing.T) {
	opts, err := parseFlags(nil)
	if err != nil {
		t.Fatalf("parse: %v", err)
	}

	if opts.addr != "127.0.0.1:34115" {
		t.Fatalf("addr = %q", opts.addr)
	}
	if opts.cluster.DebugImage != debugcontainer.DefaultImage {
		t.Fatalf("debug image = %q", opts.cluster.DebugImage)
	}
	if opts.cluster.KubectlBinary != debugcontainer.DefaultBinary {
		t.Fatalf("kubectl = %q", opts.cluster.KubectlBinary)
	}
	if opts.cluster.PromSpec != "" {
		t.Fatalf("prometheus = %q, want discovery", opts.cluster.PromSpec)
	}
	if opts.tokenFile != "" {
		t.Fatalf("token file = %q, want none unless a script asked for one", opts.tokenFile)
	}
}

func TestEveryFlagIsCarriedThrough(t *testing.T) {
	opts, err := parseFlags([]string{
		"-addr", "127.0.0.1:9999",
		"-open",
		"-debug-image", "ghcr.io/acme/toolbox:2.1",
		"-kubectl", "/usr/local/bin/kubectl",
		"-prometheus", "monitoring/prom:9090",
		"-token-file", "/tmp/spinoza.token",
	})
	if err != nil {
		t.Fatalf("parse: %v", err)
	}

	if opts.addr != "127.0.0.1:9999" {
		t.Fatalf("addr = %q", opts.addr)
	}
	if !opts.openBrowser {
		t.Fatal("open was dropped")
	}
	if opts.tokenFile != "/tmp/spinoza.token" {
		t.Fatalf("token file = %q", opts.tokenFile)
	}
	if opts.cluster.DebugImage != "ghcr.io/acme/toolbox:2.1" {
		t.Fatalf("debug image = %q", opts.cluster.DebugImage)
	}
	if opts.cluster.KubectlBinary != "/usr/local/bin/kubectl" {
		t.Fatalf("kubectl = %q", opts.cluster.KubectlBinary)
	}
	if opts.cluster.PromSpec != "monitoring/prom:9090" {
		t.Fatalf("prometheus = %q", opts.cluster.PromSpec)
	}
}

func TestNodeShellFlagRequiresAPinnedImage(t *testing.T) {
	_, err := parseFlags([]string{"-node-shell", "-node-shell-image", "busybox:1.37"})
	if err == nil || err.Error() != "node-shell-image must be pinned by sha256 digest" {
		t.Fatalf("error = %v", err)
	}
}

func TestTheTokenFileIsWrittenForScriptsOnly(t *testing.T) {
	path := filepath.Join(t.TempDir(), "token")

	err := writeTokenFile(path, "s3cret")
	if err != nil {
		t.Fatalf("write: %v", err)
	}

	data, readErr := os.ReadFile(path)
	if readErr != nil {
		t.Fatalf("read: %v", readErr)
	}
	if string(data) != "s3cret\n" {
		t.Fatalf("contents = %q", data)
	}
	info, statErr := os.Stat(path)
	if statErr != nil {
		t.Fatalf("stat: %v", statErr)
	}
	if info.Mode().Perm() != 0o600 {
		t.Fatalf("mode = %v, want the token readable by its owner only", info.Mode().Perm())
	}
}

func TestNoTokenFileMeansNoFile(t *testing.T) {
	err := writeTokenFile("", "s3cret")
	if err != nil {
		t.Fatalf("write: %v", err)
	}
}

func TestAnUnwritableTokenFileIsReported(t *testing.T) {
	path := filepath.Join(t.TempDir(), "missing", "token")

	err := writeTokenFile(path, "s3cret")

	if err == nil {
		t.Fatal("a token file that cannot be written was reported as fine")
	}
}

func TestEveryLogLevelIsAccepted(t *testing.T) {
	cases := map[string]slog.Level{
		"debug": slog.LevelDebug,
		"info":  slog.LevelInfo,
		"warn":  slog.LevelWarn,
		"error": slog.LevelError,
	}
	for name, want := range cases {
		t.Run(name, func(t *testing.T) {
			opts, err := parseFlags([]string{"-log-level", name})
			if err != nil {
				t.Fatalf("parse: %v", err)
			}
			if opts.logLevel != want {
				t.Fatalf("level = %v, want %v", opts.logLevel, want)
			}
		})
	}
}

func TestAnUnknownLogLevelIsRefused(t *testing.T) {
	_, err := parseFlags([]string{"-log-level", "chatty"})

	if err == nil {
		t.Fatal("an unknown log level was accepted")
	}
	if !strings.Contains(err.Error(), "debug, info, warn, error") {
		t.Fatalf("err = %v, want it to name the levels", err)
	}
}

func TestTheVersionFlagIsCarriedThrough(t *testing.T) {
	opts, err := parseFlags([]string{"-version"})
	if err != nil {
		t.Fatalf("parse: %v", err)
	}

	if !opts.showVersion {
		t.Fatal("version was dropped")
	}
}

func TestTheLicenseFlagIsCarriedThrough(t *testing.T) {
	opts, err := parseFlags([]string{"-license"})
	if err != nil {
		t.Fatalf("parse: %v", err)
	}

	if !opts.showLicense {
		t.Fatal("license was dropped")
	}
}

func TestTheEnvironmentFillsInWhatTheDesktopBundleCannotPass(t *testing.T) {
	t.Setenv("SPINOZA_ADDR", "127.0.0.1:7777")
	t.Setenv("SPINOZA_OPEN", "true")
	t.Setenv("SPINOZA_TOKEN_FILE", "/run/spinoza.token")
	t.Setenv("SPINOZA_LOG_LEVEL", "debug")
	t.Setenv("SPINOZA_DEBUG_IMAGE", "ghcr.io/acme/toolbox:9")
	t.Setenv("SPINOZA_KUBECTL", "/opt/kubectl")
	t.Setenv("SPINOZA_PROMETHEUS", "obs/prom:9090")

	opts, err := parseFlags(nil)
	if err != nil {
		t.Fatalf("parse: %v", err)
	}

	if opts.addr != "127.0.0.1:7777" {
		t.Fatalf("addr = %q", opts.addr)
	}
	if !opts.openBrowser {
		t.Fatal("open was not read from the environment")
	}
	if opts.tokenFile != "/run/spinoza.token" {
		t.Fatalf("token file = %q", opts.tokenFile)
	}
	if opts.logLevel != slog.LevelDebug {
		t.Fatalf("log level = %v", opts.logLevel)
	}
	if opts.cluster.DebugImage != "ghcr.io/acme/toolbox:9" {
		t.Fatalf("debug image = %q", opts.cluster.DebugImage)
	}
	if opts.cluster.KubectlBinary != "/opt/kubectl" {
		t.Fatalf("kubectl = %q", opts.cluster.KubectlBinary)
	}
	if opts.cluster.PromSpec != "obs/prom:9090" {
		t.Fatalf("prometheus = %q", opts.cluster.PromSpec)
	}
}

func TestAFlagBeatsTheEnvironment(t *testing.T) {
	t.Setenv("SPINOZA_ADDR", "127.0.0.1:7777")

	opts, err := parseFlags([]string{"-addr", "127.0.0.1:8888"})
	if err != nil {
		t.Fatalf("parse: %v", err)
	}

	if opts.addr != "127.0.0.1:8888" {
		t.Fatalf("addr = %q, want the flag to win", opts.addr)
	}
}

func TestAnUnreadableBooleanInTheEnvironmentIsIgnored(t *testing.T) {
	cases := map[string]bool{
		"true":  true,
		"1":     true,
		"false": false,
		"yes":   false,
	}
	for value, want := range cases {
		t.Run(value, func(t *testing.T) {
			t.Setenv("SPINOZA_OPEN", value)

			opts, err := parseFlags(nil)
			if err != nil {
				t.Fatalf("parse: %v", err)
			}
			if opts.openBrowser != want {
				t.Fatalf("open = %v for %q, want %v", opts.openBrowser, value, want)
			}
		})
	}
}

func TestTheBigClusterKnobsKeepTodaysValuesByDefault(t *testing.T) {
	opts, err := parseFlags(nil)
	if err != nil {
		t.Fatalf("parse: %v", err)
	}

	if opts.cluster.Kubeconfig != "" {
		t.Fatalf("kubeconfig = %q, want the usual lookup rules", opts.cluster.Kubeconfig)
	}
	if opts.cluster.ClientQPS != defaultQPS {
		t.Fatalf("qps = %v, want %v", opts.cluster.ClientQPS, float32(defaultQPS))
	}
	if opts.cluster.ClientBurst != defaultBurst {
		t.Fatalf("burst = %d, want %d", opts.cluster.ClientBurst, defaultBurst)
	}
	if opts.cluster.SyncTimeout != defaultSync {
		t.Fatalf("sync timeout = %s, want %s", opts.cluster.SyncTimeout, defaultSync)
	}
	if opts.cluster.WarmConcurrency != defaultWarm {
		t.Fatalf("warm concurrency = %d, want %d", opts.cluster.WarmConcurrency, defaultWarm)
	}
	if opts.cluster.CountBudget != defaultCountBudget {
		t.Fatalf("count budget = %s, want %s", opts.cluster.CountBudget, defaultCountBudget)
	}
	if opts.cluster.CountPerType != defaultCountPerType {
		t.Fatalf("count timeout = %s, want %s", opts.cluster.CountPerType, defaultCountPerType)
	}
	if opts.cluster.CountConcurrency != defaultCountConcurrency {
		t.Fatalf("count concurrency = %d, want %d", opts.cluster.CountConcurrency, defaultCountConcurrency)
	}
}

func TestABigClusterCanRaiseEveryLimit(t *testing.T) {
	opts, err := parseFlags([]string{
		"-kubeconfig", "/etc/spinoza/kubeconfig",
		"-qps", "200",
		"-burst", "400",
		"-sync-timeout", "3m",
		"-warm-concurrency", "24",
		"-count-budget", "90s",
		"-count-timeout", "20s",
		"-count-concurrency", "48",
	})
	if err != nil {
		t.Fatalf("parse: %v", err)
	}

	if opts.cluster.Kubeconfig != "/etc/spinoza/kubeconfig" {
		t.Fatalf("kubeconfig = %q", opts.cluster.Kubeconfig)
	}
	if opts.cluster.ClientQPS != 200 {
		t.Fatalf("qps = %v", opts.cluster.ClientQPS)
	}
	if opts.cluster.ClientBurst != 400 {
		t.Fatalf("burst = %d", opts.cluster.ClientBurst)
	}
	if opts.cluster.SyncTimeout != 3*time.Minute {
		t.Fatalf("sync timeout = %s", opts.cluster.SyncTimeout)
	}
	if opts.cluster.WarmConcurrency != 24 {
		t.Fatalf("warm concurrency = %d", opts.cluster.WarmConcurrency)
	}
	if opts.cluster.CountBudget != 90*time.Second {
		t.Fatalf("count budget = %s", opts.cluster.CountBudget)
	}
	if opts.cluster.CountPerType != 20*time.Second {
		t.Fatalf("count timeout = %s", opts.cluster.CountPerType)
	}
	if opts.cluster.CountConcurrency != 48 {
		t.Fatalf("count concurrency = %d", opts.cluster.CountConcurrency)
	}
}

func TestNonPositiveBigClusterLimitsAreRefused(t *testing.T) {
	cases := [][]string{
		{"-qps", "0"},
		{"-qps", "NaN"},
		{"-qps", "+Inf"},
		{"-burst", "0"},
		{"-sync-timeout", "0s"},
		{"-warm-concurrency", "-1"},
		{"-count-budget", "-1s"},
		{"-count-timeout", "0s"},
		{"-count-concurrency", "-1"},
	}
	for _, args := range cases {
		t.Run(strings.Join(args, "="), func(t *testing.T) {
			if _, err := parseFlags(args); err == nil {
				t.Fatalf("arguments %v were accepted", args)
			}
		})
	}
}

func TestTheEnvironmentCanRaiseTheLimitsToo(t *testing.T) {
	t.Setenv("SPINOZA_KUBECONFIG", "/tmp/kubeconfig")
	t.Setenv("SPINOZA_QPS", "150")
	t.Setenv("SPINOZA_BURST", "300")
	t.Setenv("SPINOZA_SYNC_TIMEOUT", "2m")
	t.Setenv("SPINOZA_COUNT_CONCURRENCY", "36")

	opts, err := parseFlags(nil)
	if err != nil {
		t.Fatalf("parse: %v", err)
	}

	if opts.cluster.Kubeconfig != "/tmp/kubeconfig" {
		t.Fatalf("kubeconfig = %q", opts.cluster.Kubeconfig)
	}
	if opts.cluster.ClientQPS != 150 {
		t.Fatalf("qps = %v", opts.cluster.ClientQPS)
	}
	if opts.cluster.ClientBurst != 300 {
		t.Fatalf("burst = %d", opts.cluster.ClientBurst)
	}
	if opts.cluster.SyncTimeout != 2*time.Minute {
		t.Fatalf("sync timeout = %s", opts.cluster.SyncTimeout)
	}
	if opts.cluster.CountConcurrency != 36 {
		t.Fatalf("count concurrency = %d", opts.cluster.CountConcurrency)
	}
}

func TestUnreadableLimitsInTheEnvironmentFallBack(t *testing.T) {
	t.Setenv("SPINOZA_QPS", "fast")
	t.Setenv("SPINOZA_BURST", "lots")
	t.Setenv("SPINOZA_SYNC_TIMEOUT", "ages")

	opts, err := parseFlags(nil)
	if err != nil {
		t.Fatalf("parse: %v", err)
	}

	if opts.cluster.ClientQPS != defaultQPS {
		t.Fatalf("qps = %v, want the default", opts.cluster.ClientQPS)
	}
	if opts.cluster.ClientBurst != defaultBurst {
		t.Fatalf("burst = %d, want the default", opts.cluster.ClientBurst)
	}
	if opts.cluster.SyncTimeout != defaultSync {
		t.Fatalf("sync timeout = %s, want the default", opts.cluster.SyncTimeout)
	}
}

func TestAnUnknownFlagIsAnError(t *testing.T) {
	_, err := parseFlags([]string{"-nope"})

	if err == nil {
		t.Fatal("an unknown flag was accepted")
	}
}

func TestHelpIsNotAFailure(t *testing.T) {
	_, err := parseFlags([]string{"-h"})

	if !errors.Is(err, errHelp) {
		t.Fatalf("err = %v, want the help sentinel so the process can exit 0", err)
	}
}

func TestABadFlagIsStillAFailure(t *testing.T) {
	_, err := parseFlags([]string{"-nonsense"})

	if err == nil {
		t.Fatal("an unknown flag was accepted")
	}
	if errors.Is(err, errHelp) {
		t.Fatal("an unknown flag was treated as a help request")
	}
}

func TestSettingsComeFromTheProcessArguments(t *testing.T) {
	original := os.Args
	t.Cleanup(func() { os.Args = original })
	os.Args = []string{"spinoza", "--addr", "127.0.0.1:9999", "--log-level", "debug"}

	result, err := settingsFromArgs()
	if err != nil {
		t.Fatalf("settingsFromArgs: %v", err)
	}
	if result.addr != "127.0.0.1:9999" {
		t.Fatalf("addr = %q, want the one the arguments named", result.addr)
	}
	if result.logLevel != slog.LevelDebug {
		t.Fatalf("log level = %v, want debug", result.logLevel)
	}
}

func TestSettingsRefuseAnArgumentTheyDoNotKnow(t *testing.T) {
	original := os.Args
	t.Cleanup(func() { os.Args = original })
	os.Args = []string{"spinoza", "--nonsense"}

	_, err := settingsFromArgs()

	if err == nil {
		t.Fatal("settingsFromArgs returned nil error for an unknown flag")
	}
}

func TestTheCheckerCarriesThisBuildsVersion(t *testing.T) {
	checker := updateChecker()

	if got := checker.Status(t.Context()).Current; got != version.String() {
		t.Fatalf("current = %q, want this build's version", got)
	}
}