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