// SPDX-License-Identifier: MIT package iprule import ( "context" "strings" "testing" "prx/internal/execx" ) func TestParseRules(t *testing.T) { out := `0: from all lookup local 50: from all to 198.51.100.0/24 lookup 102 100: from all fwmark 0x100 lookup 100 32766: from all lookup main 32767: from all lookup default ` entries := parseRules(out) if len(entries) != 5 { t.Fatalf("expected 5 entries, got %d", len(entries)) } if !entries[2].HasMark || entries[2].Mark != 0x100 || entries[2].Table != 100 || entries[2].Priority != 100 { t.Errorf("mark entry parsed wrong: %+v", entries[2]) } if !entries[1].HasTo || entries[1].To != "198.51.100.0/24" || entries[1].Table != 102 { t.Errorf("bypass entry parsed wrong: %+v", entries[1]) } } func ruleRunner(out string) (*execx.Fake, *[]execx.Call) { f := &execx.Fake{Handler: func(name string, args []string, _ []byte) (execx.Result, error) { if len(args) >= 2 && args[len(args)-2] == "rule" && args[len(args)-1] == "show" { return execx.Result{Stdout: []byte(out)}, nil } return execx.Result{}, nil }} return f, &f.Calls } func TestRuleExists(t *testing.T) { f, _ := ruleRunner("100:\tfrom all fwmark 0x100 lookup 100\n") ok, err := RuleExists(context.Background(), f, Spec{Family: 4, Mark: 0x100, Table: 100, Priority: 100}) if err != nil || !ok { t.Fatalf("RuleExists = %v, %v", ok, err) } ok, err = RuleExists(context.Background(), f, Spec{Family: 4, Mark: 0x100, Table: 100, Priority: 200}) if err != nil || ok { t.Fatalf("expected absent, got %v, %v", ok, err) } } func TestEnsureRuleAddsWhenMissing(t *testing.T) { f, calls := ruleRunner("") changed, err := EnsureRule(context.Background(), f, Spec{Family: 4, Mark: 0x100, Table: 100, Priority: 100}) if err != nil || !changed { t.Fatalf("EnsureRule = %v, %v", changed, err) } if !hasCall(*calls, "ip", "rule", "add", "fwmark", "0x100", "lookup", "100", "priority", "100") { t.Fatalf("expected add call, got %+v", *calls) } } func TestEnsureRuleNoopWhenPresent(t *testing.T) { f, calls := ruleRunner("100:\tfrom all fwmark 0x100 lookup 100\n") changed, err := EnsureRule(context.Background(), f, Spec{Family: 4, Mark: 0x100, Table: 100, Priority: 100}) if err != nil || changed { t.Fatalf("expected no-op, got %v, %v", changed, err) } for _, c := range *calls { if len(c.Args) >= 2 && c.Args[1] == "add" { t.Fatalf("unexpected add call: %+v", c) } } } func TestRemoveRulesByTable(t *testing.T) { out := `50: from all to 198.51.100.0/24 lookup 102 100: from all fwmark 0x100 lookup 100 110: from all fwmark 0x100 lookup 100 ` f, calls := ruleRunner(out) n, err := RemoveRulesByTable(context.Background(), f, 4, 100) if err != nil { t.Fatalf("RemoveRulesByTable: %v", err) } if n != 2 { t.Fatalf("removed %d, want 2", n) } for _, c := range *calls { if len(c.Args) >= 2 && c.Args[1] == "del" && !strings.Contains(strings.Join(c.Args, " "), "lookup 100") { t.Fatalf("deleted wrong rule: %+v", c) } } } func hasCall(calls []execx.Call, name string, args ...string) bool { for _, c := range calls { if c.Name != name || len(c.Args) != len(args) { continue } match := true for i := range args { if c.Args[i] != args[i] { match = false break } } if match { return true } } return false }