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" ) // issueAndPublish 走完送审→审核→发码→入库→发布,返回已流通内容的 MA 码与 ctid。 func issueAndPublish(t *testing.T, s *Service) (maCode, ctid string) { t.Helper() sub, err := s.SubmitForReview(sampleSub()) require.NoError(t, err) require.NoError(t, s.ReviewCSPS(sub.ReviewID, true, "rv-1")) issued, err := s.ApproveAndIssue(chain.RoleRegulator, sub.ReviewID, "陕西省广播电视局") require.NoError(t, err) require.NoError(t, s.IngestToLibrary(chain.RoleReviewer, issued.MACode, issued.ContentTwinID, "MA-001", "省媒资库")) require.NoError(t, s.PublishToOperator(PublishRequest{MACode: issued.MACode, Certificate: issued.Certificate})) return issued.MACode, issued.ContentTwinID } func TestParseMACode_Valid(t *testing.T) { p := ParseMACode("MA.156.8531.6101/WD/20260000004") assert.True(t, p.Valid) assert.Equal(t, "156", p.CountryCode) assert.Equal(t, "8531", p.IndustryNode) assert.Equal(t, "6101", p.OrgNode) assert.Equal(t, "WD", p.Category) assert.Equal(t, "2026", p.Year) assert.Equal(t, 0, p.Episode) } func TestParseMACode_WithEpisode(t *testing.T) { p := ParseMACode("MA.156.8531.6101/WD/20260000004#E07") assert.True(t, p.Valid) assert.Equal(t, 7, p.Episode) } func TestParseMACode_Invalid(t *testing.T) { for _, bad := range []string{"", "not-a-code", "MA.156", "XX.156.8531.6101/WD/20260000004"} { p := ParseMACode(bad) assert.False(t, p.Valid, "应判定非法: %q", bad) } } func TestResolve_PublishedAvailableAllScreens(t *testing.T) { s := newService(t) maCode, _ := issueAndPublish(t, s) r := s.Resolve(maCode) assert.True(t, r.Resolved) assert.True(t, r.InCirculation) assert.Equal(t, model.StatusPublished, r.Status) assert.ElementsMatch(t, model.AllScreens(), r.Screens, "流通内容应大小屏统一可用") } func TestResolve_EpisodeSubIDResolvesToBase(t *testing.T) { s := newService(t) maCode, _ := issueAndPublish(t, s) r := s.Resolve(maCode + "#E03") assert.True(t, r.Resolved) assert.Equal(t, 3, r.Parsed.Episode) assert.True(t, r.InCirculation) } func TestResolve_InvalidCode(t *testing.T) { s := newService(t) r := s.Resolve("bogus-code") assert.False(t, r.Resolved) assert.False(t, r.Parsed.Valid) } func TestResolve_UnregisteredCode(t *testing.T) { s := newService(t) r := s.Resolve("MA.156.8531.6101/WD/20269999999") assert.True(t, r.Parsed.Valid) assert.False(t, r.Resolved) } func TestResolve_RevokedNotInCirculation(t *testing.T) { s := newService(t) maCode, _ := issueAndPublish(t, s) _, err := s.Takedown(chain.RoleRegulator, maCode, "违规") require.NoError(t, err) r := s.Resolve(maCode) assert.True(t, r.Resolved) assert.False(t, r.InCirculation) assert.Empty(t, r.Screens) } func TestScanVerify_AuthenticAndCompliant(t *testing.T) { s := newService(t) maCode, _ := issueAndPublish(t, s) r := s.ScanVerify(maCode) assert.True(t, r.Authentic) assert.True(t, r.Compliant) } func TestScanVerify_FakeCode(t *testing.T) { s := newService(t) r := s.ScanVerify("MA.156.8531.6101/WD/20269999999") assert.False(t, r.Authentic, "未登记的真结构码不应判为真") assert.False(t, r.Compliant) } func TestScanVerify_RevokedNotCompliant(t *testing.T) { s := newService(t) maCode, _ := issueAndPublish(t, s) _, err := s.Takedown(chain.RoleRegulator, maCode, "违规") require.NoError(t, err) r := s.ScanVerify(maCode) assert.True(t, r.Authentic, "下架仍是真码") assert.False(t, r.Compliant, "下架内容不合规") } func TestRecordPurchase_InvalidScreenRejected(t *testing.T) { s := newService(t) maCode, _ := issueAndPublish(t, s) _, err := s.RecordPurchase(maCode, "user-1", "smartwatch") assert.Error(t, err) } func TestRecordPurchase_UnknownMACodeRejected(t *testing.T) { s := newService(t) _, err := s.RecordPurchase("MA.156.8531.6101/WD/20269999999", "user-1", model.ScreenIPTV) assert.Error(t, err) } func TestCrossScreenRights_BuyOnceWatchEverywhere(t *testing.T) { s := newService(t) maCode, _ := issueAndPublish(t, s) // 电视端购买 rec, err := s.RecordPurchase(maCode, "user-1", model.ScreenIPTV) require.NoError(t, err) assert.Equal(t, model.ScreenIPTV, rec.Screen) // 手机端通看,不重复付费 r := s.VerifyCrossScreenRights(maCode, "user-1", model.ScreenApp) assert.True(t, r.Entitled) assert.Equal(t, model.ScreenIPTV, r.PurchaseScreen) // OTT 端通看 r2 := s.VerifyCrossScreenRights(maCode, "user-1", model.ScreenOTT) assert.True(t, r2.Entitled) } func TestCrossScreenRights_NotPurchased(t *testing.T) { s := newService(t) maCode, _ := issueAndPublish(t, s) r := s.VerifyCrossScreenRights(maCode, "user-x", model.ScreenApp) assert.False(t, r.Entitled) } func TestRecordPurchase_IdempotentNoDoubleCharge(t *testing.T) { s := newService(t) maCode, _ := issueAndPublish(t, s) first, err := s.RecordPurchase(maCode, "user-1", model.ScreenIPTV) require.NoError(t, err) // 在手机端再次"购买"同内容 → 返回首次记录,不新建(跨屏通兑) second, err := s.RecordPurchase(maCode, "user-1", model.ScreenApp) require.NoError(t, err) assert.Equal(t, first.Screen, second.Screen, "重复购买应返回首次记录的购买屏") assert.Equal(t, first.PurchasedAt, second.PurchasedAt) } func TestCrossScreenRights_EpisodeSubIDSharesEntitlement(t *testing.T) { s := newService(t) maCode, _ := issueAndPublish(t, s) _, err := s.RecordPurchase(maCode, "user-1", model.ScreenIPTV) require.NoError(t, err) // 用集级子标识核验权益应归一到整剧 MA 码 r := s.VerifyCrossScreenRights(maCode+"#E05", "user-1", model.ScreenApp) assert.True(t, r.Entitled) }