package agent import ( "context" "errors" "net" "os" "path/filepath" "testing" ) // fakeResolver beantwortet CNAME- und PTR-Anfragen aus Tabellen. type fakeResolver struct { cname map[string]string ptr map[string][]string } func (f fakeResolver) LookupCNAME(_ context.Context, host string) (string, error) { if c, ok := f.cname[host]; ok { return c, nil } return "", errors.New("no such host") } func (f fakeResolver) LookupAddr(_ context.Context, addr string) ([]string, error) { if p, ok := f.ptr[addr]; ok { return p, nil } return nil, errors.New("no such host") } // FQDN: explizit > Hostname mit Punkt > aufgelöster Hostname (Suchdomain) > // Reverse-Lookup der Backend-Adresse > kurzer Hostname. func TestDetectFQDN(t *testing.T) { res := fakeResolver{ cname: map[string]string{"webdocker11": "webdocker11.lan.r25dc.de.", "kurz": "kurz"}, ptr: map[string][]string{"10.0.0.11": {"backend11.lan.r25dc.de."}}, } tests := []struct { name, explicit, hostname, backend, want string }{ {"explizit gewinnt", "web.example.de", "webdocker11", "10.0.0.11", "web.example.de"}, {"Hostname ist schon FQDN", "", "pi.home.arpa", "10.0.0.11", "pi.home.arpa"}, {"über Suchdomain aufgelöst", "", "webdocker11", "10.0.0.11", "webdocker11.lan.r25dc.de"}, {"Reverse-Lookup der Backend-Adresse", "", "kurz", "10.0.0.11", "backend11.lan.r25dc.de"}, {"nichts auflösbar: kurzer Hostname", "", "solo", "10.9.9.9", "solo"}, {"Groß/Klein und Leerraum", " Web.Example.DE ", "x", "", "web.example.de"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := detectFQDN(context.Background(), res, tt.explicit, tt.hostname, tt.backend); got != tt.want { t.Errorf("detectFQDN = %q, erwartet %q", got, tt.want) } }) } } // NetBird: Interface wt0, sonst eine Adresse aus 100.64.0.0/10 (CGNAT, wie // NetBird sie vergibt). func TestNetBirdIP(t *testing.T) { ip := net.ParseIP tests := []struct { name string addrs map[string][]net.IP want string }{ {"wt0", map[string][]net.IP{"eth0": {ip("10.0.0.11")}, "wt0": {ip("fe80::1"), ip("100.92.10.4")}}, "100.92.10.4"}, {"CGNAT auf anderem Interface", map[string][]net.IP{"eth0": {ip("10.0.0.11")}, "nb0": {ip("100.64.3.2")}}, "100.64.3.2"}, {"kein Overlay", map[string][]net.IP{"eth0": {ip("10.0.0.11")}, "lo": {ip("127.0.0.1")}}, ""}, {"100.128.x ist kein CGNAT", map[string][]net.IP{"eth0": {ip("100.128.0.1")}}, ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := netbirdIP(tt.addrs); got != tt.want { t.Errorf("netbirdIP = %q, erwartet %q", got, tt.want) } }) } } // Ohne Docker (systemd-Agent) kommt das Betriebssystem aus /etc/os-release. func TestOSPrettyName(t *testing.T) { dir := t.TempDir() p := filepath.Join(dir, "os-release") os.WriteFile(p, []byte("NAME=\"Debian GNU/Linux\"\nPRETTY_NAME=\"Debian GNU/Linux 13 (trixie)\"\nID=debian\n"), 0o644) if got := osPrettyName(p); got != "Debian GNU/Linux 13 (trixie)" { t.Errorf("osPrettyName = %q", got) } os.WriteFile(p, []byte("NAME=Alpine\nID=alpine\n"), 0o644) if got := osPrettyName(p); got != "Alpine" { t.Errorf("ohne PRETTY_NAME: %q, erwartet NAME", got) } if got := osPrettyName(filepath.Join(dir, "fehlt")); got != "" { t.Errorf("fehlende Datei: %q", got) } }