a73x

internal/cli/env_test.go

Ref:   Size: 4.4 KiB   History

package cli

import (
	"os"
	"path/filepath"
	"strings"
	"testing"
)

// cleanEnv isolates a test from the developer's own laptop: every EITRI_*
// variable unset, and the config file pointed at an empty temp dir so FromEnv
// cannot read a real ~/.eitri/config.json. It returns the config path, for the
// tests that want to put a file there.
func cleanEnv(t *testing.T) string {
	t.Helper()
	dir := t.TempDir()
	for _, v := range []string{
		"EITRI_URL", "EITRI_GATE", "EITRI_TENANT", "EITRI_CA", "EITRI_KEY",
		"EITRI_KNOWN_HOSTS", "EITRI_TOKEN",
	} {
		t.Setenv(v, "")
	}
	cfg := filepath.Join(dir, "config.json")
	t.Setenv("EITRI_CONFIG", cfg)
	t.Setenv("HOME", dir)
	return cfg
}

func TestFromEnvDefaults(t *testing.T) {
	cleanEnv(t)
	t.Setenv("EITRI_URL", "http://192.0.2.10:8080")
	t.Setenv("EITRI_GATE", "192.0.2.10:2222")
	t.Setenv("EITRI_TENANT", "acme")
	t.Setenv("HOME", "/home/u")

	e, err := FromEnv()
	if err != nil {
		t.Fatal(err)
	}
	if e.URL != "http://192.0.2.10:8080" || e.Gate != "192.0.2.10:2222" || e.Tenant != "acme" {
		t.Errorf("required fields: %+v", e)
	}
	for got, want := range map[string]string{
		e.CA:         filepath.Join("/home/u", ".ssh", "eitri_user_ca"),
		e.Key:        filepath.Join("/home/u", ".ssh", "id_ed25519"),
		e.KnownHosts: filepath.Join("/home/u", ".ssh", "eitri_known_hosts"),
	} {
		if got != want {
			t.Errorf("default: got %q want %q", got, want)
		}
	}
}

func TestFromEnvHostedDefaults(t *testing.T) {
	// A clean environment targets the hosted service at eitri.sh — no
	// configuration needed to reach it (TestFromEnvDefaults proves the env
	// still overrides these). The gate is the exception: left empty here so
	// `eitri ssh` can ask the plane to name it rather than assuming.
	cleanEnv(t)
	e, err := FromEnv()
	if err != nil {
		t.Fatalf("clean env must succeed: %v", err)
	}
	if e.URL != "https://console.eitri.sh" {
		t.Errorf("hosted default URL = %q", e.URL)
	}
	if e.Gate != "" {
		t.Errorf("gate = %q, want empty so the plane can name it", e.Gate)
	}
}

func TestFromEnvTenantOptional(t *testing.T) {
	// The tenant is optional: `eitri ssh` derives it from the credential when
	// nothing pins it, so an unset tenant is not a FromEnv error.
	cleanEnv(t)
	t.Setenv("EITRI_URL", "http://192.0.2.10:8080")
	e, err := FromEnv()
	if err != nil {
		t.Fatalf("unset tenant must be accepted: %v", err)
	}
	if e.Tenant != "" {
		t.Errorf("tenant = %q, want empty", e.Tenant)
	}
}

// TestFromEnvPrecedence is the whole layering in one test: an EITRI_* variable
// beats the config file, the config file beats the hosted default, and a field
// nobody set falls through to the default. This is what makes `eitri init`
// worth running — after it, a laptop with an empty environment still reaches
// its own plane as its own tenant.
func TestFromEnvPrecedence(t *testing.T) {
	cfg := cleanEnv(t)
	if err := SaveConfig(cfg, Config{
		URL:    "http://192.0.2.10:8080",
		Gate:   "192.0.2.10:2222",
		Tenant: "acme",
		CA:     "/keys/ca",
		Key:    "/keys/user",
	}); err != nil {
		t.Fatal(err)
	}

	// File alone: every field comes from it.
	e, err := FromEnv()
	if err != nil {
		t.Fatal(err)
	}
	if e.URL != "http://192.0.2.10:8080" || e.Gate != "192.0.2.10:2222" ||
		e.Tenant != "acme" || e.CA != "/keys/ca" || e.Key != "/keys/user" {
		t.Errorf("from config: %+v", e)
	}

	// The environment overrides it, field by field.
	t.Setenv("EITRI_URL", "http://other:8080")
	t.Setenv("EITRI_GATE", "other:2222")
	t.Setenv("EITRI_TENANT", "beta")
	e, err = FromEnv()
	if err != nil {
		t.Fatal(err)
	}
	if e.URL != "http://other:8080" || e.Gate != "other:2222" || e.Tenant != "beta" {
		t.Errorf("env must override the file: %+v", e)
	}
	if e.CA != "/keys/ca" {
		t.Errorf("an unset variable must leave the file's value: ca = %q", e.CA)
	}
}

// A config that exists but does not parse — the half-written file an aborted
// run could leave — is an error naming the path, never a silent fallback to
// the hosted defaults while the user believes they are on their own plane.
func TestFromEnvRefusesUnreadableConfig(t *testing.T) {
	cfg := cleanEnv(t)
	if err := os.WriteFile(cfg, []byte(`{"url": "http://192.0`), 0o600); err != nil {
		t.Fatal(err)
	}
	_, err := FromEnv()
	if err == nil {
		t.Fatal("a corrupt config must not resolve to the hosted defaults")
	}
	if got := err.Error(); !strings.Contains(got, cfg) || !strings.Contains(got, "eitri init") {
		t.Errorf("error must name the file and the remedy: %v", err)
	}
}