Files
tuio/internal/tui/config_test.go
T
teste.inabaandClaude Sonnet 5 2fecad021b fix: persist host port and compat mode in config.toml
The host-side port mapping and seccomp compatibility-mode selections
were only used in-memory for docker run args and never written to
config.toml, so a re-run of the installer couldn't recover them as
form defaults (worse, the host port field silently defaulted to the
container's internal port). Add host_port and seccomp_unconfined to
[server] and decode them back via AppConfig.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 15:08:24 -03:00

254 lines
7.2 KiB
Go

package tui
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/BurntSushi/toml"
)
// fullConfigValues returns a ConfigValues with every field populated with valid data.
func fullConfigValues() ConfigValues {
return ConfigValues{
Login: map[string]string{"user": "u", "password": "p"},
Server: map[string]string{
"port": "9090",
"timeout": "45",
"environment": "production",
"seccomp_unconfined": "Não",
},
Database: map[string]string{
"database_type": "postgres",
"database_url": "postgres://user:pass@db:5432/app_dono_db",
"max_conns": "20",
"min_conns": "5",
},
Cert: map[string]string{"cert_dir_path": "/etc/app-dono/certs"},
Application: map[string]string{
"central_server_url": "https://central.example.com:8443",
"enrollment_token": "tok-123",
},
Wireguard: map[string]string{
"privkey": "PRIVKEY_VALUE",
"vip": "127.0.0.1",
"psk": "PSK_VALUE",
"proxy_edps": "22:127.0.0.1:22",
"mtu": "1380",
"proto": "UDP",
},
}
}
func TestGenerateConfigTOML_RoundTrip(t *testing.T) {
cv := fullConfigValues()
out, err := GenerateConfigTOML(cv)
if err != nil {
t.Fatalf("GenerateConfigTOML returned error: %v", err)
}
var cfg AppConfig
if _, err := toml.Decode(out, &cfg); err != nil {
t.Fatalf("generated output is not valid TOML / failed to decode: %v\n---\n%s", err, out)
}
if cfg.Server.Timeout != 45 {
t.Errorf("timeout_seconds = %d, want 45", cfg.Server.Timeout)
}
if cfg.Server.Environment != "production" {
t.Errorf("environment = %q, want %q", cfg.Server.Environment, "production")
}
if cfg.Server.HostPort != 9090 {
t.Errorf("host_port = %d, want 9090", cfg.Server.HostPort)
}
if cfg.Server.SeccompUnconfined {
t.Errorf("seccomp_unconfined = true, want false")
}
if cfg.Database.Type != "postgres" {
t.Errorf("database type = %q, want %q", cfg.Database.Type, "postgres")
}
if cfg.Database.URL != "postgres://user:pass@db:5432/app_dono_db" {
t.Errorf("database url = %q", cfg.Database.URL)
}
if cfg.Database.MaxConns != 20 {
t.Errorf("max_conns = %d, want 20", cfg.Database.MaxConns)
}
if cfg.Database.MinConns != 5 {
t.Errorf("min_conns = %d, want 5", cfg.Database.MinConns)
}
if cfg.Certificates.DirPath != "/etc/app-dono/certs" {
t.Errorf("mapped_dir = %q", cfg.Certificates.DirPath)
}
if cfg.Application.CentralServerURL != "https://central.example.com:8443" {
t.Errorf("central_server_url = %q", cfg.Application.CentralServerURL)
}
if cfg.Application.EnrollmentToken != "tok-123" {
t.Errorf("enrollment_token = %q", cfg.Application.EnrollmentToken)
}
}
// The container always listens on containerAppPort internally; the user-provided
// "Porta (host)" value is only the host-side mapping and must never leak into the
// generated config.toml. This guard fails loudly if that invariant is reverted.
func TestGenerateConfigTOML_PortIsAlwaysContainerPort(t *testing.T) {
cv := fullConfigValues()
cv.Server["port"] = "9090" // host port, must NOT appear as the config port
out, err := GenerateConfigTOML(cv)
if err != nil {
t.Fatalf("GenerateConfigTOML returned error: %v", err)
}
var cfg AppConfig
if _, err := toml.Decode(out, &cfg); err != nil {
t.Fatalf("failed to decode: %v", err)
}
if cfg.Server.Port != containerAppPort {
t.Errorf("config port = %d, want fixed containerAppPort %d", cfg.Server.Port, containerAppPort)
}
if cfg.Server.HostPort != 9090 {
t.Errorf("host_port = %d, want 9090", cfg.Server.HostPort)
}
}
func TestGenerateWireguardConfig(t *testing.T) {
tests := []struct {
name string
wireguard map[string]string
wantContains []string
wantNotContns []string
}{
{
name: "all fields set",
wireguard: map[string]string{
"privkey": "PK", "vip": "10.0.0.1", "psk": "PSK",
"proxy_edps": "22:127.0.0.1:22", "mtu": "1400", "proto": "TCP",
},
wantContains: []string{
"PRIVKEY=PK", "VIP=10.0.0.1", "PSK=PSK",
"PROXY_EDPS=22:127.0.0.1:22", "MTU=1400", "PROTO=TCP",
},
},
{
name: "empty mtu is commented out",
wireguard: map[string]string{
"privkey": "PK", "vip": "10.0.0.1", "psk": "PSK",
"proxy_edps": "22:127.0.0.1:22", "mtu": "", "proto": "UDP",
},
wantContains: []string{"# MTU=1380", "PROTO=UDP"},
wantNotContns: []string{"\nMTU="},
},
{
name: "empty proto defaults to UDP",
wireguard: map[string]string{
"privkey": "PK", "vip": "10.0.0.1", "psk": "PSK",
"proxy_edps": "22:127.0.0.1:22", "proto": "",
},
wantContains: []string{"PROTO=UDP"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
out, err := GenerateWireguardConfig(ConfigValues{Wireguard: tt.wireguard})
if err != nil {
t.Fatalf("GenerateWireguardConfig returned error: %v", err)
}
for _, want := range tt.wantContains {
if !strings.Contains(out, want) {
t.Errorf("output missing %q:\n%s", want, out)
}
}
for _, notWant := range tt.wantNotContns {
if strings.Contains(out, notWant) {
t.Errorf("output should not contain %q:\n%s", notWant, out)
}
}
})
}
}
func TestWriteConfigFile_NumericValidation(t *testing.T) {
numeric := []string{"port", "timeout", "max_conns", "min_conns"}
for _, field := range numeric {
t.Run("invalid_"+field, func(t *testing.T) {
cv := fullConfigValues()
switch field {
case "port", "timeout":
cv.Server[field] = "abc"
case "max_conns", "min_conns":
cv.Database[field] = "abc"
}
path := filepath.Join(t.TempDir(), "config.toml")
err := WriteConfigFile(cv, path)
if err == nil {
t.Fatalf("expected error for non-numeric %q, got nil", field)
}
if !strings.Contains(err.Error(), field) {
t.Errorf("error %q does not mention field %q", err.Error(), field)
}
if _, statErr := os.Stat(path); statErr == nil {
t.Errorf("file should not have been written on validation failure")
}
})
}
}
func TestWriteConfigFile_ValidWritesFile(t *testing.T) {
cv := fullConfigValues()
path := filepath.Join(t.TempDir(), "config.toml")
if err := WriteConfigFile(cv, path); err != nil {
t.Fatalf("WriteConfigFile returned error: %v", err)
}
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("config file was not written: %v", err)
}
var cfg AppConfig
if _, err := toml.Decode(string(data), &cfg); err != nil {
t.Fatalf("written file is not valid TOML: %v", err)
}
}
func TestWriteWireguardConfigFile_MTUValidation(t *testing.T) {
tests := []struct {
name string
mtu string
wantErr bool
}{
{name: "valid mtu", mtu: "1380", wantErr: false},
{name: "empty mtu allowed", mtu: "", wantErr: false},
{name: "non-numeric mtu rejected", mtu: "big", wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cv := fullConfigValues()
cv.Wireguard["mtu"] = tt.mtu
path := filepath.Join(t.TempDir(), "envs")
err := WriteWireguardConfigFile(cv, path)
if tt.wantErr && err == nil {
t.Fatalf("expected error for mtu %q, got nil", tt.mtu)
}
if !tt.wantErr && err != nil {
t.Fatalf("unexpected error for mtu %q: %v", tt.mtu, err)
}
if !tt.wantErr {
if _, statErr := os.Stat(path); statErr != nil {
t.Errorf("envs file should have been written: %v", statErr)
}
}
})
}
}