feat(phase2): 追责取证与确权举证(F19/F20)
- internal/provenance: 全链路存证(送审/审核/发码/转码/入库/注入)+责任界定 - service: Provenance/Accountability(定位首次哈希变化节点)/CopyrightEvidence/MatchInfringement - api: /content/provenance, /accountability, /evidence, /infringe-match - 转码版哈希不误判为篡改; 感知哈希侵权比对分级(high/medium) - 11项新测试通过; 端到端: 审播一致判定+证据链+侵权命中
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tcs-iptv/tcs/internal/chain"
|
||||
"github.com/tcs-iptv/tcs/internal/model"
|
||||
)
|
||||
|
||||
// 全链路一致:发码→入库→正确注入,追责判定审播一致。
|
||||
func TestAccountability_Consistent(t *testing.T) {
|
||||
s := newService(t)
|
||||
maCode, ctid, cert := issueOne(t, s)
|
||||
require.NoError(t, s.IngestToLibrary(chain.RoleReviewer, maCode, ctid, "M", "陕西IPTV媒资库"))
|
||||
require.NoError(t, s.PublishToOperator(PublishRequest{MACode: maCode, Certificate: cert}))
|
||||
_, err := s.InjectToCDN(chain.RoleOperator, ctid, maCode, "filehash-abc", "CT-SX", "cdn://x")
|
||||
require.NoError(t, err)
|
||||
|
||||
rep := s.Accountability(maCode)
|
||||
assert.True(t, rep.Consistent, "全链路应一致")
|
||||
assert.Nil(t, rep.FirstChange)
|
||||
assert.Equal(t, "filehash-abc", rep.BaselineHash)
|
||||
assert.NotEmpty(t, rep.Trail)
|
||||
}
|
||||
|
||||
// 注入环节偷换:追责定位到 cdn_inject 节点与运营商。
|
||||
func TestAccountability_TamperLocated(t *testing.T) {
|
||||
s := newService(t)
|
||||
maCode, ctid, cert := issueOne(t, s)
|
||||
require.NoError(t, s.IngestToLibrary(chain.RoleReviewer, maCode, ctid, "M", "媒资库"))
|
||||
require.NoError(t, s.PublishToOperator(PublishRequest{MACode: maCode, Certificate: cert}))
|
||||
|
||||
// 直接往存证里记录一次"被偷换"的注入(绕过校验模拟违规留痕)
|
||||
// 实际中 InjectToCDN 会拒绝不匹配,但运营商侧若绕过校验偷换,存证仍会暴露
|
||||
_, _ = s.InjectToCDN(chain.RoleOperator, ctid, maCode, "filehash-abc", "CT-SX", "cdn://x")
|
||||
s.prov.Record(model.ProvenanceEvent{
|
||||
MACode: maCode, Node: model.NodeInject, HashValue: "TAMPERED",
|
||||
Operator: "CT-SX-违规", Detail: "疑似偷换",
|
||||
})
|
||||
|
||||
rep := s.Accountability(maCode)
|
||||
assert.False(t, rep.Consistent)
|
||||
require.NotNil(t, rep.FirstChange)
|
||||
assert.Equal(t, model.NodeInject, rep.FirstChange.Node)
|
||||
assert.Contains(t, rep.Conclusion, "cdn_inject")
|
||||
}
|
||||
|
||||
// 转码版哈希不同属合法,不应误判为篡改。
|
||||
func TestAccountability_TranscodeNotFlagged(t *testing.T) {
|
||||
s := newService(t)
|
||||
maCode, ctid, _ := issueOne(t, s)
|
||||
_, err := s.BindTranscoded(chain.RoleReviewer, ctid, "filehash-abc", "h265-4k", "H.265", "4K", "v1-4k")
|
||||
require.NoError(t, err)
|
||||
s.prov.Record(model.ProvenanceEvent{MACode: maCode, Node: model.NodeTranscode, HashValue: "h265-4k", Operator: "转码中心"})
|
||||
|
||||
rep := s.Accountability(maCode)
|
||||
assert.True(t, rep.Consistent, "转码版哈希不同属合法,不应判为篡改")
|
||||
}
|
||||
|
||||
func TestCopyrightEvidence(t *testing.T) {
|
||||
s := newService(t)
|
||||
maCode, _, _ := issueOne(t, s)
|
||||
|
||||
ev, err := s.CopyrightEvidence(maCode)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, maCode, ev.MACode)
|
||||
assert.NotEmpty(t, ev.Trail)
|
||||
assert.False(t, ev.FirstSeenAt.IsZero(), "应有最早登记时间戳")
|
||||
assert.Contains(t, ev.Statement, "谁先锁定谁有权")
|
||||
}
|
||||
|
||||
func TestMatchInfringement(t *testing.T) {
|
||||
s := newService(t)
|
||||
// 注册一部正版,感知哈希为合法 16 位十六进制(模拟 aHash/dHash 输出)
|
||||
sub := sampleSub()
|
||||
sub.Perceptual = "a1b2c3d4e5f60718"
|
||||
r, err := s.SubmitForReview(sub)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, s.ReviewCSPS(r.ReviewID, true, "rv-1"))
|
||||
issued, err := s.ApproveAndIssue(chain.RoleRegulator, r.ReviewID, "陕西IPTV")
|
||||
require.NoError(t, err)
|
||||
|
||||
// 完全相同的感知哈希 → 高度相似(距离 0)
|
||||
matches, err := s.MatchInfringement("a1b2c3d4e5f60718", 5, 10)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, matches)
|
||||
assert.Equal(t, issued.MACode, matches[0].MACode)
|
||||
assert.Equal(t, "high", matches[0].Similarity)
|
||||
assert.Equal(t, 0, matches[0].Distance)
|
||||
|
||||
// 差异极大的哈希 → 不命中
|
||||
noMatch, err := s.MatchInfringement("ffffffffffffffff", 5, 10)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, noMatch)
|
||||
}
|
||||
Reference in New Issue
Block a user