Files
ARTEX/db/asset_intercept_match_test.go
T
Autumn-27 d5359e1f04 feat: 资产拦截接入执行链,新增任务级拦截/允许规则
在 agent 下发意图(add_intent)与插入资产(insert_assets)两处接入资产闸门;
并新增任务级规则(拦截 block + 允许 allow 白名单),创建任务时录入、任务详情
总览里可编辑。

- 匹配/执行(db/asset_intercept_match.go):MatchAssetInterceptRules 纯匹配
  (exact/fuzzy 的 域名·IP·URL + CIDR,URL host 会拆出归类);EvaluateAssetGate
  「先拦截后允许」闸门——命中拦截即禁止;未命中但存在启用的允许规则且都不命中→
  不允许测试;无启用允许规则则不启用白名单。附单元测试。
- 执行点:add_intent 命中即不下发意图;insert_assets 命中即跳过 Upsert 及
  AddAutoScope 副作用。均合并 全局∪任务级 规则。
- 任务级规则:表 task_intercept_rules(task_id FK CASCADE + action block/allow,
  幂等 ALTER 补旧库);db/task_intercept.go 提供 List/Split/CRUD/事务插入;
  创建任务随 intercept_rules 提交;CRUD 路由 /api/tasks/{id}/intercept-rules。
- 前端:新建任务抽屉多行录入(拦截/允许 + 类型,用 NativeSelect 避免 Sheet 内
  下拉 portal 导致误关抽屉);任务详情总览新增规则管理卡片(增删改+启用开关)。
2026-09-19 09:06:22 -04:00

130 lines
5.3 KiB
Go

package db
import "testing"
func rule(kind, pattern string, enabled bool) AssetInterceptRule {
return AssetInterceptRule{Kind: kind, Pattern: pattern, Enabled: enabled}
}
func TestMatchAssetInterceptRules(t *testing.T) {
cases := []struct {
name string
rules []AssetInterceptRule
domains []string
ips []string
urls []string
want bool
wantVal string
}{
{"内置模糊政府域名命中", []AssetInterceptRule{rule("fuzzy_domain", ".gov.cn", true)},
[]string{"www.beijing.gov.cn"}, nil, nil, true, "www.beijing.gov.cn"},
{"模糊教育域名命中", []AssetInterceptRule{rule("fuzzy_domain", ".edu", true)},
[]string{"mit.edu"}, nil, nil, true, "mit.edu"},
{"全等域名命中大小写不敏感", []AssetInterceptRule{rule("exact_domain", "Example.com", true)},
[]string{"example.com"}, nil, nil, true, "example.com"},
{"全等域名不命中子域", []AssetInterceptRule{rule("exact_domain", "example.com", true)},
[]string{"a.example.com"}, nil, nil, false, ""},
{"全等IP命中", []AssetInterceptRule{rule("exact_ip", "203.0.113.5", true)},
nil, []string{"203.0.113.5"}, nil, true, "203.0.113.5"},
{"模糊IP前缀命中", []AssetInterceptRule{rule("fuzzy_ip", "203.0.113.", true)},
nil, []string{"203.0.113.99"}, nil, true, "203.0.113.99"},
{"CIDR 命中", []AssetInterceptRule{rule("cidr", "192.168.0.0/16", true)},
nil, []string{"192.168.5.20"}, nil, true, "192.168.5.20"},
{"CIDR 不命中", []AssetInterceptRule{rule("cidr", "192.168.0.0/16", true)},
nil, []string{"10.0.0.1"}, nil, false, ""},
{"全等URL命中", []AssetInterceptRule{rule("exact_url", "https://a.gov.cn/login", true)},
nil, nil, []string{"https://a.gov.cn/login"}, true, "https://a.gov.cn/login"},
{"模糊URL命中路径", []AssetInterceptRule{rule("fuzzy_url", "/admin", true)},
nil, nil, []string{"https://x.com/admin/panel"}, true, "https://x.com/admin/panel"},
{"禁用规则不命中", []AssetInterceptRule{rule("fuzzy_domain", ".gov.cn", false)},
[]string{"www.gov.cn"}, nil, nil, false, ""},
{"无规则不命中", nil, []string{"www.gov.cn"}, nil, nil, false, ""},
{"空pattern不命中", []AssetInterceptRule{rule("fuzzy_domain", " ", true)},
[]string{"www.gov.cn"}, nil, nil, false, ""},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
r, val, ok := MatchAssetInterceptRules(c.rules, c.domains, c.ips, c.urls)
if ok != c.want {
t.Fatalf("命中 = %v, 期望 %v (rule=%+v)", ok, c.want, r)
}
if ok && val != c.wantVal {
t.Fatalf("命中值 = %q, 期望 %q", val, c.wantVal)
}
})
}
}
func TestEvaluateAssetGate(t *testing.T) {
block := []AssetInterceptRule{rule("fuzzy_domain", ".gov.cn", true)}
allow := []AssetInterceptRule{rule("fuzzy_domain", "example.com", true)}
// 1. 命中拦截规则 → 拒绝(拦截原因优先)。
if d := EvaluateAssetGate(block, allow, []string{"www.gov.cn"}, nil, nil); d.Allowed {
t.Fatal("命中拦截规则应被拒绝")
}
// 2. 未命中拦截、有允许规则但不命中 → 拒绝(不允许)。
d := EvaluateAssetGate(block, allow, []string{"foo.other.com"}, nil, nil)
if d.Allowed {
t.Fatal("有白名单且不命中应被拒绝")
}
if d.Reason == "" {
t.Fatal("拒绝应带原因")
}
// 3. 未命中拦截、命中允许规则 → 放行。
if d := EvaluateAssetGate(block, allow, []string{"api.example.com"}, nil, nil); !d.Allowed {
t.Fatal("命中白名单应放行")
}
// 4. 无允许规则(白名单未启用)→ 未命中拦截即放行。
if d := EvaluateAssetGate(block, nil, []string{"foo.other.com"}, nil, nil); !d.Allowed {
t.Fatal("无白名单时未命中拦截应放行")
}
// 5. 允许规则全部禁用 → 视为白名单未启用,放行。
disabledAllow := []AssetInterceptRule{rule("fuzzy_domain", "example.com", false)}
if d := EvaluateAssetGate(nil, disabledAllow, []string{"foo.other.com"}, nil, nil); !d.Allowed {
t.Fatal("白名单全禁用时应放行")
}
// 6. 拦截优先于允许:同一目标既命中拦截又命中允许 → 拒绝。
if d := EvaluateAssetGate(
[]AssetInterceptRule{rule("fuzzy_domain", ".gov.cn", true)},
[]AssetInterceptRule{rule("fuzzy_domain", ".gov.cn", true)},
[]string{"www.gov.cn"}, nil, nil,
); d.Allowed {
t.Fatal("拦截应优先于允许")
}
}
func TestAssetInterceptCandidates(t *testing.T) {
// 只带 URL 的服务资产:host 应被拆出并归入域名候选,从而被 fuzzy_domain 命中。
a := &Asset{Type: "service", URL: "https://portal.beijing.gov.cn:8443/app"}
domains, _, urls := a.interceptCandidates()
if len(urls) != 1 || urls[0] != a.URL {
t.Fatalf("urls = %v", urls)
}
found := false
for _, d := range domains {
if d == "portal.beijing.gov.cn" {
found = true
}
}
if !found {
t.Fatalf("URL host 未拆入域名候选: %v", domains)
}
r, _, ok := MatchAssetInterceptRules([]AssetInterceptRule{rule("fuzzy_domain", ".gov.cn", true)}, domains, nil, urls)
if !ok {
t.Fatalf("仅带 URL 的政府服务资产应被 fuzzy_domain 命中, rule=%+v", r)
}
// URL host 是 IP 时应归入 IP 候选,可被 CIDR 命中。
b := &Asset{Type: "service", URL: "http://10.1.2.3/x"}
_, ips, _ := b.interceptCandidates()
if r, _, ok := MatchAssetInterceptRules([]AssetInterceptRule{rule("cidr", "10.0.0.0/8", true)}, nil, ips, nil); !ok {
t.Fatalf("URL 中的 IP 应被 CIDR 命中, ips=%v rule=%+v", ips, r)
}
}