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>
545 lines
16 KiB
Go
545 lines
16 KiB
Go
package setup
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.zb-server.de/Sebi/ssh-netbox-wrapper/internal/config"
|
|
)
|
|
|
|
func TestSave_WritesFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
orig := os.Getenv("XDG_CONFIG_HOME")
|
|
os.Setenv("XDG_CONFIG_HOME", dir)
|
|
defer os.Setenv("XDG_CONFIG_HOME", orig)
|
|
|
|
cfg := config.Config{
|
|
NetBox: config.NetBoxConfig{
|
|
URL: "https://netbox.example.com",
|
|
Token: "nbt_abc123",
|
|
TokenVersion: 2,
|
|
},
|
|
SSH: config.SSHConfig{DefaultUser: "admin"},
|
|
Resolver: config.ResolverConfig{
|
|
Strategies: []string{"primary_ip", "management_subnet"},
|
|
ManagementSubnets: []string{"10.0.0.0/8"},
|
|
},
|
|
Cache: config.CacheConfig{TTL: 3600},
|
|
}
|
|
|
|
if err := save(cfg); err != nil {
|
|
t.Fatalf("save: %v", err)
|
|
}
|
|
|
|
data, err := os.ReadFile(filepath.Join(dir, "netssh.yaml"))
|
|
if err != nil {
|
|
t.Fatalf("reading saved file: %v", err)
|
|
}
|
|
content := string(data)
|
|
|
|
for _, want := range []string{
|
|
`"https://netbox.example.com"`,
|
|
`"nbt_abc123"`,
|
|
`token_version: 2`,
|
|
`- primary_ip`,
|
|
`- management_subnet`,
|
|
`- 10.0.0.0/8`,
|
|
`ttl: 3600`,
|
|
`"admin"`,
|
|
} {
|
|
if !strings.Contains(content, want) {
|
|
t.Errorf("saved config missing %q\nfull content:\n%s", want, content)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSave_FilePermissions(t *testing.T) {
|
|
dir := t.TempDir()
|
|
orig := os.Getenv("XDG_CONFIG_HOME")
|
|
os.Setenv("XDG_CONFIG_HOME", dir)
|
|
defer os.Setenv("XDG_CONFIG_HOME", orig)
|
|
|
|
if err := save(config.Config{
|
|
NetBox: config.NetBoxConfig{URL: "http://x", Token: "t", TokenVersion: 1},
|
|
Cache: config.CacheConfig{TTL: 60},
|
|
}); err != nil {
|
|
t.Fatalf("save: %v", err)
|
|
}
|
|
|
|
info, err := os.Stat(filepath.Join(dir, "netssh.yaml"))
|
|
if err != nil {
|
|
t.Fatalf("stat: %v", err)
|
|
}
|
|
if perm := info.Mode().Perm(); perm != 0o600 {
|
|
t.Errorf("file permissions: got %o, want 600", perm)
|
|
}
|
|
}
|
|
|
|
func TestSave_OmitsEmptyOptionalFields(t *testing.T) {
|
|
dir := t.TempDir()
|
|
orig := os.Getenv("XDG_CONFIG_HOME")
|
|
os.Setenv("XDG_CONFIG_HOME", dir)
|
|
defer os.Setenv("XDG_CONFIG_HOME", orig)
|
|
|
|
cfg := config.Config{
|
|
NetBox: config.NetBoxConfig{URL: "http://x", Token: "t", TokenVersion: 1},
|
|
Cache: config.CacheConfig{TTL: 60},
|
|
// No DefaultUser, no ManagementSubnets, no InterfaceName
|
|
}
|
|
if err := save(cfg); err != nil {
|
|
t.Fatalf("save: %v", err)
|
|
}
|
|
|
|
data, _ := os.ReadFile(filepath.Join(dir, "netssh.yaml"))
|
|
content := string(data)
|
|
|
|
for _, absent := range []string{"default_user", "management_subnets", "interface_name"} {
|
|
if strings.Contains(content, absent) {
|
|
t.Errorf("config should not contain %q when field is empty\nfull content:\n%s", absent, content)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSave_CreatesConfigDir(t *testing.T) {
|
|
dir := filepath.Join(t.TempDir(), "does", "not", "exist")
|
|
orig := os.Getenv("XDG_CONFIG_HOME")
|
|
os.Setenv("XDG_CONFIG_HOME", dir)
|
|
defer os.Setenv("XDG_CONFIG_HOME", orig)
|
|
|
|
if err := save(config.Config{
|
|
NetBox: config.NetBoxConfig{URL: "http://x", Token: "t", TokenVersion: 1},
|
|
Cache: config.CacheConfig{TTL: 60},
|
|
}); err != nil {
|
|
t.Fatalf("save should create missing directories: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSave_DefaultPort(t *testing.T) {
|
|
dir := t.TempDir()
|
|
orig := os.Getenv("XDG_CONFIG_HOME")
|
|
os.Setenv("XDG_CONFIG_HOME", dir)
|
|
defer os.Setenv("XDG_CONFIG_HOME", orig)
|
|
|
|
cfg := config.Config{
|
|
NetBox: config.NetBoxConfig{URL: "http://x", Token: "t", TokenVersion: 1},
|
|
Cache: config.CacheConfig{TTL: 60},
|
|
SSH: config.SSHConfig{DefaultPort: 2222},
|
|
}
|
|
if err := save(cfg); err != nil {
|
|
t.Fatalf("save: %v", err)
|
|
}
|
|
|
|
data, _ := os.ReadFile(filepath.Join(dir, "netssh.yaml"))
|
|
if !strings.Contains(string(data), "default_port: 2222") {
|
|
t.Errorf("expected default_port: 2222 in config, got:\n%s", string(data))
|
|
}
|
|
}
|
|
|
|
func TestSave_Shortcuts_WritesSection(t *testing.T) {
|
|
dir := t.TempDir()
|
|
orig := os.Getenv("XDG_CONFIG_HOME")
|
|
os.Setenv("XDG_CONFIG_HOME", dir)
|
|
defer os.Setenv("XDG_CONFIG_HOME", orig)
|
|
|
|
cfg := config.Config{
|
|
NetBox: config.NetBoxConfig{URL: "http://x", Token: "t", TokenVersion: 1},
|
|
Cache: config.CacheConfig{TTL: 60},
|
|
Shortcuts: config.ShortcutsConfig{
|
|
Domains: []string{".example.com"},
|
|
StripHyphens: true,
|
|
},
|
|
}
|
|
if err := save(cfg); err != nil {
|
|
t.Fatalf("save: %v", err)
|
|
}
|
|
|
|
data, _ := os.ReadFile(filepath.Join(dir, "netssh.yaml"))
|
|
content := string(data)
|
|
for _, want := range []string{"shortcuts:", "domains:", ".example.com", "strip_hyphens: true"} {
|
|
if !strings.Contains(content, want) {
|
|
t.Errorf("expected %q in config, got:\n%s", want, content)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSave_OmitsDefaultPortWhenZero(t *testing.T) {
|
|
dir := t.TempDir()
|
|
orig := os.Getenv("XDG_CONFIG_HOME")
|
|
os.Setenv("XDG_CONFIG_HOME", dir)
|
|
defer os.Setenv("XDG_CONFIG_HOME", orig)
|
|
|
|
cfg := config.Config{
|
|
NetBox: config.NetBoxConfig{URL: "http://x", Token: "t", TokenVersion: 1},
|
|
Cache: config.CacheConfig{TTL: 60},
|
|
SSH: config.SSHConfig{DefaultPort: 0},
|
|
}
|
|
if err := save(cfg); err != nil {
|
|
t.Fatalf("save: %v", err)
|
|
}
|
|
|
|
data, _ := os.ReadFile(filepath.Join(dir, "netssh.yaml"))
|
|
if strings.Contains(string(data), "default_port") {
|
|
t.Errorf("default_port should be omitted when zero, got:\n%s", string(data))
|
|
}
|
|
}
|
|
|
|
func TestSave_OmitsShortcutsWhenEmpty(t *testing.T) {
|
|
dir := t.TempDir()
|
|
orig := os.Getenv("XDG_CONFIG_HOME")
|
|
os.Setenv("XDG_CONFIG_HOME", dir)
|
|
defer os.Setenv("XDG_CONFIG_HOME", orig)
|
|
|
|
cfg := config.Config{
|
|
NetBox: config.NetBoxConfig{URL: "http://x", Token: "t", TokenVersion: 1},
|
|
Cache: config.CacheConfig{TTL: 60},
|
|
Shortcuts: config.ShortcutsConfig{}, // empty
|
|
}
|
|
if err := save(cfg); err != nil {
|
|
t.Fatalf("save: %v", err)
|
|
}
|
|
|
|
data, _ := os.ReadFile(filepath.Join(dir, "netssh.yaml"))
|
|
if strings.Contains(string(data), "shortcuts:") {
|
|
t.Errorf("shortcuts section should be omitted when empty, got:\n%s", string(data))
|
|
}
|
|
}
|
|
|
|
func TestSave_Roundtrip_WithNewFields(t *testing.T) {
|
|
dir := t.TempDir()
|
|
orig := os.Getenv("XDG_CONFIG_HOME")
|
|
os.Setenv("XDG_CONFIG_HOME", dir)
|
|
defer os.Setenv("XDG_CONFIG_HOME", orig)
|
|
|
|
original := config.Config{
|
|
NetBox: config.NetBoxConfig{
|
|
URL: "https://netbox.example.com",
|
|
Token: "nbt_test",
|
|
TokenVersion: 2,
|
|
},
|
|
SSH: config.SSHConfig{
|
|
DefaultUser: "admin",
|
|
DefaultPort: 2222,
|
|
},
|
|
Resolver: config.ResolverConfig{
|
|
Strategies: []string{"primary_ip"},
|
|
},
|
|
Cache: config.CacheConfig{TTL: 3600},
|
|
Shortcuts: config.ShortcutsConfig{
|
|
Domains: []string{".example.com", ".example.de"},
|
|
StripHyphens: true,
|
|
},
|
|
}
|
|
|
|
if err := save(original); err != nil {
|
|
t.Fatalf("save: %v", err)
|
|
}
|
|
|
|
loaded, err := config.Load()
|
|
if err != nil {
|
|
t.Fatalf("Load after save: %v", err)
|
|
}
|
|
|
|
if loaded.SSH.DefaultPort != original.SSH.DefaultPort {
|
|
t.Errorf("DefaultPort: got %d, want %d", loaded.SSH.DefaultPort, original.SSH.DefaultPort)
|
|
}
|
|
if len(loaded.Shortcuts.Domains) != len(original.Shortcuts.Domains) {
|
|
t.Errorf("Shortcuts.Domains length: got %d, want %d", len(loaded.Shortcuts.Domains), len(original.Shortcuts.Domains))
|
|
} else {
|
|
for i, d := range original.Shortcuts.Domains {
|
|
if loaded.Shortcuts.Domains[i] != d {
|
|
t.Errorf("Shortcuts.Domains[%d]: got %q, want %q", i, loaded.Shortcuts.Domains[i], d)
|
|
}
|
|
}
|
|
}
|
|
if loaded.Shortcuts.StripHyphens != original.Shortcuts.StripHyphens {
|
|
t.Errorf("StripHyphens: got %v, want %v", loaded.Shortcuts.StripHyphens, original.Shortcuts.StripHyphens)
|
|
}
|
|
}
|
|
|
|
func TestParseStrategies(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
want []string
|
|
}{
|
|
{"primary_ip", []string{"primary_ip"}},
|
|
{"management_subnet, primary_ip", []string{"management_subnet", "primary_ip"}},
|
|
{"primary_ip,management_subnet,interface_name", []string{"primary_ip", "management_subnet", "interface_name"}},
|
|
{" primary_ip , management_subnet ", []string{"primary_ip", "management_subnet"}},
|
|
{"", nil},
|
|
{" , ", nil},
|
|
}
|
|
for _, tt := range tests {
|
|
got := parseStrategies(tt.in)
|
|
if len(got) != len(tt.want) {
|
|
t.Errorf("parseStrategies(%q): got %v, want %v", tt.in, got, tt.want)
|
|
continue
|
|
}
|
|
for i := range got {
|
|
if got[i] != tt.want[i] {
|
|
t.Errorf("parseStrategies(%q)[%d]: got %q, want %q", tt.in, i, got[i], tt.want[i])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseStrategies_PreservesOrder(t *testing.T) {
|
|
got := parseStrategies("interface_name, management_subnet, primary_ip")
|
|
want := []string{"interface_name", "management_subnet", "primary_ip"}
|
|
for i, s := range got {
|
|
if s != want[i] {
|
|
t.Errorf("order not preserved at [%d]: got %q, want %q", i, s, want[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidateStrategies_Valid(t *testing.T) {
|
|
cases := []string{
|
|
"primary_ip",
|
|
"management_subnet, primary_ip",
|
|
"interface_name, management_subnet, primary_ip",
|
|
}
|
|
for _, c := range cases {
|
|
if err := validateStrategies(c); err != nil {
|
|
t.Errorf("validateStrategies(%q) should be valid, got: %v", c, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidateStrategies_Invalid(t *testing.T) {
|
|
cases := []string{
|
|
"",
|
|
"unknown_strategy",
|
|
"primary_ip, typo",
|
|
}
|
|
for _, c := range cases {
|
|
if err := validateStrategies(c); err == nil {
|
|
t.Errorf("validateStrategies(%q) should return an error", c)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSave_RoundtripViaLoad(t *testing.T) {
|
|
dir := t.TempDir()
|
|
orig := os.Getenv("XDG_CONFIG_HOME")
|
|
os.Setenv("XDG_CONFIG_HOME", dir)
|
|
defer os.Setenv("XDG_CONFIG_HOME", orig)
|
|
|
|
original := config.Config{
|
|
NetBox: config.NetBoxConfig{
|
|
URL: "https://netbox.zb-server.de",
|
|
Token: "nbt_supersecret",
|
|
TokenVersion: 2,
|
|
},
|
|
SSH: config.SSHConfig{DefaultUser: "root"},
|
|
Resolver: config.ResolverConfig{
|
|
Strategies: []string{"primary_ip"},
|
|
ManagementSubnets: []string{"192.168.0.0/16"},
|
|
InterfaceName: "eth0",
|
|
},
|
|
Cache: config.CacheConfig{TTL: 7200},
|
|
}
|
|
|
|
if err := save(original); err != nil {
|
|
t.Fatalf("save: %v", err)
|
|
}
|
|
|
|
loaded, err := config.Load()
|
|
if err != nil {
|
|
t.Fatalf("Load after save: %v", err)
|
|
}
|
|
|
|
if loaded.NetBox.URL != original.NetBox.URL {
|
|
t.Errorf("URL: got %q, want %q", loaded.NetBox.URL, original.NetBox.URL)
|
|
}
|
|
if loaded.NetBox.Token != original.NetBox.Token {
|
|
t.Errorf("Token: got %q, want %q", loaded.NetBox.Token, original.NetBox.Token)
|
|
}
|
|
if loaded.NetBox.TokenVersion != original.NetBox.TokenVersion {
|
|
t.Errorf("TokenVersion: got %d, want %d", loaded.NetBox.TokenVersion, original.NetBox.TokenVersion)
|
|
}
|
|
if loaded.SSH.DefaultUser != original.SSH.DefaultUser {
|
|
t.Errorf("DefaultUser: got %q, want %q", loaded.SSH.DefaultUser, original.SSH.DefaultUser)
|
|
}
|
|
if loaded.Cache.TTL != original.Cache.TTL {
|
|
t.Errorf("TTL: got %d, want %d", loaded.Cache.TTL, original.Cache.TTL)
|
|
}
|
|
}
|
|
|
|
func TestSaveCommandAliasRoundtripAndDisable(t *testing.T) {
|
|
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
|
|
cfg := config.Config{Shell: config.ShellConfig{Alias: "j"}, Cache: config.CacheConfig{Path: filepath.Join(t.TempDir(), "hosts.json")}}
|
|
if err := save(cfg); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
loaded, err := config.Load()
|
|
if err != nil || loaded.Shell.Alias != "j" || loaded.Cache.Path != cfg.Cache.Path {
|
|
t.Fatalf("roundtrip: %+v, %v", loaded, err)
|
|
}
|
|
cfg.Shell.Alias = ""
|
|
if err := save(cfg); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
loaded, err = config.Load()
|
|
if err != nil || loaded.Shell.Alias != "" {
|
|
t.Fatalf("disable: %+v, %v", loaded, err)
|
|
}
|
|
}
|
|
|
|
func TestSaveMultipleNetBoxesRoundTrip(t *testing.T) {
|
|
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
|
|
cfg := config.Config{
|
|
NetBoxes: []config.NetBoxConfig{
|
|
{Name: "office", URL: "https://office.example/netbox", Token: "nbt_secret: office", TokenVersion: 2},
|
|
{Name: "lab", URL: "https://lab.example", Token: "legacy#token", TokenVersion: 1},
|
|
},
|
|
Resolver: config.ResolverConfig{Strategies: []string{"primary_ip"}},
|
|
Cache: config.CacheConfig{TTL: 3600},
|
|
}
|
|
if err := save(cfg); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
loaded, err := config.Load()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(loaded.NetBoxes) != 2 || loaded.NetBox.URL != "" {
|
|
t.Fatalf("instances lost during save: %+v", loaded)
|
|
}
|
|
for i, want := range cfg.NetBoxes {
|
|
if !reflect.DeepEqual(loaded.NetBoxes[i], want) {
|
|
t.Fatalf("instance changed: %+v, want %+v", loaded.NetBoxes[i], want)
|
|
}
|
|
}
|
|
info, err := os.Stat(config.Path())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if info.Mode().Perm() != 0o600 {
|
|
t.Fatal("credentials are not private")
|
|
}
|
|
}
|
|
|
|
func TestSaveKeepsSSHOptions(t *testing.T) {
|
|
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
|
|
t.Setenv("HOME", t.TempDir())
|
|
opts := config.SSHOptions{
|
|
ProxyJump: "jh.example.com",
|
|
HostKeyChecking: "accept-new",
|
|
Target: "name",
|
|
AddressFamily: "inet",
|
|
ExtraArgs: []string{"-A"},
|
|
Options: map[string]string{"serveraliveinterval": "60"},
|
|
}
|
|
cfg := config.Config{
|
|
NetBoxes: []config.NetBoxConfig{{Name: "prod", URL: "https://prod.example", Token: "t", TokenVersion: 1, SSH: config.SSHOptions{HostKeyChecking: "off"}}},
|
|
Resolver: config.ResolverConfig{Strategies: []string{"primary_ip"}},
|
|
Cache: config.CacheConfig{TTL: 3600},
|
|
SSH: config.SSHConfig{
|
|
DefaultUser: "root",
|
|
SSHOptions: opts,
|
|
Rules: []config.SSHRule{{Match: config.SSHMatch{Tag: "dmz", Name: "*.dmz"}, SSHOptions: config.SSHOptions{ProxyJump: "none"}}},
|
|
},
|
|
Completion: config.CompletionConfig{MaxResults: 100},
|
|
}
|
|
if err := save(cfg); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
loaded, err := config.Load()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(loaded.SSH, cfg.SSH) || !reflect.DeepEqual(loaded.NetBoxes[0].SSH, cfg.NetBoxes[0].SSH) || loaded.Completion != cfg.Completion {
|
|
t.Fatalf("ssh settings lost:\n%+v\n%+v", loaded.SSH, loaded.NetBoxes[0].SSH)
|
|
}
|
|
}
|
|
|
|
// Re-running the configurator must keep settings that have no form field of
|
|
// their own or were not touched in the menu.
|
|
func TestPrepareKeepsUnmanagedSettings(t *testing.T) {
|
|
cfg := config.Config{
|
|
NetBox: config.NetBoxConfig{URL: "https://legacy.example", Token: "t"},
|
|
SSH: config.SSHConfig{
|
|
DefaultUser: "root",
|
|
SSHOptions: config.SSHOptions{ProxyJump: "jh.example.com", Options: map[string]string{"forwardagent": "yes"}},
|
|
Rules: []config.SSHRule{{Match: config.SSHMatch{Tag: "dmz"}, SSHOptions: config.SSHOptions{ProxyJump: "none"}}},
|
|
},
|
|
Completion: config.CompletionConfig{MaxResults: 50},
|
|
Cache: config.CacheConfig{Path: "/var/cache/netssh/hosts.json"},
|
|
}
|
|
sources := []config.NetBoxConfig{{Name: "default", URL: "https://legacy.example", Token: "t"}}
|
|
got := prepare(cfg, sources)
|
|
|
|
if !reflect.DeepEqual(got.SSH, cfg.SSH) || got.Completion != cfg.Completion || got.Cache.Path != cfg.Cache.Path {
|
|
t.Fatalf("settings lost: %+v", got)
|
|
}
|
|
if got.NetBox.URL != "" || !reflect.DeepEqual(got.NetBoxes, sources) {
|
|
t.Fatalf("legacy instance not migrated: %+v / %+v", got.NetBox, got.NetBoxes)
|
|
}
|
|
if !reflect.DeepEqual(got.Resolver.Strategies, []string{"primary_ip"}) || got.Cache.TTL != 3600 {
|
|
t.Fatalf("defaults missing: %+v %+v", got.Resolver, got.Cache)
|
|
}
|
|
}
|
|
|
|
func TestSaveRejectsInvalidSSHOptions(t *testing.T) {
|
|
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
|
|
t.Setenv("HOME", t.TempDir())
|
|
cfg := config.Config{
|
|
NetBoxes: []config.NetBoxConfig{{Name: "prod", URL: "https://prod.example", Token: "t"}},
|
|
Resolver: config.ResolverConfig{Strategies: []string{"primary_ip"}},
|
|
SSH: config.SSHConfig{Rules: []config.SSHRule{{SSHOptions: config.SSHOptions{ProxyJump: "none"}}}},
|
|
}
|
|
if err := save(cfg); err == nil {
|
|
t.Fatal("rule without match was saved")
|
|
}
|
|
if _, err := os.Stat(config.Path()); !os.IsNotExist(err) {
|
|
t.Fatalf("config file written despite error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestParseRewrites(t *testing.T) {
|
|
text := `^([a-z]+)[0-9]+- => $1
|
|
|
|
\.lab$ =>
|
|
`
|
|
got, err := parseRewrites(text)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := []config.ShortcutRewrite{{Pattern: `^([a-z]+)[0-9]+-`, Replace: "$1"}, {Pattern: `\.lab$`, Replace: ""}}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("got %+v", got)
|
|
}
|
|
if again, _ := parseRewrites(formatRewrites(got)); !reflect.DeepEqual(again, want) {
|
|
t.Fatalf("roundtrip: %+v", again)
|
|
}
|
|
for _, bad := range []string{"no arrow", "[a- => x", " => x"} {
|
|
if _, err := parseRewrites(bad); err == nil {
|
|
t.Errorf("%q accepted", bad)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSaveShortcutRewritesRoundtrip(t *testing.T) {
|
|
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
|
|
t.Setenv("HOME", t.TempDir())
|
|
rewrites := []config.ShortcutRewrite{{Pattern: `^([a-z]+)[0-9]+-`, Replace: "$1"}, {Pattern: `\.lab\.example\.com$`, Replace: `"q"`}}
|
|
cfg := config.Config{
|
|
NetBoxes: []config.NetBoxConfig{{Name: "prod", URL: "https://prod.example", Token: "t"}},
|
|
Resolver: config.ResolverConfig{Strategies: []string{"primary_ip"}},
|
|
Shortcuts: config.ShortcutsConfig{Rewrites: rewrites},
|
|
}
|
|
if err := save(cfg); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
loaded, err := config.Load()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(loaded.Shortcuts.Rewrites, rewrites) {
|
|
t.Fatalf("got %+v", loaded.Shortcuts.Rewrites)
|
|
}
|
|
}
|