package app import ( "errors" "testing" "bee/audit/internal/platform" ) type fakeNetwork struct { listInterfacesFn func() ([]platform.InterfaceInfo, error) defaultRouteFn func() string dhcpOneFn func(string) (string, error) dhcpAllFn func() (string, error) setStaticIPv4Fn func(platform.StaticIPv4Config) (string, error) } func (f fakeNetwork) ListInterfaces() ([]platform.InterfaceInfo, error) { return f.listInterfacesFn() } func (f fakeNetwork) DefaultRoute() string { return f.defaultRouteFn() } func (f fakeNetwork) DHCPOne(iface string) (string, error) { return f.dhcpOneFn(iface) } func (f fakeNetwork) DHCPAll() (string, error) { return f.dhcpAllFn() } func (f fakeNetwork) SetStaticIPv4(cfg platform.StaticIPv4Config) (string, error) { return f.setStaticIPv4Fn(cfg) } type fakeServices struct { serviceStatusFn func(string) (string, error) serviceDoFn func(string, platform.ServiceAction) (string, error) } func (f fakeServices) ListBeeServices() ([]string, error) { return nil, nil } func (f fakeServices) ServiceStatus(name string) (string, error) { return f.serviceStatusFn(name) } func (f fakeServices) ServiceDo(name string, action platform.ServiceAction) (string, error) { return f.serviceDoFn(name, action) } type fakeExports struct{} func (f fakeExports) ListRemovableTargets() ([]platform.RemovableTarget, error) { return nil, nil } func (f fakeExports) ExportFileToTarget(src string, target platform.RemovableTarget) (string, error) { return "", nil } type fakeTools struct { tailFileFn func(string, int) string checkToolsFn func([]string) []platform.ToolStatus } func (f fakeTools) TailFile(path string, lines int) string { return f.tailFileFn(path, lines) } func (f fakeTools) CheckTools(names []string) []platform.ToolStatus { return f.checkToolsFn(names) } type fakeSAT struct { runFn func(string) (string, error) } func (f fakeSAT) RunNvidiaAcceptancePack(baseDir string) (string, error) { return f.runFn(baseDir) } func TestNetworkStatusFormatsInterfacesAndRoute(t *testing.T) { t.Parallel() a := &App{ network: fakeNetwork{ listInterfacesFn: func() ([]platform.InterfaceInfo, error) { return []platform.InterfaceInfo{ {Name: "eth0", State: "UP", IPv4: []string{"10.0.0.2/24"}}, {Name: "eth1", State: "DOWN", IPv4: nil}, }, nil }, defaultRouteFn: func() string { return "10.0.0.1" }, }, } result, err := a.NetworkStatus() if err != nil { t.Fatalf("NetworkStatus error: %v", err) } if result.Title != "Network status" { t.Fatalf("title=%q want %q", result.Title, "Network status") } if want := "- eth0: state=UP ip=10.0.0.2/24"; !contains(result.Body, want) { t.Fatalf("body missing %q\nbody=%s", want, result.Body) } if want := "- eth1: state=DOWN ip=(no IPv4)"; !contains(result.Body, want) { t.Fatalf("body missing %q\nbody=%s", want, result.Body) } if want := "Default route: 10.0.0.1"; !contains(result.Body, want) { t.Fatalf("body missing %q\nbody=%s", want, result.Body) } } func TestNetworkStatusPropagatesListError(t *testing.T) { t.Parallel() a := &App{ network: fakeNetwork{ listInterfacesFn: func() ([]platform.InterfaceInfo, error) { return nil, errors.New("boom") }, defaultRouteFn: func() string { return "" }, }, } result, err := a.NetworkStatus() if err == nil { t.Fatal("expected error") } if result.Title != "Network status" { t.Fatalf("title=%q want %q", result.Title, "Network status") } } func TestParseStaticIPv4ConfigAndDefaults(t *testing.T) { t.Parallel() a := &App{ network: fakeNetwork{ defaultRouteFn: func() string { return " 192.168.1.1 " }, listInterfacesFn: func() ([]platform.InterfaceInfo, error) { return nil, nil }, dhcpOneFn: func(string) (string, error) { return "", nil }, dhcpAllFn: func() (string, error) { return "", nil }, setStaticIPv4Fn: func(platform.StaticIPv4Config) (string, error) { return "", nil }, }, } defaults := a.DefaultStaticIPv4FormFields("eth0") if len(defaults) != 4 { t.Fatalf("len(defaults)=%d want 4", len(defaults)) } if defaults[1] != "24" || defaults[2] != "192.168.1.1" { t.Fatalf("unexpected defaults: %#v", defaults) } cfg := a.ParseStaticIPv4Config("eth0", []string{ " 10.10.0.5 ", " 23 ", " 10.10.0.1 ", " 1.1.1.1 8.8.8.8 ", }) if cfg.Interface != "eth0" || cfg.Address != "10.10.0.5" || cfg.Prefix != "23" || cfg.Gateway != "10.10.0.1" { t.Fatalf("unexpected cfg: %#v", cfg) } if len(cfg.DNS) != 2 || cfg.DNS[0] != "1.1.1.1" || cfg.DNS[1] != "8.8.8.8" { t.Fatalf("unexpected dns: %#v", cfg.DNS) } } func TestServiceActionResults(t *testing.T) { t.Parallel() a := &App{ services: fakeServices{ serviceStatusFn: func(name string) (string, error) { return "active", nil }, serviceDoFn: func(name string, action platform.ServiceAction) (string, error) { return string(action) + " ok", nil }, }, } statusResult, err := a.ServiceStatusResult("bee-audit") if err != nil { t.Fatalf("ServiceStatusResult error: %v", err) } if statusResult.Title != "service: bee-audit" || statusResult.Body != "active" { t.Fatalf("unexpected status result: %#v", statusResult) } actionResult, err := a.ServiceActionResult("bee-audit", platform.ServiceRestart) if err != nil { t.Fatalf("ServiceActionResult error: %v", err) } if actionResult.Title != "service: bee-audit" || actionResult.Body != "restart ok" { t.Fatalf("unexpected action result: %#v", actionResult) } } func TestToolCheckAndLogTailResults(t *testing.T) { t.Parallel() a := &App{ tools: fakeTools{ tailFileFn: func(path string, lines int) string { return path }, checkToolsFn: func(names []string) []platform.ToolStatus { return []platform.ToolStatus{ {Name: "dmidecode", OK: true, Path: "/usr/bin/dmidecode"}, {Name: "smartctl", OK: false}, } }, }, } toolsResult := a.ToolCheckResult([]string{"dmidecode", "smartctl"}) if toolsResult.Title != "Required tools" { t.Fatalf("title=%q want %q", toolsResult.Title, "Required tools") } if want := "- dmidecode: OK (/usr/bin/dmidecode)"; !contains(toolsResult.Body, want) { t.Fatalf("body missing %q\nbody=%s", want, toolsResult.Body) } if want := "- smartctl: MISSING"; !contains(toolsResult.Body, want) { t.Fatalf("body missing %q\nbody=%s", want, toolsResult.Body) } logResult := a.AuditLogTailResult() if logResult.Title != "Audit log tail" { t.Fatalf("title=%q want %q", logResult.Title, "Audit log tail") } if want := DefaultAuditLogPath + "\n\n" + DefaultAuditJSONPath; logResult.Body != want { t.Fatalf("body=%q want %q", logResult.Body, want) } } func TestRunNvidiaAcceptancePackResult(t *testing.T) { t.Parallel() a := &App{ sat: fakeSAT{ runFn: func(baseDir string) (string, error) { if baseDir != "/tmp/sat" { t.Fatalf("baseDir=%q want %q", baseDir, "/tmp/sat") } return "/tmp/sat/out.tar.gz", nil }, }, } result, err := a.RunNvidiaAcceptancePackResult("/tmp/sat") if err != nil { t.Fatalf("RunNvidiaAcceptancePackResult error: %v", err) } if result.Title != "NVIDIA SAT" || result.Body != "Archive written to /tmp/sat/out.tar.gz" { t.Fatalf("unexpected result: %#v", result) } } func contains(haystack, needle string) bool { return len(needle) == 0 || (len(haystack) >= len(needle) && (haystack == needle || containsAt(haystack, needle))) } func containsAt(haystack, needle string) bool { for i := 0; i+len(needle) <= len(haystack); i++ { if haystack[i:i+len(needle)] == needle { return true } } return false }