Files
artex/notify/filter_test.go
T
dela 0335d572de
ci / go (push) Waiting to run
ci / go-db (agent) (push) Waiting to run
ci / go-db (config) (push) Waiting to run
ci / go-db (db) (push) Waiting to run
ci / go-db (evidence) (push) Waiting to run
ci / go-db (llmrec) (push) Waiting to run
ci / go-db (server) (push) Waiting to run
web / web (push) Waiting to run
docs / links (push) Canceled after 0s
detections / detections (push) Canceled after 0s
First Commit
2026-10-09 08:38:16 +08:00

144 lines
5.0 KiB
Go

package notify
import "testing"
func TestParseFilterMalformedFallsBackToMatchAll(t *testing.T) {
// 畸形 JSON、空输入、类型不对的字段——全部必须退化为零值 Filter,
// 即「不过滤」。这条不变量是「宁可多推不可漏推」的落点:
// 一旦这里改成报错或半解析,用户配错一个字符就会静默丢掉所有高危通知。
cases := []struct {
name string
raw string
}{
{"空输入", ""},
{"非法 JSON", `{not json`},
{"截断的 JSON", `{"min_severity":`},
{"类型不匹配", `{"min_severity": 123, "task_ids": "abc"}`},
{"顶层是数组", `[1,2,3]`},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
f := ParseFilter([]byte(tc.raw))
if f.MinSeverity != "" || len(f.TaskIDs) != 0 || len(f.AssetIDs) != 0 {
t.Fatalf("畸形配置应退化为零值 Filter,得到 %+v", f)
}
// 零值 Filter 必须命中任意事件。
ev := Snapshot{Kind: EventFindingCreated, Severity: "low", VulnClass: "XSS"}
if !Match(f, ev) {
t.Fatal("零值 Filter 应命中所有事件")
}
})
}
}
func TestMatchSeverityThreshold(t *testing.T) {
ev := func(sev string) Snapshot {
return Snapshot{Kind: EventFindingCreated, Severity: sev}
}
cases := []struct {
min string
sev string
expect bool
}{
{"", "low", true},
{"", "critical", true},
{"high", "critical", true},
{"high", "high", true},
{"high", "medium", false},
{"high", "low", false},
{"critical", "high", false},
{"critical", "critical", true},
// 未知级别序数为 0,应被任何非空门槛挡住(存疑时不推)。
{"low", "", false},
{"low", "unknown", false},
{"", "", true},
}
for _, tc := range cases {
got := Match(Filter{MinSeverity: tc.min}, ev(tc.sev))
if got != tc.expect {
t.Errorf("min=%q sev=%q: 期望 %v 得到 %v", tc.min, tc.sev, tc.expect, got)
}
}
}
func TestMatchScopeRestrictions(t *testing.T) {
ev := Snapshot{
Kind: EventFindingCreated,
Severity: "high",
TaskID: 7,
AssetIDs: []int64{10, 20},
VulnClass: "SQL注入",
}
cases := []struct {
name string
filter Filter
expect bool
}{
{"空范围=不限", Filter{}, true},
{"任务命中", Filter{TaskIDs: []int64{7}}, true},
{"任务未命中", Filter{TaskIDs: []int64{8}}, false},
{"任务多选含命中", Filter{TaskIDs: []int64{8, 7}}, true},
{"资产有交集", Filter{AssetIDs: []int64{20, 99}}, true},
{"资产无交集", Filter{AssetIDs: []int64{99}}, false},
{"任务与资产同时命中", Filter{TaskIDs: []int64{7}, AssetIDs: []int64{10}}, true},
{"任务命中但资产未命中", Filter{TaskIDs: []int64{7}, AssetIDs: []int64{99}}, false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := Match(tc.filter, ev); got != tc.expect {
t.Errorf("期望 %v 得到 %v", tc.expect, got)
}
})
}
}
func TestMatchVulnClassKeywords(t *testing.T) {
ev := func(class string) Snapshot {
return Snapshot{Kind: EventFindingCreated, Severity: "high", VulnClass: class}
}
cases := []struct {
name string
filter Filter
class string
expect bool
}{
{"include 为空=全收", Filter{}, "任意类型", true},
{"include 命中", Filter{VulnClassInclude: []string{"SQL"}}, "SQL注入", true},
{"include 未命中", Filter{VulnClassInclude: []string{"命令执行"}}, "SQL注入", false},
{"include 多词任一命中", Filter{VulnClassInclude: []string{"命令执行", "SQL"}}, "SQL注入", true},
{"大小写不敏感", Filter{VulnClassInclude: []string{"sql"}}, "SQL注入", true},
{"exclude 命中即排除", Filter{VulnClassExclude: []string{"信息泄露"}}, "信息泄露", false},
{"exclude 未命中则放行", Filter{VulnClassExclude: []string{"信息泄露"}}, "SQL注入", true},
// 排除优先于包含:同时命中时应当出局。
{"排除优先于包含", Filter{
VulnClassInclude: []string{"SQL"},
VulnClassExclude: []string{"注入"},
}, "SQL注入", false},
// 纯空白关键词应被忽略,否则会退化成「匹配所有含空格的字符串」。
{"空白关键词被忽略", Filter{VulnClassInclude: []string{"", " "}}, "SQL注入", false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := Match(tc.filter, ev(tc.class)); got != tc.expect {
t.Errorf("期望 %v 得到 %v", tc.expect, got)
}
})
}
}
func TestMatchStatusChangeRequiresOptIn(t *testing.T) {
ev := Snapshot{Kind: EventFindingStatusChanged, Severity: "critical", FromStatus: "pending", ToStatus: "fixed"}
// 默认关:绝大多数人说的「推送漏洞」指发现新漏洞,不是状态流水账。
if Match(Filter{MinSeverity: "low"}, ev) {
t.Fatal("状态变更事件在未开启时应被跳过")
}
if !Match(Filter{OnStatusChange: true}, ev) {
t.Fatal("开启 on_status_change 后状态变更事件应命中")
}
// 创建事件不受 on_status_change 影响。
created := Snapshot{Kind: EventFindingCreated, Severity: "critical"}
if !Match(Filter{MinSeverity: "low"}, created) {
t.Fatal("创建事件不应依赖 on_status_change")
}
}