157 lines
4.3 KiB
Go
157 lines
4.3 KiB
Go
// SPDX-License-Identifier: MIT
|
|
|
|
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"prx/internal/config"
|
|
"prx/internal/execx"
|
|
"prx/internal/proto"
|
|
)
|
|
|
|
func TestCheckMark(t *testing.T) {
|
|
cases := []struct {
|
|
check proto.Check
|
|
want string
|
|
}{
|
|
{proto.Check{OK: true}, "ok"},
|
|
{proto.Check{OK: false, Warn: true}, "warn"},
|
|
{proto.Check{OK: false}, "FAIL"},
|
|
{proto.Check{OK: false, Fatal: true}, "FAIL"},
|
|
}
|
|
for _, tc := range cases {
|
|
if got := checkMark(tc.check); got != tc.want {
|
|
t.Errorf("checkMark(%+v) = %q, want %q", tc.check, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildPlanMapsConfig(t *testing.T) {
|
|
cfg := config.Default()
|
|
cfg.Network.WANInterface = "wlan0"
|
|
cfg.Network.WANGateway = "192.168.1.1"
|
|
cfg.Network.WANGateway6 = "2001:db8::1"
|
|
cfg.Health.Probe = "9.9.9.9:443"
|
|
cfg.Bypass.ExtraCIDRs = []string{"203.0.113.0/24"}
|
|
|
|
a := &app{}
|
|
plan := a.buildPlan(cfg, proto.ActionApply)
|
|
|
|
if plan.Action != proto.ActionApply {
|
|
t.Errorf("action = %q", plan.Action)
|
|
}
|
|
if plan.WANInterface != "wlan0" || plan.WANGateway4 != "192.168.1.1" || plan.WANGateway6 != "2001:db8::1" {
|
|
t.Errorf("network fields not mapped: %+v", plan)
|
|
}
|
|
if plan.HealthProbe != "9.9.9.9:443" {
|
|
t.Errorf("health probe not mapped: %q", plan.HealthProbe)
|
|
}
|
|
if len(plan.BypassCIDRs) != 1 || plan.BypassCIDRs[0] != "203.0.113.0/24" {
|
|
t.Errorf("bypass CIDRs not mapped: %v", plan.BypassCIDRs)
|
|
}
|
|
if plan.StatePath != config.DefaultStatePath {
|
|
t.Errorf("state path not mapped: %q", plan.StatePath)
|
|
}
|
|
}
|
|
|
|
func TestPrintResultFormatsChecks(t *testing.T) {
|
|
buf := &bytes.Buffer{}
|
|
a := &app{out: buf}
|
|
a.printResult(proto.Result{
|
|
Messages: []string{"done"},
|
|
Checks: []proto.Check{
|
|
{Name: "nft", OK: true, Detail: "present"},
|
|
{Name: "tun", OK: false, Warn: true},
|
|
},
|
|
})
|
|
out := buf.String()
|
|
for _, needle := range []string{" done", "[ok] nft: present", "[warn] tun"} {
|
|
if !strings.Contains(out, needle) {
|
|
t.Errorf("output missing %q:\n%s", needle, out)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFormatResultError(t *testing.T) {
|
|
if err := formatResultError(proto.Result{Error: "boom"}); err == nil || err.Error() != "boom" {
|
|
t.Fatalf("formatResultError = %v", err)
|
|
}
|
|
if err := formatResultError(proto.Result{}); err == nil {
|
|
t.Fatal("expected a generic error when Result.Error is empty")
|
|
}
|
|
}
|
|
|
|
func TestNftVersionWarning(t *testing.T) {
|
|
cases := []struct {
|
|
in string
|
|
warn bool
|
|
}{
|
|
{"nftables v0.9.3 (Lester Gomes)\n", true},
|
|
{"nftables v0.9.4 (Fearless Fred)\n", false},
|
|
{"nftables v0.9.0 (Topsy)\n", true},
|
|
{"nftables v1.0.9 (Sunday, 1 September 2024)\n", false},
|
|
{"nftables v2.0.1\n", false},
|
|
{"unparseable output", false},
|
|
}
|
|
for _, tc := range cases {
|
|
got := nftVersionWarning(tc.in)
|
|
if tc.warn && got == "" {
|
|
t.Errorf("nftVersionWarning(%q) = \"\", want a warning", tc.in)
|
|
}
|
|
if !tc.warn && got != "" {
|
|
t.Errorf("nftVersionWarning(%q) = %q, want no warning", tc.in, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIsPermissionError(t *testing.T) {
|
|
if !isPermissionError(errors.New("nft list: exit status 1: Error: Operation not permitted")) {
|
|
t.Error("expected a permission error to be recognised")
|
|
}
|
|
if isPermissionError(errors.New("exec: nft: executable file not found in $PATH")) {
|
|
t.Error("a missing binary must not be reported as a permission error")
|
|
}
|
|
}
|
|
|
|
// privilegeCheck must always return a named check and, for a non-root caller,
|
|
// never panic even when nothing is installed.
|
|
func TestPrivilegeCheckAlwaysNamed(t *testing.T) {
|
|
a := &app{}
|
|
c := a.privilegeCheck(context.Background(), &execx.Fake{})
|
|
if c.Name != "privilege" {
|
|
t.Fatalf("check name = %q, want %q", c.Name, "privilege")
|
|
}
|
|
if os.Geteuid() == 0 && !c.OK {
|
|
t.Fatalf("root should pass the privilege check: %+v", c)
|
|
}
|
|
}
|
|
|
|
func TestPackageHint(t *testing.T) {
|
|
for bin, want := range map[string]string{
|
|
"nft": "install nftables",
|
|
"ip": "install iproute2",
|
|
"systemctl": "install systemd",
|
|
"journalctl": "install systemd",
|
|
} {
|
|
if got := packageHint(bin); got != want {
|
|
t.Errorf("packageHint(%q) = %q, want %q", bin, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// errorText(nil) must be empty so a valid state is not reported as unreadable.
|
|
func TestErrorText(t *testing.T) {
|
|
if got := errorText(nil); got != "" {
|
|
t.Fatalf("errorText(nil) = %q, want \"\"", got)
|
|
}
|
|
if got := errorText(errors.New("boom")); got != "boom" {
|
|
t.Fatalf("errorText(err) = %q, want %q", got, "boom")
|
|
}
|
|
}
|