// 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) }