a329d4906b
- 方案文档: AVCC 体系建设、IPTV TCS 需求(0-req)/PRD(1-prd)/任务(2-task)/二三四期任务 - tcs-iptv: Go 后端(哈希SDK/MA码生成/可信数据空间mock/业务编排/HTTP API+HMAC鉴权) - web-console: React+AntD 监管大屏(角色工作台/全流程演示/监管片库) - 一剧一码+集级哈希, 集级下架/恢复, 全栈测试通过
150 lines
4.6 KiB
Go
150 lines
4.6 KiB
Go
package chain
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/tcs-iptv/tcs/internal/model"
|
|
)
|
|
|
|
func newIssued(t *testing.T) *MemoryChain {
|
|
t.Helper()
|
|
c := NewMemoryChain()
|
|
_, err := c.IssueMA(RoleRegulator, IssueRequest{
|
|
MACode: "(京)网微剧审字(2025)第123号",
|
|
ContentTwinID: "ctid-001",
|
|
MerkleRoot: "merkle-root-1",
|
|
FileHash: "filehash-1",
|
|
Content: model.Content{Title: "示例剧集", EpisodeCount: 24},
|
|
})
|
|
require.NoError(t, err)
|
|
return c
|
|
}
|
|
|
|
func TestIssueMA_OnlyRegulator(t *testing.T) {
|
|
c := NewMemoryChain()
|
|
_, err := c.IssueMA(RoleCP, IssueRequest{MACode: "MA-1", ContentTwinID: "ct-1", FileHash: "h1"})
|
|
assert.ErrorIs(t, err, ErrPermissionDenied)
|
|
|
|
_, err = c.IssueMA(RoleReviewer, IssueRequest{MACode: "MA-1", ContentTwinID: "ct-1", FileHash: "h1"})
|
|
assert.ErrorIs(t, err, ErrPermissionDenied)
|
|
|
|
_, err = c.IssueMA(RoleRegulator, IssueRequest{MACode: "MA-1", ContentTwinID: "ct-1", FileHash: "h1"})
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestIssueMA_NoReissue(t *testing.T) {
|
|
c := newIssued(t)
|
|
// 同 MA 码重复签发被拒(1:1 不可解绑/不可覆盖)
|
|
_, err := c.IssueMA(RoleRegulator, IssueRequest{
|
|
MACode: "(京)网微剧审字(2025)第123号", ContentTwinID: "ctid-001", FileHash: "other",
|
|
})
|
|
assert.ErrorIs(t, err, ErrMAAlreadyIssued)
|
|
}
|
|
|
|
func TestIssueMA_DuplicateHashRejected(t *testing.T) {
|
|
c := newIssued(t)
|
|
// 换壳重发:不同 MA 码但相同内容哈希 → 拒绝
|
|
_, err := c.IssueMA(RoleRegulator, IssueRequest{
|
|
MACode: "(沪)网微剧审字(2025)第999号", ContentTwinID: "ctid-002", FileHash: "filehash-1",
|
|
})
|
|
assert.ErrorIs(t, err, ErrHashExists)
|
|
}
|
|
|
|
func TestVerifyHash_MatchAndMismatch(t *testing.T) {
|
|
c := newIssued(t)
|
|
|
|
res, err := c.VerifyHash("(京)网微剧审字(2025)第123号", "filehash-1")
|
|
require.NoError(t, err)
|
|
assert.True(t, res.Match)
|
|
assert.True(t, res.Valid)
|
|
|
|
res, err = c.VerifyHash("(京)网微剧审字(2025)第123号", "tampered-hash")
|
|
require.NoError(t, err)
|
|
assert.False(t, res.Match)
|
|
assert.Equal(t, "filehash-1", res.BoundHash)
|
|
}
|
|
|
|
func TestVerifyHash_UnknownMA(t *testing.T) {
|
|
c := NewMemoryChain()
|
|
_, err := c.VerifyHash("no-such-ma", "h")
|
|
assert.ErrorIs(t, err, ErrMANotIssued)
|
|
}
|
|
|
|
func TestRegisterMapping_RequiresIssuedMA(t *testing.T) {
|
|
c := NewMemoryChain()
|
|
// CTID 未签发 → 拒绝
|
|
_, err := c.RegisterMapping(RoleOperator, model.Mapping{
|
|
ContentTwinID: "ctid-x", Party: model.PartyOperator, PartyID: "OP-1",
|
|
})
|
|
assert.ErrorIs(t, err, ErrMANotIssued)
|
|
|
|
c = newIssued(t)
|
|
_, err = c.RegisterMapping(RoleOperator, model.Mapping{
|
|
ContentTwinID: "ctid-001", Party: model.PartyOperator,
|
|
PartyID: "CT-IPTV-008923", CDNEndpoint: "cdn://ct-gd/iptv/vod/008923",
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
res, err := c.QueryMappings("(京)网微剧审字(2025)第123号")
|
|
require.NoError(t, err)
|
|
assert.Len(t, res.Mappings, 1)
|
|
assert.Equal(t, []string{"cdn://ct-gd/iptv/vod/008923"}, res.CDNEndpoints)
|
|
}
|
|
|
|
func TestTranscodedBinding_ParentChild(t *testing.T) {
|
|
c := newIssued(t)
|
|
_, err := c.RegisterHashBinding(RoleReviewer, model.HashBinding{
|
|
ContentTwinID: "ctid-001",
|
|
HashType: model.HashTranscoded,
|
|
HashValue: "transcoded-h265-4k",
|
|
ParentHash: "filehash-1",
|
|
Version: "v1.0-h265",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// 转码版哈希也能验真通过
|
|
res, err := c.VerifyHash("(京)网微剧审字(2025)第123号", "transcoded-h265-4k")
|
|
require.NoError(t, err)
|
|
assert.True(t, res.Match)
|
|
}
|
|
|
|
func TestRevoke_OnlyRegulator(t *testing.T) {
|
|
c := newIssued(t)
|
|
_, _ = c.RegisterMapping(RoleOperator, model.Mapping{
|
|
ContentTwinID: "ctid-001", Party: model.PartyOperator,
|
|
PartyID: "OP-1", CDNEndpoint: "cdn://x/y/z",
|
|
})
|
|
|
|
_, err := c.Revoke(RoleOperator, "(京)网微剧审字(2025)第123号", "试图越权")
|
|
assert.ErrorIs(t, err, ErrPermissionDenied)
|
|
|
|
res, err := c.Revoke(RoleRegulator, "(京)网微剧审字(2025)第123号", "违规")
|
|
require.NoError(t, err)
|
|
assert.Contains(t, res.CDNEndpoints, "cdn://x/y/z")
|
|
|
|
ct, _ := c.QueryContent("(京)网微剧审字(2025)第123号")
|
|
assert.Equal(t, model.StatusRevoked, ct.Status)
|
|
}
|
|
|
|
func TestRecordVersionChange(t *testing.T) {
|
|
c := newIssued(t)
|
|
_, err := c.RecordVersionChange(model.VersionChange{
|
|
ContentTwinID: "ctid-001", Version: "v2.0",
|
|
ChangeReason: "片尾字幕修正", PrevHash: "filehash-1", NewHash: "filehash-2",
|
|
ReauditRequired: true, AffectedEpisode: 24,
|
|
})
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestHashExists(t *testing.T) {
|
|
c := newIssued(t)
|
|
ma, ok := c.HashExists("filehash-1")
|
|
assert.True(t, ok)
|
|
assert.Equal(t, "(京)网微剧审字(2025)第123号", ma)
|
|
|
|
_, ok = c.HashExists("unknown")
|
|
assert.False(t, ok)
|
|
}
|