Files
ssh-netbox-wrapper/internal/config/config_test.go
T
Sebastian UnterschützandClaude Opus 5.5 878701aba7 feat(shortcuts): regex rewrite rules for host shortcuts
shortcuts.rewrites applies regex replacements after the domain suffix
is stripped, e.g. ^([a-z]+)[0-9]+- -> $1 turns fsn1-bx1.hoster.de into
fsnbx1. The shortcut works for connecting (cached hosts), shell
aliases, and Tab completion, where it is offered when the full name
does not match the typed text. Rules are editable in netssh configure,
and netssh alias --test HOSTNAME shows a name's shortcut and the cached
hosts it matches.

The shortcut lookup only ran when domains or strip_hyphens were set;
it now also runs for rewrite-only configurations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 22:42:13 +01:00

199 lines
5.4 KiB
Go

package config
import (
"os"
"path/filepath"
"strings"
"testing"
)
func writeConfig(t *testing.T, content string) string {
t.Helper()
dir := t.TempDir()
path := filepath.Join(dir, "netssh.yaml")
if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
t.Fatal(err)
}
return dir
}
func loadFromDir(t *testing.T, dir string) *Config {
t.Helper()
// Override UserConfigDir by pointing viper at our temp dir via env isn't
// straightforward, so we exercise Load() by temporarily changing XDG_CONFIG_HOME.
orig := os.Getenv("XDG_CONFIG_HOME")
os.Setenv("XDG_CONFIG_HOME", dir)
t.Cleanup(func() { os.Setenv("XDG_CONFIG_HOME", orig) })
cfg, err := Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
return cfg
}
func TestLoad_V2TokenVersion_Preserved(t *testing.T) {
dir := writeConfig(t, `
netbox:
url: "https://netbox.example.com"
token: "nbt_abc123"
token_version: 2
`)
cfg := loadFromDir(t, dir)
if cfg.NetBox.TokenVersion != 2 {
t.Errorf("TokenVersion: got %d, want 2", cfg.NetBox.TokenVersion)
}
}
func TestLoad_V1TokenVersion_Preserved(t *testing.T) {
dir := writeConfig(t, `
netbox:
url: "https://netbox.example.com"
token: "legacyhex123"
token_version: 1
`)
cfg := loadFromDir(t, dir)
if cfg.NetBox.TokenVersion != 1 {
t.Errorf("TokenVersion: got %d, want 1", cfg.NetBox.TokenVersion)
}
}
func TestLoad_AutoDetectsV2_WhenFieldMissing(t *testing.T) {
dir := writeConfig(t, `
netbox:
url: "https://netbox.example.com"
token: "nbt_mytoken"
`)
cfg := loadFromDir(t, dir)
if cfg.NetBox.TokenVersion != 2 {
t.Errorf("TokenVersion: got %d, want 2 (auto-detected from nbt_ prefix)", cfg.NetBox.TokenVersion)
}
}
func TestLoad_AutoDetectsV1_WhenFieldMissing(t *testing.T) {
dir := writeConfig(t, `
netbox:
url: "https://netbox.example.com"
token: "abc123def456"
`)
cfg := loadFromDir(t, dir)
if cfg.NetBox.TokenVersion != 1 {
t.Errorf("TokenVersion: got %d, want 1 (auto-detected from plain token)", cfg.NetBox.TokenVersion)
}
}
func TestLoad_TokenVersionZero_WhenNoToken(t *testing.T) {
dir := writeConfig(t, `
netbox:
url: "https://netbox.example.com"
`)
cfg := loadFromDir(t, dir)
if cfg.NetBox.TokenVersion != 0 {
t.Errorf("TokenVersion: got %d, want 0 (no token present)", cfg.NetBox.TokenVersion)
}
}
func TestLoad_Defaults(t *testing.T) {
dir := writeConfig(t, `
netbox:
url: "https://netbox.example.com"
token: "nbt_x"
`)
cfg := loadFromDir(t, dir)
if cfg.Cache.TTL != 3600 {
t.Errorf("default cache.ttl: got %d, want 3600", cfg.Cache.TTL)
}
if cfg.Cache.Path == "" {
t.Error("cache.path should be auto-set when empty")
}
}
func TestLoad_MissingFile_ReturnsEmptyConfig(t *testing.T) {
orig := os.Getenv("XDG_CONFIG_HOME")
os.Setenv("XDG_CONFIG_HOME", t.TempDir()) // dir exists but no netssh.yaml
defer os.Setenv("XDG_CONFIG_HOME", orig)
cfg, err := Load()
if err != nil {
t.Fatalf("Load on missing file should not error: %v", err)
}
if cfg.NetBox.URL != "" {
t.Errorf("expected empty URL, got %q", cfg.NetBox.URL)
}
}
func TestLoad_InvalidYAML_ReturnsError(t *testing.T) {
dir := writeConfig(t, "not: valid: yaml: [[[")
orig := os.Getenv("XDG_CONFIG_HOME")
os.Setenv("XDG_CONFIG_HOME", dir)
defer os.Setenv("XDG_CONFIG_HOME", orig)
_, err := Load()
if err == nil {
t.Error("invalid YAML should return an error")
}
}
func TestPath_ReturnsNonEmpty(t *testing.T) {
p := Path()
if p == "" {
t.Error("Path() should return a non-empty string")
}
if filepath.Base(p) != "netssh.yaml" {
t.Errorf("Path() base: got %q, want netssh.yaml", filepath.Base(p))
}
}
func TestEnvironmentOverridesWithoutConfigKeys(t *testing.T) {
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
t.Setenv("NETSSH_NETBOX_URL", "https://env.example")
t.Setenv("NETSSH_NETBOX_TOKEN", "nbt_env")
t.Setenv("NETSSH_SSH_DEFAULT_PORT", "2222")
cfg, err := Load()
if err != nil {
t.Fatal(err)
}
if cfg.NetBox.URL != "https://env.example" || cfg.NetBox.Token != "nbt_env" || cfg.NetBox.TokenVersion != 2 || cfg.SSH.DefaultPort != 2222 {
t.Fatalf("env ignored: %+v", cfg)
}
}
func TestCommandAliasEnvironmentAndValidation(t *testing.T) {
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
t.Setenv("NETSSH_SHELL_ALIAS", "j")
cfg, err := Load()
if err != nil || cfg.Shell.Alias != "j" {
t.Fatalf("alias env: %+v, %v", cfg, err)
}
for _, name := range []string{"netssh", "if", "alias", "j;touch /tmp/bad", "a b", "1bad"} {
t.Setenv("NETSSH_SHELL_ALIAS", name)
if _, err := Load(); err == nil {
t.Errorf("unsafe alias accepted: %q", name)
}
}
for _, name := range []string{"", "j", "jump_server", "goSSH"} {
if err := ValidateCommandAlias(name); err != nil {
t.Errorf("valid alias rejected: %s: %v", name, err)
}
}
}
func TestLoad_ShortcutRewrites(t *testing.T) {
dir := writeConfig(t, "shortcuts:\n rewrites:\n - pattern: '^([a-z]+)[0-9]+-'\n replace: '$1'\n")
cfg := loadFromDir(t, dir)
want := []ShortcutRewrite{{Pattern: `^([a-z]+)[0-9]+-`, Replace: "$1"}}
if len(cfg.Shortcuts.Rewrites) != 1 || cfg.Shortcuts.Rewrites[0] != want[0] || !cfg.Shortcuts.Enabled() {
t.Fatalf("rewrites: %+v", cfg.Shortcuts)
}
}
func TestLoad_InvalidShortcutRewrite(t *testing.T) {
for _, rule := range []string{"pattern: '[a-'", "replace: x"} {
t.Setenv("XDG_CONFIG_HOME", writeConfig(t, "shortcuts:\n rewrites:\n - "+rule+"\n"))
if _, err := Load(); err == nil || !strings.Contains(err.Error(), "shortcuts.rewrites[0]") {
t.Errorf("%s: got %v", rule, err)
}
}
}