dynamic_rules_config_test.go
Rama main · réplica de lectura del repositorio original.
package main
import (
"errors"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"netgoat.xyz/agent/internal/config"
"netgoat.xyz/agent/internal/dynamicrules"
)
func TestConfigureDynamicRulesEvaluatesAndRestoresRequestBody(t *testing.T) {
cfg := &config.Config{}
cfg.DynamicRules.Enabled = true
cfg.DynamicRules.MaxInputBytes = 512
cfg.DynamicRules.Rules = []config.DynamicRule{{
Name: "block-secret",
Language: "typescript",
Source: `export function evaluate(request) {
return request.body === "secret" ? { action: "block", reason: "secret body" } : null;
}`,
}}
engine, err := configureDynamicRules(cfg)
if err != nil {
t.Fatalf("configureDynamicRules(): %v", err)
}
if engine == nil {
t.Fatal("configureDynamicRules() returned nil engine")
}
request := httptest.NewRequest(http.MethodPost, "http://api.example.test/submit", strings.NewReader("secret"))
decision, err := evaluateDynamicRules(request, engine, "203.0.113.7")
if err != nil || decision.Action != dynamicrules.ActionBlock || decision.Rule != "block-secret" {
t.Fatalf("evaluateDynamicRules() = %#v, %v", decision, err)
}
body, err := io.ReadAll(request.Body)
if err != nil {
t.Fatalf("read restored body: %v", err)
}
if string(body) != "secret" {
t.Fatalf("restored body = %q", body)
}
retryBody, err := request.GetBody()
if err != nil {
t.Fatalf("GetBody(): %v", err)
}
defer retryBody.Close()
if copied, _ := io.ReadAll(retryBody); string(copied) != "secret" {
t.Fatalf("GetBody() = %q", copied)
}
}
func TestDynamicRulesRejectInvalidConfigAndBoundBodies(t *testing.T) {
cfg := &config.Config{}
cfg.DynamicRules.Enabled = true
cfg.DynamicRules.Rules = []config.DynamicRule{{Name: "broken", Source: "export function"}}
if _, err := configureDynamicRules(cfg); err == nil {
t.Fatal("configureDynamicRules() accepted invalid source")
}
cfg.DynamicRules.Rules = []config.DynamicRule{{Name: "disabled", Source: "export function evaluate() { return null; }", Enabled: boolPointer(false)}}
if engine, err := configureDynamicRules(cfg); err != nil || engine != nil {
t.Fatalf("disabled rules engine = %v, %v", engine, err)
}
request := httptest.NewRequest(http.MethodPost, "http://api.example.test/", strings.NewReader("12345"))
if _, err := copyRequestBodyForDynamicRules(request, 4); !errors.Is(err, dynamicrules.ErrLimitExceeded) {
t.Fatalf("copyRequestBodyForDynamicRules() error = %v, want limit error", err)
}
}
func TestDynamicRulesRuntimeRetainsLastKnownGoodEngine(t *testing.T) {
cfg := &config.Config{}
cfg.DynamicRules.Enabled = true
cfg.DynamicRules.Rules = []config.DynamicRule{{
Name: "stable",
Source: `export function evaluate(request) { return request.path === "/blocked" ? { action: "block" } : null; }`,
}}
runtime, err := newDynamicRulesRuntime(cfg)
if err != nil {
t.Fatalf("newDynamicRulesRuntime(): %v", err)
}
cfg.DynamicRules.Rules = []config.DynamicRule{{Name: "broken", Source: "export function"}}
if err := runtime.Update(cfg); err == nil {
t.Fatal("Update() accepted invalid replacement rules")
}
request := httptest.NewRequest(http.MethodGet, "http://api.example.test/blocked", nil)
decision, err := evaluateDynamicRules(request, runtime.Load(), "203.0.113.7")
if err != nil || decision.Action != dynamicrules.ActionBlock || decision.Rule != "stable" {
t.Fatalf("last-known-good evaluation = %#v, %v", decision, err)
}
}
func boolPointer(value bool) *bool {
return &value
}