package paymentguideser import ( "encoding/json" "strings" "testing" "time" "91porn-server/models/v/paymentguidemod" "go.mongodb.org/mongo-driver/bson/primitive" ) func TestPaymentGuideListResponseUsesLegacyVideoLimitDefault(t *testing.T) { list := []paymentguidemod.PaymentGuide{ {Scene: paymentguidemod.SceneVIPContentUpdate}, {Scene: paymentguidemod.SceneVIPContentUpdate, VideoLimit: 12}, {Scene: paymentguidemod.SceneVIPCenter}, } normalizeVideoLimits(list) if list[0].VideoLimit != paymentguidemod.DefaultVIPContentVideoLimit { t.Fatalf("legacy videoLimit = %d, want %d", list[0].VideoLimit, paymentguidemod.DefaultVIPContentVideoLimit) } if list[1].VideoLimit != 12 { t.Fatalf("configured videoLimit = %d, want 12", list[1].VideoLimit) } if list[2].VideoLimit != 0 { t.Fatalf("unrelated scene videoLimit = %d, want 0", list[2].VideoLimit) } } func TestSceneOptionsUseConfiguredFreeTrialCountAndHideLegacyScene(t *testing.T) { options := sceneOptions(5) if len(options) != paymentguidemod.MaxBatchSceneCount { t.Fatalf("len(options) = %d, want %d", len(options), paymentguidemod.MaxBatchSceneCount) } if options[0].Value != paymentguidemod.SceneHomeNewUserFreeTrial || options[0].Label != "首页新用户免费5次试看" || options[0].TotalWatchCount == nil || *options[0].TotalWatchCount != 5 { t.Fatalf("unexpected free-trial option: %+v", options[0]) } for _, option := range options { if option.Value == paymentguidemod.SceneHomeNewUser { t.Fatalf("legacy HOME_NEW_USER must not be returned: %+v", options) } } } func TestWebConfigurationRejectsLegacyHomeNewUserScene(t *testing.T) { listReq := ListReq{Scene: paymentguidemod.SceneHomeNewUser, PageNumber: 1, PageSize: 20} if _, err := listReq.List(); err == nil || !strings.Contains(err.Error(), "unsupported scene") { t.Fatalf("List() error = %v, want unsupported scene", err) } config := validBatchConfig() config.Scene = paymentguidemod.SceneHomeNewUser if err := Add(&config); err == nil || !strings.Contains(err.Error(), "unsupported scene") { t.Fatalf("Add() error = %v, want unsupported scene", err) } } func validBatchConfig() paymentguidemod.PaymentGuide { return paymentguidemod.PaymentGuide{ Segments: []string{paymentguidemod.SegmentNormal}, Style: "BOTTOM_SHEET", Title: "会员内容更新", VideoIDs: []string{"video-a"}, Action: paymentguidemod.Action{Type: "NONE"}, Enable: true, Sort: 100, } } func TestBatchAddReqConfigsNormalizesScenesAndDefaultsVideoLimit(t *testing.T) { req := BatchAddReq{ Scenes: []string{" home_new_user_free_trial ", "vip_content_update"}, Config: validBatchConfig(), } configs, err := req.Configs() if err != nil { t.Fatal(err) } if len(configs) != 2 { t.Fatalf("len(configs) = %d, want 2", len(configs)) } if configs[0].Scene != paymentguidemod.SceneHomeNewUserFreeTrial || configs[1].Scene != paymentguidemod.SceneVIPContentUpdate { t.Fatalf("unexpected scenes: %#v", []string{configs[0].Scene, configs[1].Scene}) } for i := range configs { if configs[i].VideoLimit != paymentguidemod.DefaultVIPContentVideoLimit { t.Fatalf("configs[%d].VideoLimit = %d, want %d", i, configs[i].VideoLimit, paymentguidemod.DefaultVIPContentVideoLimit) } } configs[0].Segments[0] = paymentguidemod.SegmentMaxVIP configs[0].VideoIDs[0] = "changed" if configs[1].Segments[0] != paymentguidemod.SegmentNormal || configs[1].VideoIDs[0] != "video-a" { t.Fatal("batch configs must not share slice storage") } if req.Config.Segments[0] != paymentguidemod.SegmentNormal || req.Config.VideoIDs[0] != "video-a" { t.Fatal("building configs must not mutate the request template") } } func TestBatchAddReqConfigsPreservesExplicitVideoLimit(t *testing.T) { config := validBatchConfig() config.VideoLimit = 12 configs, err := (BatchAddReq{ Scenes: []string{paymentguidemod.SceneVIPContentUpdate}, Config: config, }).Configs() if err != nil { t.Fatal(err) } if configs[0].VideoLimit != 12 { t.Fatalf("VideoLimit = %d, want 12", configs[0].VideoLimit) } } func TestBatchAddReqConfigsSupportsEveryScene(t *testing.T) { configs, err := (BatchAddReq{ Scenes: []string{ paymentguidemod.SceneHomeNewUserFreeTrial, paymentguidemod.SceneHomeOldUser, paymentguidemod.SceneVideoPreviewEnd, paymentguidemod.SceneDiscountCountdown, paymentguidemod.SceneVideoBack, paymentguidemod.SceneVIPCenter, paymentguidemod.SceneVIPContentUpdate, }, Config: validBatchConfig(), }).Configs() if err != nil { t.Fatal(err) } if len(configs) != paymentguidemod.MaxBatchSceneCount { t.Fatalf("len(configs) = %d, want %d", len(configs), paymentguidemod.MaxBatchSceneCount) } } func TestBatchAddReqConfigsDiscardsTemplateIdentityAndTimestamps(t *testing.T) { config := validBatchConfig() config.ID = primitive.NewObjectID() config.Scene = paymentguidemod.SceneVIPCenter config.CreatedAt = time.Now().Add(-2 * time.Hour) config.UpdatedAt = time.Now().Add(-time.Hour) configs, err := (BatchAddReq{ Scenes: []string{paymentguidemod.SceneHomeNewUserFreeTrial}, Config: config, }).Configs() if err != nil { t.Fatal(err) } if !configs[0].ID.IsZero() { t.Fatalf("ID = %s, want zero before insertion", configs[0].ID.Hex()) } if configs[0].Scene != paymentguidemod.SceneHomeNewUserFreeTrial { t.Fatalf("Scene = %s, want %s", configs[0].Scene, paymentguidemod.SceneHomeNewUserFreeTrial) } if !configs[0].CreatedAt.IsZero() || !configs[0].UpdatedAt.IsZero() { t.Fatalf("timestamps must be zero before insertion: %+v", configs[0]) } } func TestBatchAddReqConfigsRejectsInvalidScenes(t *testing.T) { tests := []struct { name string scenes []string match string }{ {name: "empty", scenes: nil, match: "scenes are required"}, {name: "unsupported", scenes: []string{"UNKNOWN"}, match: "unsupported scene"}, {name: "duplicate after normalization", scenes: []string{"VIP_CENTER", " vip_center "}, match: "duplicate scene"}, {name: "too many inputs", scenes: []string{"a", "b", "c", "d", "e", "f", "g", "h"}, match: "more than 7"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := (BatchAddReq{Scenes: tt.scenes, Config: validBatchConfig()}).Configs() if err == nil || !strings.Contains(err.Error(), tt.match) { t.Fatalf("Configs() error = %v, want match %q", err, tt.match) } }) } } func TestBatchAddReqConfigsValidatesTemplateForEveryScene(t *testing.T) { config := validBatchConfig() config.Title = "" _, err := (BatchAddReq{ Scenes: []string{paymentguidemod.SceneVIPCenter}, Config: config, }).Configs() if err == nil || !strings.Contains(err.Error(), "title is required") { t.Fatalf("Configs() error = %v, want title validation error", err) } } func TestAddReqSupportsExistingSingleSceneJSON(t *testing.T) { body := []byte(`{ "scene":"VIP_CONTENT_UPDATE", "style":"BOTTOM_SHEET", "title":"会员内容更新", "action":{"type":"NONE","value":""} }`) var req AddReq if err := json.Unmarshal(body, &req); err != nil { t.Fatal(err) } if req.IsBatch() { t.Fatal("existing flat add request must remain single-scene") } if req.Scene != paymentguidemod.SceneVIPContentUpdate || req.Title != "会员内容更新" { t.Fatalf("unexpected single add request: %+v", req.PaymentGuide) } } func TestAddReqSupportsBatchJSONShape(t *testing.T) { body := []byte(`{ "scenes":["HOME_NEW_USER_FREE_TRIAL","VIP_CONTENT_UPDATE"], "config":{ "style":"BOTTOM_SHEET", "title":"会员内容更新", "videoLimit":20, "action":{"type":"NONE","value":""}, "enable":true, "sort":100 } }`) var req AddReq if err := json.Unmarshal(body, &req); err != nil { t.Fatal(err) } if !req.IsBatch() || req.Config == nil { t.Fatalf("unexpected batch add request: %+v", req) } configs, err := (BatchAddReq{Scenes: req.Scenes, Config: *req.Config}).Configs() if err != nil { t.Fatal(err) } if len(configs) != 2 || configs[1].VideoLimit != 20 { t.Fatalf("unexpected configs: %+v", configs) } } func TestAddReqTreatsPresentEmptyScenesAsBatch(t *testing.T) { var req AddReq if err := json.Unmarshal([]byte(`{"scenes":[]}`), &req); err != nil { t.Fatal(err) } if !req.IsBatch() { t.Fatal("a present scenes field must use batch validation") } _, err := (BatchAddReq{Scenes: req.Scenes, Config: paymentguidemod.PaymentGuide{}}).Configs() if err == nil || !strings.Contains(err.Error(), "scenes are required") { t.Fatalf("Configs() error = %v, want scenes validation error", err) } }