@@ -0,0 +1,397 @@
|
||||
package paymentguideser
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"91porn-server/models/v/paymentguidemod"
|
||||
"91porn-server/models/v/sysconfmod"
|
||||
"91porn-server/models/v/usermod"
|
||||
"91porn-server/models/v/vidmod"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
func TestSegmentForUser(t *testing.T) {
|
||||
now := time.Now()
|
||||
tests := []struct {
|
||||
name string
|
||||
user *usermod.User
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "new unpaid",
|
||||
user: &usermod.User{
|
||||
CreatedAt: now.Add(-time.Hour),
|
||||
WatchCount: 3,
|
||||
},
|
||||
want: paymentguidemod.SegmentNewNeverPaid,
|
||||
},
|
||||
{
|
||||
name: "new user with exhausted free views stays new unpaid",
|
||||
user: &usermod.User{
|
||||
CreatedAt: now.Add(-time.Hour),
|
||||
WatchCount: 0,
|
||||
},
|
||||
want: paymentguidemod.SegmentNewNeverPaid,
|
||||
},
|
||||
{
|
||||
name: "expired is old unpaid",
|
||||
user: &usermod.User{
|
||||
CreatedAt: now.AddDate(0, 0, -30),
|
||||
VipLevel: 2,
|
||||
VipExpireDate: now.Add(-time.Hour),
|
||||
},
|
||||
want: paymentguidemod.SegmentOldNeverPaid,
|
||||
},
|
||||
{
|
||||
name: "active paid upgrade",
|
||||
user: &usermod.User{
|
||||
CreatedAt: now.AddDate(0, 0, -30),
|
||||
VipLevel: 2,
|
||||
VipExpireDate: now.AddDate(0, 1, 0),
|
||||
},
|
||||
want: paymentguidemod.SegmentPaidUpgrade,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := SegmentForUser(tt.user); got != tt.want {
|
||||
t.Fatalf("SegmentForUser() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestContentVersion(t *testing.T) {
|
||||
videos := []vidmod.PaymentGuideVideo{{ID: "video-a"}, {ID: "video-b"}}
|
||||
got := contentVersion(videos)
|
||||
if got == "" {
|
||||
t.Fatal("contentVersion must not be empty")
|
||||
}
|
||||
if got != contentVersion(videos) {
|
||||
t.Fatal("contentVersion must be stable for the same videos")
|
||||
}
|
||||
reordered := []vidmod.PaymentGuideVideo{{ID: "video-b"}, {ID: "video-a"}}
|
||||
if got == contentVersion(reordered) {
|
||||
t.Fatal("contentVersion must change when video order changes")
|
||||
}
|
||||
expanded := append(append([]vidmod.PaymentGuideVideo{}, videos...), vidmod.PaymentGuideVideo{ID: "video-c"})
|
||||
if got == contentVersion(expanded) {
|
||||
t.Fatal("contentVersion must change when configured video count changes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPaymentGuideSwitchesDefaultLegacyHomeToEnabled(t *testing.T) {
|
||||
legacy := paymentGuideSwitchesFromValues(map[sysconfmod.VCode]bool{
|
||||
sysconfmod.VCodePaymentGuideEnabled: true,
|
||||
})
|
||||
if !legacy.global || !legacy.home {
|
||||
t.Fatalf("legacy switches must keep the home scenes enabled: %+v", legacy)
|
||||
}
|
||||
disabled := paymentGuideSwitchesFromValues(map[sysconfmod.VCode]bool{
|
||||
sysconfmod.VCodePaymentGuideEnabled: true,
|
||||
sysconfmod.VCodePaymentGuideHomeEnabled: false,
|
||||
})
|
||||
if !disabled.global || disabled.home {
|
||||
t.Fatalf("configured home switch must be honored: %+v", disabled)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuideRespUsesVideosWithoutVideoIDs(t *testing.T) {
|
||||
body, err := json.Marshal(GuideResp{
|
||||
Show: true,
|
||||
Videos: []vidmod.PaymentGuideVideo{{ID: "video-a"}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
jsonBody := string(body)
|
||||
if !strings.Contains(jsonBody, `"videos"`) {
|
||||
t.Fatalf("response must contain videos: %s", jsonBody)
|
||||
}
|
||||
if strings.Contains(jsonBody, `"videoIds"`) {
|
||||
t.Fatalf("response must not contain videoIds: %s", jsonBody)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildPingGuide(t *testing.T) {
|
||||
freeTrialID := primitive.NewObjectID()
|
||||
homeID := primitive.NewObjectID()
|
||||
contentID := primitive.NewObjectID()
|
||||
configs := map[string]paymentguidemod.PaymentGuide{
|
||||
paymentguidemod.SceneHomeNewUserFreeTrial: {
|
||||
ID: freeTrialID,
|
||||
Scene: paymentguidemod.SceneHomeNewUserFreeTrial,
|
||||
Style: "BOTTOM_SHEET",
|
||||
Title: "free trial",
|
||||
Action: paymentguidemod.Action{Type: "NONE"},
|
||||
},
|
||||
paymentguidemod.SceneHomeOldUser: {
|
||||
ID: homeID,
|
||||
Scene: paymentguidemod.SceneHomeOldUser,
|
||||
Style: "BOTTOM_SHEET",
|
||||
Title: "open vip",
|
||||
ProductID: "product-id",
|
||||
DurationSeconds: 60,
|
||||
Action: paymentguidemod.Action{Type: "VIP_PRODUCT", Value: "product-id"},
|
||||
},
|
||||
paymentguidemod.SceneVIPContentUpdate: {
|
||||
ID: contentID,
|
||||
Scene: paymentguidemod.SceneVIPContentUpdate,
|
||||
},
|
||||
}
|
||||
|
||||
resp := buildPingGuide(paymentGuideSwitchState{global: true, home: true}, paymentguidemod.SegmentOldNeverPaid, 3, true, configs, nil)
|
||||
if !resp.Enabled || resp.Segment != paymentguidemod.SegmentOldNeverPaid {
|
||||
t.Fatalf("unexpected response header: %+v", resp)
|
||||
}
|
||||
if len(resp.Scenes) != len(pingGuideScenes) {
|
||||
t.Fatalf("scene count = %d, want %d", len(resp.Scenes), len(pingGuideScenes))
|
||||
}
|
||||
freeTrial := resp.Scenes[paymentguidemod.SceneHomeNewUserFreeTrial]
|
||||
if !freeTrial.Enabled || freeTrial.TotalWatchCount == nil || *freeTrial.TotalWatchCount != 3 || freeTrial.ConfigID != freeTrialID.Hex() {
|
||||
t.Fatalf("unexpected new-user free-trial scene: %+v", freeTrial)
|
||||
}
|
||||
home := resp.Scenes[paymentguidemod.SceneHomeOldUser]
|
||||
if !home.Enabled || home.Show == nil || !*home.Show || home.ConfigID != homeID.Hex() {
|
||||
t.Fatalf("unexpected ordinary scene: %+v", home)
|
||||
}
|
||||
if home.Action == nil || home.Action.Value != "product-id" {
|
||||
t.Fatalf("ordinary scene action missing: %+v", home)
|
||||
}
|
||||
content := resp.Scenes[paymentguidemod.SceneVIPContentUpdate]
|
||||
if !content.Enabled || content.Show != nil || content.ConfigID != "" {
|
||||
t.Fatalf("content-update Ping must expose enabled only: %+v", content)
|
||||
}
|
||||
missing := resp.Scenes[paymentguidemod.SceneVideoBack]
|
||||
if missing.Enabled || missing.Show == nil || *missing.Show {
|
||||
t.Fatalf("missing ordinary scene must be disabled: %+v", missing)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildPingGuideSuppressesShownAndGlobalDisabled(t *testing.T) {
|
||||
configID := primitive.NewObjectID()
|
||||
configs := map[string]paymentguidemod.PaymentGuide{
|
||||
paymentguidemod.SceneVIPCenter: {
|
||||
ID: configID,
|
||||
Scene: paymentguidemod.SceneVIPCenter,
|
||||
Style: "CENTER_POPUP",
|
||||
Title: "shown",
|
||||
Description: "description",
|
||||
Cover: "cover.png",
|
||||
ProductID: "product-id",
|
||||
DurationSeconds: 0,
|
||||
Action: paymentguidemod.Action{Type: "VIP_PRODUCT", Value: "product-id"},
|
||||
},
|
||||
}
|
||||
resp := buildPingGuide(
|
||||
paymentGuideSwitchState{global: true, home: true},
|
||||
paymentguidemod.SegmentNormal,
|
||||
3,
|
||||
true,
|
||||
configs,
|
||||
map[primitive.ObjectID]bool{configID: true},
|
||||
)
|
||||
vipCenter := resp.Scenes[paymentguidemod.SceneVIPCenter]
|
||||
if !vipCenter.Enabled || vipCenter.Show == nil || *vipCenter.Show || vipCenter.ConfigID != configID.Hex() {
|
||||
t.Fatalf("shown configuration must retain its display payload: %+v", vipCenter)
|
||||
}
|
||||
if vipCenter.Description == nil || *vipCenter.Description != "description" ||
|
||||
vipCenter.Cover == nil || *vipCenter.Cover != "cover.png" ||
|
||||
vipCenter.ProductID == nil || *vipCenter.ProductID != "product-id" ||
|
||||
vipCenter.DurationSeconds == nil || *vipCenter.DurationSeconds != 0 ||
|
||||
vipCenter.Action == nil || vipCenter.Action.Value != "product-id" {
|
||||
t.Fatalf("shown configuration payload is incomplete: %+v", vipCenter)
|
||||
}
|
||||
|
||||
disabled := buildPingGuide(paymentGuideSwitchState{}, paymentguidemod.SegmentNormal, 3, true, configs, nil)
|
||||
if disabled.Enabled {
|
||||
t.Fatal("global-disabled response must be disabled")
|
||||
}
|
||||
for scene, item := range disabled.Scenes {
|
||||
if item.Enabled {
|
||||
t.Fatalf("scene %s must be disabled", scene)
|
||||
}
|
||||
if scene != paymentguidemod.SceneVIPContentUpdate && (item.Show == nil || *item.Show) {
|
||||
t.Fatalf("ordinary scene %s must explicitly return show=false", scene)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildPingGuideUsesSharedHomeSwitch(t *testing.T) {
|
||||
freeTrialID := primitive.NewObjectID()
|
||||
homeOldID := primitive.NewObjectID()
|
||||
videoBackID := primitive.NewObjectID()
|
||||
configs := map[string]paymentguidemod.PaymentGuide{
|
||||
paymentguidemod.SceneHomeNewUserFreeTrial: {ID: freeTrialID, Scene: paymentguidemod.SceneHomeNewUserFreeTrial},
|
||||
paymentguidemod.SceneHomeOldUser: {ID: homeOldID, Scene: paymentguidemod.SceneHomeOldUser},
|
||||
paymentguidemod.SceneVideoBack: {ID: videoBackID, Scene: paymentguidemod.SceneVideoBack},
|
||||
}
|
||||
resp := buildPingGuide(
|
||||
paymentGuideSwitchState{global: true, home: false},
|
||||
paymentguidemod.SegmentNormal,
|
||||
3,
|
||||
true,
|
||||
configs,
|
||||
nil,
|
||||
)
|
||||
for _, scene := range []string{paymentguidemod.SceneHomeNewUserFreeTrial, paymentguidemod.SceneHomeOldUser} {
|
||||
item := resp.Scenes[scene]
|
||||
if item.Enabled || item.Show == nil || *item.Show || item.ConfigID != "" {
|
||||
t.Fatalf("home scene %s must be disabled by the shared switch: %+v", scene, item)
|
||||
}
|
||||
}
|
||||
videoBack := resp.Scenes[paymentguidemod.SceneVideoBack]
|
||||
if !videoBack.Enabled || videoBack.Show == nil || !*videoBack.Show || videoBack.ConfigID != videoBackID.Hex() {
|
||||
t.Fatalf("non-home scene must not be affected by the home switch: %+v", videoBack)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildPingGuideCountdownIsNotSuppressedByImpression(t *testing.T) {
|
||||
configID := primitive.NewObjectID()
|
||||
configs := map[string]paymentguidemod.PaymentGuide{
|
||||
paymentguidemod.SceneDiscountCountdown: {
|
||||
ID: configID,
|
||||
Scene: paymentguidemod.SceneDiscountCountdown,
|
||||
Style: "CENTER_POPUP",
|
||||
Title: "限时优惠",
|
||||
Description: "前端本地倒计时",
|
||||
Cover: "countdown.png",
|
||||
ProductID: "product-id",
|
||||
DurationSeconds: 0,
|
||||
Action: paymentguidemod.Action{Type: "VIP_PRODUCT", Value: "product-id"},
|
||||
},
|
||||
}
|
||||
resp := buildPingGuide(
|
||||
paymentGuideSwitchState{global: true, home: true},
|
||||
paymentguidemod.SegmentNormal,
|
||||
3,
|
||||
true,
|
||||
configs,
|
||||
map[primitive.ObjectID]bool{configID: true},
|
||||
)
|
||||
countdown := resp.Scenes[paymentguidemod.SceneDiscountCountdown]
|
||||
if !countdown.Enabled || countdown.Show == nil || !*countdown.Show || countdown.ConfigID != configID.Hex() {
|
||||
t.Fatalf("countdown must remain displayable on every App launch: %+v", countdown)
|
||||
}
|
||||
if countdown.DurationSeconds == nil || *countdown.DurationSeconds != 0 {
|
||||
t.Fatalf("countdown must return the complete configured card: %+v", countdown)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPaymentGuidePingJSONOmitsContentUpdateShow(t *testing.T) {
|
||||
body, err := json.Marshal(buildPingGuide(paymentGuideSwitchState{}, paymentguidemod.SegmentUnregistered, 3, false, nil, nil))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var decoded struct {
|
||||
Scenes map[string]map[string]interface{} `json:"scenes"`
|
||||
}
|
||||
var envelope struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Segment string `json:"segment"`
|
||||
Scenes map[string]map[string]interface{} `json:"scenes"`
|
||||
}
|
||||
if err = json.Unmarshal(body, &envelope); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
decoded.Scenes = envelope.Scenes
|
||||
if _, exists := decoded.Scenes[paymentguidemod.SceneHomeNewUser]; exists {
|
||||
t.Fatalf("legacy HOME_NEW_USER must not be returned in Ping: %s", body)
|
||||
}
|
||||
freeTrial := decoded.Scenes[paymentguidemod.SceneHomeNewUserFreeTrial]
|
||||
if count, exists := freeTrial["totalWatchCount"]; !exists || count != float64(3) {
|
||||
t.Fatalf("new-user free-trial Ping must include totalWatchCount=3: %s", body)
|
||||
}
|
||||
if _, exists := decoded.Scenes[paymentguidemod.SceneVIPContentUpdate]["show"]; exists {
|
||||
t.Fatalf("VIP_CONTENT_UPDATE Ping must omit show: %s", body)
|
||||
}
|
||||
if show, exists := decoded.Scenes[paymentguidemod.SceneHomeOldUser]["show"]; !exists || show != false {
|
||||
t.Fatalf("home Ping scene must include show=false: %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPaymentGuidePingJSONKeepsConfiguredFieldsWhenShowFalse(t *testing.T) {
|
||||
configID := primitive.NewObjectID()
|
||||
body, err := json.Marshal(buildPingGuide(
|
||||
paymentGuideSwitchState{global: true, home: true},
|
||||
paymentguidemod.SegmentNormal,
|
||||
3,
|
||||
true,
|
||||
map[string]paymentguidemod.PaymentGuide{
|
||||
paymentguidemod.SceneVideoBack: {
|
||||
ID: configID,
|
||||
Scene: paymentguidemod.SceneVideoBack,
|
||||
Style: "CENTER_POPUP",
|
||||
Title: "返回引导",
|
||||
Description: "",
|
||||
Cover: "",
|
||||
ProductID: "",
|
||||
DurationSeconds: 0,
|
||||
Action: paymentguidemod.Action{Type: "NONE"},
|
||||
},
|
||||
},
|
||||
map[primitive.ObjectID]bool{configID: true},
|
||||
))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var envelope struct {
|
||||
Scenes map[string]map[string]interface{} `json:"scenes"`
|
||||
}
|
||||
if err = json.Unmarshal(body, &envelope); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
item := envelope.Scenes[paymentguidemod.SceneVideoBack]
|
||||
for _, field := range []string{"configId", "style", "title", "description", "cover", "productId", "durationSeconds", "action"} {
|
||||
if _, exists := item[field]; !exists {
|
||||
t.Fatalf("configured show=false scene must include %s: %s", field, body)
|
||||
}
|
||||
}
|
||||
if show, ok := item["show"].(bool); !ok || show {
|
||||
t.Fatalf("configured scene must preserve show=false: %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildPingGuideSuppressesExhaustedNewUserFreeTrialOnly(t *testing.T) {
|
||||
freeTrialID := primitive.NewObjectID()
|
||||
videoBackID := primitive.NewObjectID()
|
||||
resp := buildPingGuide(
|
||||
paymentGuideSwitchState{global: true, home: true},
|
||||
paymentguidemod.SegmentNewNeverPaid,
|
||||
3,
|
||||
false,
|
||||
map[string]paymentguidemod.PaymentGuide{
|
||||
paymentguidemod.SceneHomeNewUserFreeTrial: {
|
||||
ID: freeTrialID,
|
||||
Scene: paymentguidemod.SceneHomeNewUserFreeTrial,
|
||||
Style: "BOTTOM_SHEET",
|
||||
Title: "free trial",
|
||||
Action: paymentguidemod.Action{Type: "NONE"},
|
||||
},
|
||||
paymentguidemod.SceneVideoBack: {
|
||||
ID: videoBackID,
|
||||
Scene: paymentguidemod.SceneVideoBack,
|
||||
Style: "BOTTOM_SHEET",
|
||||
Title: "video back",
|
||||
Action: paymentguidemod.Action{Type: "NONE"},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
||||
if resp.Segment != paymentguidemod.SegmentNewNeverPaid {
|
||||
t.Fatalf("segment = %q, want %q", resp.Segment, paymentguidemod.SegmentNewNeverPaid)
|
||||
}
|
||||
freeTrial := resp.Scenes[paymentguidemod.SceneHomeNewUserFreeTrial]
|
||||
if !freeTrial.Enabled || freeTrial.Show == nil || *freeTrial.Show || freeTrial.ConfigID != freeTrialID.Hex() {
|
||||
t.Fatalf("exhausted free-trial scene must retain config with show=false: %+v", freeTrial)
|
||||
}
|
||||
videoBack := resp.Scenes[paymentguidemod.SceneVideoBack]
|
||||
if !videoBack.Enabled || videoBack.Show == nil || !*videoBack.Show || videoBack.ConfigID != videoBackID.Hex() {
|
||||
t.Fatalf("free-trial exhaustion must not suppress other matching scenes: %+v", videoBack)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user