feat(cli): add the prx command-line interface
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user