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/macode" "github.com/tcs-iptv/tcs/internal/model" ) // 24 集微短剧:一剧一 MA 码,每集独立哈希,可按集验真。 func TestEpisodeLevel_OneSeriesOneCodeMultiEpisodeHash(t *testing.T) { s := newService(t) eps := make([]model.EpisodeHash, 0, 24) for i := 1; i <= 24; i++ { eps = append(eps, model.EpisodeHash{ Episode: i, FileSHA256: "ep-hash-" + string(rune('a'+i)), MerkleRoot: "ep-mr-" + string(rune('a'+i)), Duration: 180, }) } sub := Submission{ Title: "长安少年行", EpisodeCount: 24, Category: macode.CategoryMicroDrama, FileHash: "series-root-hash", MerkleRoot: "series-merkle-root", Episodes: eps, CPMediaID: "XAQJSL-2026-001", CPName: "西安曲江丝路文化传播有限公司", } 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) // 一剧一码 assert.True(t, macode.IsValid(issued.MACode)) // 24 集哈希全部绑定在同一 MA 码下 list, err := s.ListEpisodes(issued.MACode) require.NoError(t, err) assert.Len(t, list, 24) // 按集验真:第 7 集正确哈希匹配 res, err := s.VerifyEpisode(issued.MACode, 7, "ep-hash-"+string(rune('a'+7))) require.NoError(t, err) assert.True(t, res.Match) // 第 7 集错误哈希 → 不匹配(疑似该集被替换) _, err = s.VerifyEpisode(issued.MACode, 7, "tampered-ep7") assert.ErrorIs(t, err, ErrHashMismatch) } // 集级下架:只下架第3集,整剧其他集不受影响。 func TestEpisodeTakedown(t *testing.T) { s := newService(t) eps := []model.EpisodeHash{ {Episode: 1, FileSHA256: "h1"}, {Episode: 2, FileSHA256: "h2"}, {Episode: 3, FileSHA256: "h3"}, {Episode: 4, FileSHA256: "h4"}, } sub := Submission{ Title: "多集剧", EpisodeCount: 4, Category: macode.CategoryMicroDrama, FileHash: "series-h", MerkleRoot: "series-mr", Episodes: eps, } 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) // 运营商无权集级下架 err = s.TakedownEpisode(chain.RoleOperator, issued.MACode, 3, "第3集违规") assert.ErrorIs(t, err, chain.ErrPermissionDenied) // 监管下架第3集 require.NoError(t, s.TakedownEpisode(chain.RoleRegulator, issued.MACode, 3, "第3集违规")) list, err := s.ListEpisodes(issued.MACode) require.NoError(t, err) for _, b := range list { if b.Episode == 3 { assert.True(t, b.Revoked, "第3集应已下架") } else { assert.False(t, b.Revoked, "第%d集不应受影响", b.Episode) } } } // 集级子标识:MA码#E07 解析与生成。 func TestEpisodeSubID(t *testing.T) { ma := "MA.156.8531.6101/WD/20260000001" sub := macode.EpisodeSubID(ma, 7) assert.Equal(t, "MA.156.8531.6101/WD/20260000001#E07", sub) parsedMA, ep := macode.ParseEpisodeSubID(sub) assert.Equal(t, ma, parsedMA) assert.Equal(t, 7, ep) // 无后缀 → 整剧(episode 0) parsedMA2, ep2 := macode.ParseEpisodeSubID(ma) assert.Equal(t, ma, parsedMA2) assert.Equal(t, 0, ep2) } // 单体内容(电影,无分集):episodes 为空也能正常签发与整剧验真。 func TestSingleContent_NoEpisodes(t *testing.T) { s := newService(t) sub := sampleSub() sub.Episodes = nil 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) list, err := s.ListEpisodes(issued.MACode) require.NoError(t, err) assert.Empty(t, list, "单体内容无集级绑定") // 整剧验真仍可用 res, err := s.Verify(issued.MACode, sub.FileHash) require.NoError(t, err) assert.True(t, res.Match) }