Ctrl K
ComunidadGuías…

internal/procctl/env_test.go

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

Archivo original ↗Preguntar a KiBot
package procctl

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

const testTemplate = `## Server -- Telegram-compatible MTProto server settings
# Product name shown to the clients
TELESRV_PRODUCT_NAME=telesrv
# Admin login password
TELESRV_ADMIN_UI_PASSWORD=changeme
# Secret-chat files retention (not a credential)
TELESRV_SECRET_CHAT_DELETE_FILE_AFTER_DOWNLOAD=true

## Optional -- commented-out optional fields
# API token for integrations
# TELESRV_API_TOKEN=
# Debug logs (off by default)
# TELESRV_DEBUG_LOGS=false

# ============ end ============
`

func writeRepo(t *testing.T) string {
	t.Helper()
	dir := t.TempDir()
	if err := os.WriteFile(filepath.Join(dir, ".env.example"), []byte(testTemplate), 0o644); err != nil {
		t.Fatal(err)
	}
	return dir
}

func TestReadEnvGroupsNeverParsesPlaceholders(t *testing.T) {
	m := NewManager(writeRepo(t))
	groups, err := m.ReadEnvGroups()
	if err != nil {
		t.Fatal(err)
	}
	if len(groups) != 2 {
		t.Fatalf("groups = %d, want 2", len(groups))
	}
	server := groups[0]
	if server.Title != "Server" || server.Description != "Telegram-compatible MTProto server settings" {
		t.Fatalf("server group = %+v", server)
	}
	if len(server.Fields) != 3 {
		t.Fatalf("server fields = %d, want 3", len(server.Fields))
	}
	if f := server.Fields[0]; f.Key != "TELESRV_PRODUCT_NAME" || f.Value != "telesrv" || !f.EnabledByDefault || f.Sensitive {
		t.Fatalf("product field = %+v", f)
	}
	if f := server.Fields[1]; f.Key != "TELESRV_ADMIN_UI_PASSWORD" || f.Value != "changeme" || !f.Sensitive {
		t.Fatalf("password field = %+v", f)
	}
	if f := server.Fields[2]; f.Key != "TELESRV_SECRET_CHAT_DELETE_FILE_AFTER_DOWNLOAD" || f.Sensitive {
		t.Fatalf("secret-chat field must not be sensitive: %+v", f)
	}
	opt := groups[1]
	if len(opt.Fields) != 2 {
		t.Fatalf("optional fields = %d, want 2", len(opt.Fields))
	}
	if f := opt.Fields[0]; f.Key != "TELESRV_API_TOKEN" || f.EnabledByDefault || !f.Sensitive || f.Value != "" {
		t.Fatalf("api token field = %+v", f)
	}
	if f := opt.Fields[1]; f.Key != "TELESRV_DEBUG_LOGS" || f.EnabledByDefault || f.Value != "" {
		t.Fatalf("debug logs field = %+v", f)
	}
}

func TestReadEnvGroupsFillsValuesFromEnv(t *testing.T) {
	dir := writeRepo(t)
	if err := os.WriteFile(filepath.Join(dir, ".env"), []byte("TELESRV_PRODUCT_NAME=my-server\nTELESRV_ADMIN_UI_PASSWORD=secretpw\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	m := NewManager(dir)
	groups, err := m.ReadEnvGroups()
	if err != nil {
		t.Fatal(err)
	}
	if v := groups[0].Fields[0].Value; v != "my-server" {
		t.Fatalf(".env value not picked up: %q", v)
	}
	if v := groups[0].Fields[1].Value; v != "secretpw" {
		t.Fatalf("password value not picked up: %q", v)
	}

	// Optional field enabled in .env shows its .env value.
	if err := os.WriteFile(filepath.Join(dir, ".env"), []byte("TELESRV_API_TOKEN=abc123\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	groups, err = m.ReadEnvGroups()
	if err != nil {
		t.Fatal(err)
	}
	if v := groups[1].Fields[0].Value; v != "abc123" {
		t.Fatalf("optional enabled value = %q, want abc123", v)
	}
}

func TestWriteEnvValuesRewritesInPlacePreservingLayout(t *testing.T) {
	dir := writeRepo(t)
	if err := os.WriteFile(filepath.Join(dir, ".env"), []byte("TELESRV_ADMIN_UI_PASSWORD=existingpw\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	m := NewManager(dir)
	if err := m.WriteEnvValues(map[string]string{"TELESRV_PRODUCT_NAME": "renamed"}); err != nil {
		t.Fatal(err)
	}
	data, err := os.ReadFile(filepath.Join(dir, ".env"))
	if err != nil {
		t.Fatal(err)
	}
	got := string(data)
	// Header/comment layout preserved verbatim.
	if !strings.Contains(got, "## Server -- Telegram-compatible MTProto server settings") ||
		!strings.Contains(got, "# Product name shown to the clients") {
		t.Fatalf("layout not preserved:\n%s", got)
	}
	// Only the touched key rewrote; the untouched password kept its .env value.
	if !strings.Contains(got, "TELESRV_PRODUCT_NAME=renamed") {
		t.Fatalf("touched key not updated:\n%s", got)
	}
	if !strings.Contains(got, "TELESRV_ADMIN_UI_PASSWORD=existingpw") {
		t.Fatalf("untouched existing value was not preserved:\n%s", got)
	}
}

func TestWriteEnvValuesUncommentsOptionalField(t *testing.T) {
	dir := writeRepo(t)
	if err := os.WriteFile(filepath.Join(dir, ".env"), []byte("TELESRV_ADMIN_UI_PASSWORD=existingpw\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	m := NewManager(dir)
	if err := m.WriteEnvValues(map[string]string{"TELESRV_API_TOKEN": "tok-123", "TELESRV_DEBUG_LOGS": ""}); err != nil {
		t.Fatal(err)
	}
	data, err := os.ReadFile(filepath.Join(dir, ".env"))
	if err != nil {
		t.Fatal(err)
	}
	got := string(data)
	if !strings.Contains(got, "TELESRV_API_TOKEN=tok-123") {
		t.Fatalf("optional field not uncommented+set:\n%s", got)
	}
	// Empty value keeps the commented-out template line.
	if !strings.Contains(got, "# TELESRV_DEBUG_LOGS=false") {
		t.Fatalf("empty optional field should stay commented:\n%s", got)
	}
}

func TestWriteEnvValuesClearsOptionalFieldBackToCommented(t *testing.T) {
	dir := writeRepo(t)
	if err := os.WriteFile(filepath.Join(dir, ".env"), []byte("TELESRV_API_TOKEN=tok-123\nTELESRV_ADMIN_UI_PASSWORD=existingpw\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	m := NewManager(dir)
	if err := m.WriteEnvValues(map[string]string{"TELESRV_API_TOKEN": ""}); err != nil {
		t.Fatal(err)
	}
	data, err := os.ReadFile(filepath.Join(dir, ".env"))
	if err != nil {
		t.Fatal(err)
	}
	got := string(data)
	// Clearing an active optional field falls back to the template's commented line.
	if strings.Contains(got, "TELESRV_API_TOKEN=tok-123") || !strings.Contains(got, "# TELESRV_API_TOKEN=") {
		t.Fatalf("cleared optional field should revert to commented template line:\n%s", got)
	}
}

func TestReadEnvGroupsMissingTemplateReturnsEmpty(t *testing.T) {
	m := NewManager(t.TempDir())
	groups, err := m.ReadEnvGroups()
	if err != nil || len(groups) != 0 {
		t.Fatalf("groups=%d err=%v", len(groups), err)
	}
}

// TestRepoEnvExampleGroups ensure the shipped .env.example keeps staying a
// valid template the panel can actually edit: every key must end up inside a
// `##` group (fields that fall between a section break and the next header —
// or before the first header — are orphaned and silently dropped from the
// panel), and no header may be a lone `## line` the regex refuses.
func TestRepoEnvExampleGroups(t *testing.T) {
	repoRoot, err := filepath.Abs(filepath.Join("..", ".."))
	if err != nil {
		t.Fatal(err)
	}
	data, err := os.ReadFile(filepath.Join(repoRoot, ".env.example"))
	if err != nil {
		t.Fatal(err)
	}
	var templateKeys []string
	for _, raw := range strings.Split(string(data), "\n") {
		line := strings.TrimSpace(raw)
		if a := activeFieldRe.FindStringSubmatch(line); a != nil {
			templateKeys = append(templateKeys, a[1])
			continue
		}
		if c := commentedFieldRe.FindStringSubmatch(line); c != nil {
			templateKeys = append(templateKeys, c[1])
			continue
		}
	}

	m := NewManager(repoRoot)
	groups, err := m.ReadEnvGroups()
	if err != nil {
		t.Fatal(err)
	}
	covered := map[string]bool{}
	for _, g := range groups {
		for _, f := range g.Fields {
			covered[f.Key] = true
		}
	}
	var missing []string
	for _, key := range templateKeys {
		if !covered[key] {
			missing = append(missing, key)
		}
	}
	if len(missing) > 0 {
		t.Fatalf("keys outside any `##` group would be invisible to the panel: %v", missing)
	}
}

func TestSensitiveDetection(t *testing.T) {
	cases := map[string]bool{
		"TELESRV_ADMIN_UI_PASSWORD":                      true,
		"TELESRV_SECRET_KEY":                             true,
		"TELESRV_API_TOKEN":                              true,
		"TELESRV_REDIS_PASSWORD":                         true,
		"TELESRV_SECRET_CHAT_DELETE_FILE_AFTER_DOWNLOAD": false,
		"TELESRV_PRODUCT_NAME":                           false,
	}
	for key, want := range cases {
		got := sensitiveKeyRe.MatchString(key) && !sensitiveKeyExceptRe.MatchString(key)
		if got != want {
			t.Fatalf("sensitive(%q) = %v, want %v", key, got, want)
		}
	}
}