Files
edgeguard-native/internal/services/waf/appprofiles_test.go
Debian 5c268425c1 feat(waf): benutzerdefinierte App-Profile + Fix: CRS-Plugins wurden im Agent nie geladen — v1.3.17
Neu: eigene WAF-App-Profile (benannte, wiederverwendbare Rule-ID-Ausnahme-
Bündel) — zentrale Bibliothek im UI (eigener Tab), pro Domain zuweisbar,
Built-in-OWASP-Plugins bleiben read-only + als Vorlage klonbar. Nur reine
Rule-IDs/Ranges (keine SecLang-Ausführung, injektionssicher).
- Migration 0047: Tabelle waf_app_profiles (repliziert via reconcile) +
  waf_configs.app_profiles.
- Service/Handler: CRUD (/waf/profiles), Built-ins geschützt (builtin=false-Gate).
- Agent-Loader: app_profiles → in effektive rule_exclusions gemerged; ihr
  updated_at hebt das effektive updated_at der Domain → Engine-Rebuild bei
  Profil-Edit.
- UI: Profile-Tab (Liste/Editor mit durchsuchbaren Rule-IDs) + Multi-Select im
  Domain-Drawer.

FIX (wichtig): ListAllWithDomain — der EINZIGE Loader des laufenden WAF-Agents —
selektierte crs_plugins nie. Dadurch war cfg.CRSPlugins im Agent immer leer und
KEIN Built-in-CRS-Plugin (Nextcloud/WordPress/Drupal) wurde je in die Engine
inkludiert. Jetzt geladen (+ app_profiles). Die per-Domain-Plugin-Wahl wirkt
damit erstmals tatsächlich.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-03 15:51:39 +02:00

49 lines
1.5 KiB
Go

package waf
import (
"testing"
"time"
"git.netcell-it.de/projekte/edgeguard-native/internal/models"
)
func TestMergeProfileExclusions(t *testing.T) {
base := time.Date(2026, 8, 3, 10, 0, 0, 0, time.UTC)
newer := base.Add(1 * time.Hour)
profiles := map[string]models.WafAppProfile{
"nc": {Name: "nc", RuleExclusions: []string{"942100", "920420"}, UpdatedAt: newer},
"wp": {Name: "wp", RuleExclusions: []string{"942100", "941100"}, UpdatedAt: base},
}
t.Run("keine Profile → unverändert", func(t *testing.T) {
got, ts := mergeProfileExclusions([]string{"1000"}, base, nil, profiles)
if len(got) != 1 || got[0] != "1000" || !ts.Equal(base) {
t.Fatalf("got=%v ts=%v", got, ts)
}
})
t.Run("union dedupliziert, Reihenfolge stabil", func(t *testing.T) {
got, ts := mergeProfileExclusions([]string{"942100", "900001"}, base, []string{"nc", "wp"}, profiles)
want := []string{"942100", "900001", "920420", "941100"} // 942100 nicht doppelt
if len(got) != len(want) {
t.Fatalf("got=%v want=%v", got, want)
}
for i := range want {
if got[i] != want[i] {
t.Fatalf("got=%v want=%v", got, want)
}
}
// effektives updated_at = max(base, nc.newer) = newer
if !ts.Equal(newer) {
t.Fatalf("ts=%v want=%v (Profil-Edit muss Rebuild ausloesen)", ts, newer)
}
})
t.Run("unbekanntes Profil defensiv ignoriert", func(t *testing.T) {
got, ts := mergeProfileExclusions([]string{"1000"}, base, []string{"gibtsnicht"}, profiles)
if len(got) != 1 || got[0] != "1000" || !ts.Equal(base) {
t.Fatalf("got=%v ts=%v", got, ts)
}
})
}