internal/procctl/env_test.go
Rama main · réplica de lectura del repositorio original.
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)
}
}
}