package main import ( "reflect" "strings" "testing" "git.zb-server.de/Sebi/ssh-netbox-wrapper/internal/cache" "git.zb-server.de/Sebi/ssh-netbox-wrapper/internal/config" ) func TestExpandSelectors(t *testing.T) { cfg := &config.Config{ Hosts: []config.StaticHost{{Name: "box", Address: "10.0.0.1", Tags: []string{"Web Servers"}}}, Groups: map[string][]string{ "all": {"@web", "db1", "box"}, "web": {"web1", "tag:web-servers", "web1"}, "loop": {"@loop"}, }, } c := cache.New(t.TempDir()+"/hosts.json", 60).WithStatic(cfg.Hosts) got, _, err := expandSelectors(cfg, c, []string{"@all", "WEB1"}, "") if err != nil || !reflect.DeepEqual(got, []string{"web1", "box", "db1"}) { t.Fatalf("got %v %v", got, err) } if _, _, err := expandSelectors(cfg, c, []string{"@loop"}, ""); err == nil || !strings.Contains(err.Error(), "contains itself") { t.Fatalf("cycle: %v", err) } if _, _, err := expandSelectors(cfg, c, []string{"@nope"}, ""); err == nil || !strings.Contains(err.Error(), "available: @all, @loop, @web") { t.Fatalf("unknown group: %v", err) } if _, _, err := expandSelectors(cfg, c, []string{"tag:web-servers"}, "device"); err == nil { t.Fatal("--kind device must exclude static hosts") } } // Without a reachable NetBox, tag: and cluster: fall back to the cache. func TestExpandSelectorsCacheFallback(t *testing.T) { cfg := &config.Config{NetBox: config.NetBoxConfig{URL: "http://127.0.0.1:1", Token: "t", TokenVersion: 1}} c := cache.New(t.TempDir()+"/hosts.json", 60) c.Upsert(cache.Entry{Name: "node2", Kind: "device", Cluster: "pve"}) c.Upsert(cache.Entry{Name: "node1", Kind: "device", Cluster: "pve", Tags: []string{"pve"}}) c.Upsert(cache.Entry{Name: "vm1", Kind: "vm", Cluster: "pve"}) got, warnings, err := expandSelectors(cfg, c, []string{"cluster:PVE"}, "") if err != nil || !reflect.DeepEqual(got, []string{"node1", "node2"}) || len(warnings) != 1 { t.Fatalf("devices: %v %v %v", got, warnings, err) } if got, _, _ := expandSelectors(cfg, c, []string{"cluster:pve"}, "all"); len(got) != 3 { t.Fatalf("all: %v", got) } if _, _, err := expandSelectors(cfg, c, []string{"cluster:other"}, ""); err == nil || !strings.Contains(err.Error(), "--kind vm") { t.Fatalf("empty cluster: %v", err) } } func TestMultiLabelAndPaneArgv(t *testing.T) { for selectors, want := range map[string]string{"@k8s": "k8s", "cluster:pve-fsn1": "pve-fsn1", "tag:a.b": "a-b", "a b": "multi"} { if got := multiLabel(strings.Fields(selectors)); got != want { t.Errorf("%q: got %q", selectors, got) } } t.Setenv("XDG_CONFIG_HOME", "/cfg") t.Setenv("NETSSH_NETBOX_TOKEN", "secret") argv := strings.Join(paneArgv("prod/web1", multiFlags{user: "ops", jump: "jh"}), " ") for _, want := range []string{"NETSSH_NO_REFRESH=1", "XDG_CONFIG_HOME=/cfg", "--jump jh -l ops prod/web1"} { if !strings.Contains(argv, want) { t.Errorf("argv lacks %q: %s", want, argv) } } if strings.Contains(argv, "secret") { t.Fatalf("token leaked into the pane arguments: %s", argv) } } func TestMultiDryRun(t *testing.T) { prepareConfig(t, "hosts:\n - {name: a, address: 10.0.0.1, tags: [web]}\n - {name: b, address: 10.0.0.2, tags: [web]}\ngroups:\n web: [\"tag:web\"]\n") out, err := runNetssh(t, "multi", "--dry-run", "--tmux", "--sync", "@web") if err != nil { t.Fatalf("%s %v", out, err) } for _, want := range []string{"# 2 hosts: a b", "tmux new-session -d -x 240 -y 70 -s netssh-web", "synchronize-panes on", "tmux attach-session -t netssh-web"} { if !strings.Contains(out, want) { t.Errorf("missing %q:\n%s", want, out) } } if out, err := runNetssh(t, "multi", "--dry-run", "--zellij", "a", "b"); err != nil || !strings.Contains(out, `pane name="b"`) { t.Errorf("zellij: %s %v", out, err) } if out, err := runNetssh(t, "sync"); err == nil || !strings.Contains(out, "inside tmux or zellij") { t.Errorf("sync outside: %s %v", out, err) } }