package config import ( "os" "path/filepath" "strings" "testing" ) func writeTempConfig(t *testing.T, body string) string { t.Helper() dir := t.TempDir() path := filepath.Join(dir, "config.yaml") if err := os.WriteFile(path, []byte(body), 0o600); err != nil { t.Fatal(err) } return path } func TestLoadValidConfig(t *testing.T) { path := writeTempConfig(t, ` listen: host: 127.0.0.1 port: 13306 local_auth: username: root password: "local-secret" tunnel: url: "https://example.invalid/ntunnel?token=abc" remote: host: mysql port: 3306 username: remote-root password: "remote-secret" database: app charset: utf8mb4 options: request_timeout: 15 max_connections: 3 log_level: debug log_sql: false `) cfg, err := Load(path, Overrides{}) if err != nil { t.Fatalf("Load: %v", err) } if cfg.ListenHost != "127.0.0.1" || cfg.ListenPort != 13306 { t.Fatalf("listen = %s:%d", cfg.ListenHost, cfg.ListenPort) } if cfg.RemoteHost != "mysql" || cfg.RemoteUsername != "remote-root" { t.Fatalf("remote mismatch") } if cfg.RequestTimeout.Seconds() != 15 { t.Fatalf("timeout = %s", cfg.RequestTimeout) } } func TestRejectUnspecifiedListen(t *testing.T) { path := writeTempConfig(t, ` listen: host: 0.0.0.0 port: 13306 local_auth: username: root password: x tunnel: url: "https://example.invalid/?token=t" remote: host: mysql username: root password: x `) _, err := Load(path, Overrides{}) if err == nil || !strings.Contains(err.Error(), "0.0.0.0") { t.Fatalf("expected unspecified listen error, got %v", err) } } func TestMissingTunnelURL(t *testing.T) { path := writeTempConfig(t, ` local_auth: username: root password: x remote: host: mysql username: root password: x `) _, err := Load(path, Overrides{}) if err == nil || !strings.Contains(err.Error(), "tunnel.url") { t.Fatalf("expected tunnel.url error, got %v", err) } } func TestCLIOverrides(t *testing.T) { path := writeTempConfig(t, ` listen: host: 127.0.0.1 port: 13306 local_auth: username: root password: x tunnel: url: "https://example.invalid/?token=t" remote: host: mysql username: root password: x `) cfg, err := Load(path, Overrides{ListenHost: "127.0.0.1", ListenPort: 23306}) if err != nil { t.Fatal(err) } if cfg.ListenPort != 23306 { t.Fatalf("port override failed: %d", cfg.ListenPort) } } func TestRedactedTunnelURL(t *testing.T) { got := RedactedTunnelURL("https://bth-ntunnel.example/?token=super-secret&x=1") if strings.Contains(got, "super-secret") { t.Fatalf("token leaked: %s", got) } if !strings.Contains(got, "token=%2A%2A%2A") && !strings.Contains(got, "token=***") { t.Fatalf("token not redacted: %s", got) } } func TestWarnWhenTokenMissing(t *testing.T) { path := writeTempConfig(t, ` local_auth: username: root password: x tunnel: url: "https://example.invalid/ntunnel" remote: host: mysql username: root password: x `) cfg, err := Load(path, Overrides{}) if err != nil { t.Fatal(err) } found := false for _, w := range cfg.Warnings { if strings.Contains(w, "token") { found = true } } if !found { t.Fatalf("expected token warning, got %#v", cfg.Warnings) } }