diff --git a/cmd/sluice/policy.go b/cmd/sluice/policy.go index 41a325a..06f8bd3 100644 --- a/cmd/sluice/policy.go +++ b/cmd/sluice/policy.go @@ -78,11 +78,15 @@ func handlePolicyList(args []string) error { if len(r.Protocols) > 0 { proto = " protocols=" + strings.Join(r.Protocols, ",") } + replacement := "" + if r.Replacement != "" { + replacement = fmt.Sprintf(" -> %q", r.Replacement) + } name := "" if r.Name != "" { name = " (" + r.Name + ")" } - fmt.Printf("[%d] %s %s%s%s%s [%s]\n", r.ID, r.Verdict, target, ports, proto, name, r.Source) + fmt.Printf("[%d] %s %s%s%s%s%s [%s]\n", r.ID, r.Verdict, target, ports, proto, replacement, name, r.Source) } return nil } diff --git a/cmd/sluice/policy_test.go b/cmd/sluice/policy_test.go index 7f56124..293df75 100644 --- a/cmd/sluice/policy_test.go +++ b/cmd/sluice/policy_test.go @@ -159,6 +159,32 @@ func TestHandlePolicyListShowsPorts(t *testing.T) { } } +func TestHandlePolicyListShowsReplacement(t *testing.T) { + dir := t.TempDir() + dbPath := filepath.Join(dir, "test.db") + db, err := store.New(dbPath) + if err != nil { + t.Fatalf("create test DB: %v", err) + } + if _, err := db.AddRule("redact", store.RuleOpts{ + Pattern: `sk-[A-Za-z0-9]+`, + Replacement: "sk-REDACTED", + }); err != nil { + t.Fatalf("add redact rule: %v", err) + } + _ = db.Close() + + output := capturePolicyOutput(t, func() { + if err := handlePolicyList([]string{"--db", dbPath}); err != nil { + t.Fatalf("handlePolicyList: %v", err) + } + }) + + if !strings.Contains(output, `-> "sk-REDACTED"`) { + t.Errorf("expected replacement in output: %s", output) + } +} + // --- handlePolicyAdd tests --- func TestHandlePolicyAddAllow(t *testing.T) { diff --git a/internal/telegram/commands.go b/internal/telegram/commands.go index d24b8a4..0522de7 100644 --- a/internal/telegram/commands.go +++ b/internal/telegram/commands.go @@ -240,6 +240,10 @@ func (h *CommandHandler) policyShow() string { b.WriteString(" ports=") b.WriteString(formatPorts(r.Ports)) } + if len(r.Protocols) > 0 { + b.WriteString(" protocols=") + b.WriteString(strings.Join(r.Protocols, ",")) + } b.WriteString("\n") } } @@ -303,6 +307,19 @@ func (h *CommandHandler) policyShowFromStore() string { b.WriteString(" ports=") b.WriteString(formatPorts(r.Ports)) } + if len(r.Protocols) > 0 { + b.WriteString(" protocols=") + b.WriteString(strings.Join(r.Protocols, ",")) + } + if r.Replacement != "" { + fmt.Fprintf(&b, " -> %q", r.Replacement) + } + if r.Name != "" { + fmt.Fprintf(&b, " (%s)", r.Name) + } + if r.Source != "" { + fmt.Fprintf(&b, " [%s]", r.Source) + } b.WriteString("\n") } } diff --git a/internal/telegram/commands_test.go b/internal/telegram/commands_test.go index f2c4e02..b5e623b 100644 --- a/internal/telegram/commands_test.go +++ b/internal/telegram/commands_test.go @@ -445,6 +445,46 @@ func TestPolicyPersistence(t *testing.T) { } } +func TestPolicyShowIncludesAllFields(t *testing.T) { + s := newTestStore(t) + + if _, err := s.AddRule("allow", store.RuleOpts{ + Destination: "example.com", + Ports: []int{443}, + Protocols: []string{"quic"}, + Name: "test rule", + Source: "manual", + }); err != nil { + t.Fatal(err) + } + if _, err := s.AddRule("redact", store.RuleOpts{ + Pattern: `sk-[A-Za-z0-9]+`, + Replacement: "sk-REDACTED", + Source: "seed", + }); err != nil { + t.Fatal(err) + } + + handler := newTestHandlerWithStore(t, s, nil, "") + out := handler.Handle(&Command{Name: "policy", Args: []string{"show"}}) + + mustContain := []string{ + "example.com", + "ports=443", + "protocols=quic", + "(test rule)", + "[manual]", + "pattern:sk-[A-Za-z0-9]+", + `-> "sk-REDACTED"`, + "[seed]", + } + for _, want := range mustContain { + if !strings.Contains(out, want) { + t.Errorf("policy show output missing %q\nfull output:\n%s", want, out) + } + } +} + func TestPolicyRemoveThenRecompile(t *testing.T) { s := newTestStore(t) id, err := s.AddRule("allow", store.RuleOpts{Destination: "to-remove.com"})