398 lines
14 KiB
Go
398 lines
14 KiB
Go
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)
|
|
}
|
|
}
|