Files
edgeguard-native/internal/handlers/firewall_counters_test.go
Debian f4d178cb38 feat(firewall): nftables hit-counter pro Regel + Live-Hits-Spalte in der UI
- ruleset.nft.tpl: operator-rules bekommen `counter <action> comment "egid:<id>"`
  → nft zählt Packets + Bytes per Regel ab dem letzten Ruleset-Apply
- handlers/firewall_counters.go: GET /firewall/counters parst
  `sudo nft list table inet edgeguard` per Regex, liefert [{rule_id,packets,bytes}]
- handlers/firewall_counters_test.go: unit-tests für parseNFTCounters
- Firewall/Rules.tsx: neue "Hits"-Spalte (Packet-Count, Tooltip mit Bytes),
  10s-Polling via TanStack Query ['fw','counters']
- i18n de+en: fw.rule.hits

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 08:42:31 +02:00

71 lines
2.3 KiB
Go

package handlers
import (
"testing"
)
func TestParseNFTCounters_ParsesCounterAndEgid(t *testing.T) {
// nft list output shows `counter packets N bytes M` with actual values
// (zero when unused). Template emits `counter <action> comment "egid:X"`;
// nft expands that to `counter packets N bytes M <action> comment "egid:X"`.
input := `
table inet edgeguard {
chain input {
type filter hook input priority 0; policy drop;
tcp dport 22 ct state new limit rate 10/minute accept comment "anti-lockout: SSH"
tcp dport 80 counter packets 0 bytes 0 accept comment "egid:1"
ip saddr 10.0.0.0/8 tcp dport 443 counter packets 1234 bytes 567890 accept comment "egid:2"
ip saddr 1.2.3.4 drop comment "egid:3"
tcp dport 8080 log prefix "edgeguard:4 " group 0 counter packets 7 bytes 420 reject comment "egid:4"
}
}
`
counters := parseNFTCounters(input)
if len(counters) != 3 {
t.Fatalf("expected 3 counters (egid:1,2,4), got %d: %+v", len(counters), counters)
}
byID := map[int64]ruleCounter{}
for _, c := range counters {
byID[c.RuleID] = c
}
// egid:1 — zero counters are valid
if c, ok := byID[1]; !ok {
t.Error("missing counter for egid:1")
} else if c.Packets != 0 || c.Bytes != 0 {
t.Errorf("egid:1 want packets=0 bytes=0, got packets=%d bytes=%d", c.Packets, c.Bytes)
}
// egid:2 — non-zero counters
if c, ok := byID[2]; !ok {
t.Error("missing counter for egid:2")
} else if c.Packets != 1234 || c.Bytes != 567890 {
t.Errorf("egid:2 want packets=1234 bytes=567890, got packets=%d bytes=%d", c.Packets, c.Bytes)
}
// egid:3 — line has no counter keyword → must not appear
if _, ok := byID[3]; ok {
t.Error("egid:3 has no counter statement and must not appear in output")
}
// egid:4 — counter + log + reject
if _, ok := byID[4]; !ok {
t.Error("missing counter for egid:4")
}
}
func TestParseNFTCounters_EmptyOutput(t *testing.T) {
if counters := parseNFTCounters(""); len(counters) != 0 {
t.Errorf("expected empty result, got %+v", counters)
}
}
func TestParseNFTCounters_NoRules(t *testing.T) {
input := "table inet edgeguard {\n chain input {\n }\n}\n"
if counters := parseNFTCounters(input); len(counters) != 0 {
t.Errorf("expected empty result for table with no operator rules, got %+v", counters)
}
}