Files
MAcode/tcs-iptv/internal/service/phase4_test.go
T
selfrelease 2cd5fbec6d 四期(大小屏融合)后端可代码部分:跨域解析网关/扫码验真/跨屏权益通兑
- model/rights.go: ScreenType/ParsedMA/ResolveResult/ScanVerifyResult/UserRights/PurchaseRecord/CrossScreenRightsResult
- service/phase4.go: ParseMACode + Resolve(C.1/C.2) + ScanVerify(B.2) + RecordPurchase/VerifyCrossScreenRights(D.1)
- api/handlers.go: GET /content/resolve, POST /content/scan-verify, /rights/purchase, /rights/verify
- service/phase4_test.go: 18 单测全绿
- 同一MA码跨iptv/ott/app统一解析; 任一屏购买全屏通看不重复扣费
- OTT/移动端SDK/C2PA凭证标注需外部环境
- 更新 5-task-IPTV-四期.md 进度
2026-06-14 19:01:26 +08:00

184 lines
5.7 KiB
Go

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