aliyun sensor server
This commit is contained in:
@@ -0,0 +1,287 @@
|
||||
// Copyright 2022 E99p1ant. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package censor
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// resp 构造一个带指定维度判定的审核结果,省去每个用例重复拼结构体。
|
||||
func resp(suggestion string, details ...LabelScore) *TextCensorResponse {
|
||||
return &TextCensorResponse{
|
||||
SourceName: "qiniu",
|
||||
Pass: suggestion != SuggestionBlock,
|
||||
ProviderSuggestion: suggestion,
|
||||
Details: details,
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecideBaselineAlwaysBlocks(t *testing.T) {
|
||||
// 底线维度(涉政 / 暴恐 / 色情 / 自定义词库)是强制项:任何档位、任何置信度都必须拦截,
|
||||
// 且不受审核服务整体建议的影响(这里刻意给 pass 建议,验证仍然拦得住)。
|
||||
baselines := []ForbiddenType{
|
||||
ForbiddenTypePolitics,
|
||||
ForbiddenTypeTerrorism,
|
||||
ForbiddenTypePorn,
|
||||
ForbiddenTypeCustomized,
|
||||
}
|
||||
levels := []string{"lax", "standard", "strict", "", "unknown-level"}
|
||||
|
||||
for _, label := range baselines {
|
||||
for _, level := range levels {
|
||||
t.Run(string(label)+"/"+level, func(t *testing.T) {
|
||||
decision, detail := Decide(level, resp(SuggestionPass, LabelScore{Label: label, Score: 0.01}))
|
||||
assert.Equal(t, DecisionBlock, decision)
|
||||
assert.NotNil(t, detail)
|
||||
assert.Equal(t, label, detail.Label)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecideByLevel(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
level string
|
||||
response *TextCensorResponse
|
||||
wantBlock bool
|
||||
wantDetail ForbiddenType
|
||||
}{
|
||||
{
|
||||
name: "宽松:高置信度辱骂也放行",
|
||||
level: "lax",
|
||||
response: resp(SuggestionReview, LabelScore{Label: ForbiddenTypeAbuse, Score: 0.99}),
|
||||
wantBlock: false,
|
||||
wantDetail: "",
|
||||
},
|
||||
{
|
||||
name: "宽松:垃圾信息放行",
|
||||
level: "lax",
|
||||
response: resp(SuggestionPass, LabelScore{Label: ForbiddenTypeSpam, Score: 0.9}),
|
||||
wantBlock: false,
|
||||
wantDetail: "",
|
||||
},
|
||||
{
|
||||
name: "宽松:不采信审核服务的整体 block 建议",
|
||||
level: "lax",
|
||||
response: resp(SuggestionBlock),
|
||||
wantBlock: false,
|
||||
wantDetail: "",
|
||||
},
|
||||
{
|
||||
name: "标准:高置信度辱骂拦截",
|
||||
level: "standard",
|
||||
response: resp(SuggestionReview, LabelScore{Label: ForbiddenTypeAbuse, Score: 0.95}),
|
||||
wantBlock: true,
|
||||
wantDetail: ForbiddenTypeAbuse,
|
||||
},
|
||||
{
|
||||
name: "标准:辱骂置信度不足 90% 放行(收敛误伤)",
|
||||
level: "standard",
|
||||
response: resp(SuggestionReview, LabelScore{Label: ForbiddenTypeAbuse, Score: 0.72}),
|
||||
wantBlock: false,
|
||||
wantDetail: "",
|
||||
},
|
||||
{
|
||||
name: "标准:辱骂恰好 90% 拦截(阈值含等号)",
|
||||
level: "standard",
|
||||
response: resp(SuggestionReview, LabelScore{Label: ForbiddenTypeAbuse, Score: 0.9}),
|
||||
wantBlock: true,
|
||||
wantDetail: ForbiddenTypeAbuse,
|
||||
},
|
||||
{
|
||||
name: "标准:违禁不限置信度",
|
||||
level: "standard",
|
||||
response: resp(SuggestionPass, LabelScore{Label: ForbiddenTypeContraband, Score: 0.3}),
|
||||
wantBlock: true,
|
||||
wantDetail: ForbiddenTypeContraband,
|
||||
},
|
||||
{
|
||||
name: "标准:垃圾信息放行",
|
||||
level: "standard",
|
||||
response: resp(SuggestionPass, LabelScore{Label: ForbiddenTypeSpam, Score: 0.99}),
|
||||
wantBlock: false,
|
||||
wantDetail: "",
|
||||
},
|
||||
{
|
||||
name: "标准:不采信整体 block 建议",
|
||||
level: "standard",
|
||||
response: resp(SuggestionBlock),
|
||||
wantBlock: false,
|
||||
wantDetail: "",
|
||||
},
|
||||
{
|
||||
name: "严格:辱骂不限置信度拦截(最严档)",
|
||||
level: "strict",
|
||||
response: resp(SuggestionReview, LabelScore{Label: ForbiddenTypeAbuse, Score: 0.2}),
|
||||
wantBlock: true,
|
||||
wantDetail: ForbiddenTypeAbuse,
|
||||
},
|
||||
{
|
||||
name: "严格:无意义内容拦截",
|
||||
level: "strict",
|
||||
response: resp(SuggestionReview, LabelScore{Label: ForbiddenTypeMeaningless, Score: 0.5}),
|
||||
wantBlock: true,
|
||||
wantDetail: ForbiddenTypeMeaningless,
|
||||
},
|
||||
{
|
||||
name: "严格:采信整体 block 建议(无明细维度)",
|
||||
level: "strict",
|
||||
response: resp(SuggestionBlock),
|
||||
wantBlock: true,
|
||||
wantDetail: "",
|
||||
},
|
||||
{
|
||||
name: "严格:仅 review 且无维度命中则放行",
|
||||
level: "strict",
|
||||
response: resp(SuggestionReview),
|
||||
wantBlock: false,
|
||||
wantDetail: "",
|
||||
},
|
||||
{
|
||||
name: "全部正常文本放行",
|
||||
level: "strict",
|
||||
response: resp(SuggestionPass),
|
||||
wantBlock: false,
|
||||
wantDetail: "",
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
decision, detail := Decide(tc.level, tc.response)
|
||||
assert.Equal(t, tc.wantBlock, decision.Blocked())
|
||||
|
||||
if tc.wantDetail == "" {
|
||||
assert.Nil(t, detail)
|
||||
return
|
||||
}
|
||||
assert.NotNil(t, detail)
|
||||
assert.Equal(t, tc.wantDetail, detail.Label)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecideNilResponse(t *testing.T) {
|
||||
// 空结果(审核未返回)不应被当成命中,避免把「拿不到结论」误判为违规。
|
||||
decision, detail := Decide("strict", nil)
|
||||
assert.Equal(t, DecisionAllow, decision)
|
||||
assert.Nil(t, detail)
|
||||
}
|
||||
|
||||
func TestDecideUnknownLevelFallsBackToStrict(t *testing.T) {
|
||||
// 脏数据 / 未识别取值必须回退「严格」,方向保守:
|
||||
// 宁可沿用升级前的严格判定,也不要因为认不出档位就静默放宽。
|
||||
unknown := resp(SuggestionPass, LabelScore{Label: ForbiddenTypeSpam, Score: 0.9})
|
||||
decision, _ := Decide("", unknown)
|
||||
assert.Equal(t, DecisionBlock, decision)
|
||||
decision, _ = Decide("bogus", unknown)
|
||||
assert.Equal(t, DecisionBlock, decision)
|
||||
}
|
||||
|
||||
func TestStrictnessHelpers(t *testing.T) {
|
||||
assert.True(t, IsValidStrictness("lax"))
|
||||
assert.True(t, IsValidStrictness("standard"))
|
||||
assert.True(t, IsValidStrictness("strict"))
|
||||
assert.False(t, IsValidStrictness(""))
|
||||
assert.False(t, IsValidStrictness("STRICT"))
|
||||
assert.False(t, IsValidStrictness("off"))
|
||||
|
||||
assert.Equal(t, StrictnessStrict, ParseStrictness(""))
|
||||
assert.Equal(t, StrictnessStrict, ParseStrictness("nope"))
|
||||
assert.Equal(t, StrictnessLax, ParseStrictness("lax"))
|
||||
assert.Equal(t, StrictnessStandard, ParseStrictness("standard"))
|
||||
}
|
||||
|
||||
func TestQiniuParserFeedsPolicy(t *testing.T) {
|
||||
// 策略层完全依赖 parser 填好的 Details / ProviderSuggestion,这里做一次端到端校验:
|
||||
// 审核服务给出 review(自身判定为放行)但带 abuse 维度,严格档仍应拦得住。
|
||||
raw := []byte(`{
|
||||
"code": 200,
|
||||
"message": "ok",
|
||||
"result": {
|
||||
"suggestion": "review",
|
||||
"scenes": {
|
||||
"antispam": {
|
||||
"suggestion": "review",
|
||||
"details": [
|
||||
{"label": "normal", "score": 0.02, "contexts": []},
|
||||
{"label": "abuse", "score": 0.94, "contexts": [{"context": "示例片段", "positions": [{"startPos": 0, "endPos": 4}]}]}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
||||
parsed, err := QiniuTextCensorParser(raw)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// normal 维度必须被过滤掉,只保留真正命中的维度。
|
||||
assert.Len(t, parsed.Details, 1)
|
||||
assert.Equal(t, ForbiddenTypeAbuse, parsed.Details[0].Label)
|
||||
assert.Equal(t, 0.94, parsed.Details[0].Score)
|
||||
assert.Equal(t, SuggestionReview, parsed.ProviderSuggestion)
|
||||
assert.Equal(t, "示例片段", parsed.Hint)
|
||||
|
||||
// 审核服务自己判 review(Pass=true),但档位策略仍可覆盖它。
|
||||
assert.True(t, parsed.Pass)
|
||||
|
||||
decision, detail := Decide("strict", parsed)
|
||||
assert.Equal(t, DecisionBlock, decision)
|
||||
assert.NotNil(t, detail)
|
||||
assert.Equal(t, ForbiddenTypeAbuse, detail.Label)
|
||||
|
||||
decision, _ = Decide("standard", parsed)
|
||||
assert.Equal(t, DecisionBlock, decision)
|
||||
|
||||
decision, _ = Decide("lax", parsed)
|
||||
assert.False(t, decision.Blocked())
|
||||
}
|
||||
|
||||
func TestQiniuParserProviderSuggestionFallback(t *testing.T) {
|
||||
// 外层 suggestion 缺失时回退到 antispam 场景内的建议,避免整体建议丢失后
|
||||
// 严格档的「采信整体 block」分支失效。
|
||||
raw := []byte(`{"code":200,"result":{"scenes":{"antispam":{"suggestion":"block","details":[]}}}}`)
|
||||
|
||||
parsed, err := QiniuTextCensorParser(raw)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, SuggestionBlock, parsed.ProviderSuggestion)
|
||||
assert.Empty(t, parsed.Details)
|
||||
|
||||
// 严格档:无具体维度,但整体建议为 block → 拦截。
|
||||
decision, detail := Decide("strict", parsed)
|
||||
assert.Equal(t, DecisionBlock, decision)
|
||||
assert.Nil(t, detail)
|
||||
|
||||
// 标准档不采信整体建议 → 放行(这是「少误伤」的核心差异)。
|
||||
decision, _ = Decide("standard", parsed)
|
||||
assert.False(t, decision.Blocked())
|
||||
}
|
||||
|
||||
func TestBlockedLabelsIncludesBaselineAtEveryLevel(t *testing.T) {
|
||||
// 供设置页展示拦截范围:底线维度必须出现在每一个档位的清单里。
|
||||
for _, level := range []string{"lax", "standard", "strict"} {
|
||||
labels := BlockedLabels(level)
|
||||
assert.Contains(t, labels, ForbiddenTypePolitics, "level=%s", level)
|
||||
assert.Contains(t, labels, ForbiddenTypeTerrorism, "level=%s", level)
|
||||
assert.Contains(t, labels, ForbiddenTypePorn, "level=%s", level)
|
||||
assert.Contains(t, labels, ForbiddenTypeCustomized, "level=%s", level)
|
||||
|
||||
// 不重复展示底线维度。
|
||||
seen := map[ForbiddenType]int{}
|
||||
for _, label := range labels {
|
||||
seen[label]++
|
||||
}
|
||||
for label, count := range seen {
|
||||
assert.Equal(t, 1, count, "label %s 重复出现", label)
|
||||
}
|
||||
}
|
||||
|
||||
// 宽松档只含底线四个维度(涉政 / 暴恐 / 色情 / 自定义词库)。
|
||||
assert.Len(t, BlockedLabels("lax"), 4)
|
||||
assert.Contains(t, BlockedLabels("standard"), ForbiddenTypeAbuse)
|
||||
assert.NotContains(t, BlockedLabels("standard"), ForbiddenTypeSpam)
|
||||
assert.Contains(t, BlockedLabels("strict"), ForbiddenTypeSpam)
|
||||
}
|
||||
Reference in New Issue
Block a user