480 lines
15 KiB
Go
480 lines
15 KiB
Go
package vipcardexperimentser
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"91porn-server/app/proto"
|
|
"91porn-server/models/v/productmod"
|
|
"91porn-server/models/v/vipcardexperimentmod"
|
|
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
)
|
|
|
|
func TestApplyVariantOrdersFiltersAndBadgesProducts(t *testing.T) {
|
|
first := primitive.NewObjectID()
|
|
second := primitive.NewObjectID()
|
|
unaffected := primitive.NewObjectID()
|
|
response := proto.ProductRes{List: []proto.ProductList{
|
|
{
|
|
Position: "vip",
|
|
List: []proto.VIPListRes{
|
|
{Product: productmod.Product{ID: first}},
|
|
{Product: productmod.Product{ID: second}},
|
|
},
|
|
},
|
|
{
|
|
Position: "coin",
|
|
List: []proto.VIPListRes{
|
|
{Product: productmod.Product{ID: unaffected}},
|
|
},
|
|
},
|
|
}}
|
|
config := vipcardexperimentmod.VariantConfig{
|
|
DefaultProductID: second,
|
|
ProductIDs: []primitive.ObjectID{second, first},
|
|
ProductBadges: []vipcardexperimentmod.ProductBadge{{
|
|
ProductID: second,
|
|
BadgeType: vipcardexperimentmod.BadgeMostPopular,
|
|
BadgeText: "最受欢迎",
|
|
}},
|
|
}
|
|
if !applyVariant(&response, config) {
|
|
t.Fatal("applyVariant() = false, want true")
|
|
}
|
|
vipProducts := response.List[0].List
|
|
if len(vipProducts) != 2 || vipProducts[0].ID != second || vipProducts[1].ID != first {
|
|
t.Fatalf("VIP product order = %#v", vipProducts)
|
|
}
|
|
if vipProducts[0].Sort != 1 || vipProducts[1].Sort != 2 {
|
|
t.Fatalf("VIP product sort values = %#v", vipProducts)
|
|
}
|
|
if vipProducts[0].BadgeType != vipcardexperimentmod.BadgeMostPopular ||
|
|
vipProducts[0].BadgeText != "最受欢迎" {
|
|
t.Fatalf("badge not applied: %#v", vipProducts[0])
|
|
}
|
|
if len(response.List[1].List) != 1 || response.List[1].List[0].ID != unaffected {
|
|
t.Fatalf("unaffected position changed: %#v", response.List[1].List)
|
|
}
|
|
}
|
|
|
|
func TestApplyVariantOrdersIndependentPositionsByFirstConfiguredProduct(t *testing.T) {
|
|
firstVIP := primitive.NewObjectID()
|
|
secondVIP := primitive.NewObjectID()
|
|
presale := primitive.NewObjectID()
|
|
unaffected := primitive.NewObjectID()
|
|
response := proto.ProductRes{List: []proto.ProductList{
|
|
{
|
|
Position: "会员卡",
|
|
PositionID: "vip",
|
|
List: []proto.VIPListRes{
|
|
{Product: productmod.Product{ID: firstVIP}},
|
|
{Product: productmod.Product{ID: secondVIP}},
|
|
},
|
|
},
|
|
{
|
|
Position: "预售卡",
|
|
PositionID: "presale",
|
|
List: []proto.VIPListRes{
|
|
{Product: productmod.Product{ID: presale}},
|
|
},
|
|
},
|
|
{
|
|
Position: "金币",
|
|
PositionID: "coin",
|
|
List: []proto.VIPListRes{
|
|
{Product: productmod.Product{ID: unaffected}},
|
|
},
|
|
},
|
|
}}
|
|
config := vipcardexperimentmod.VariantConfig{
|
|
ProductIDs: []primitive.ObjectID{presale, secondVIP, firstVIP},
|
|
}
|
|
|
|
if !applyVariant(&response, config) {
|
|
t.Fatal("applyVariant() = false, want true")
|
|
}
|
|
if len(response.List) != 3 {
|
|
t.Fatalf("positions = %#v, want member, presale and coin positions", response.List)
|
|
}
|
|
if response.List[0].Position != "预售卡" || response.List[0].PositionID != "presale" ||
|
|
len(response.List[0].List) != 1 || response.List[0].List[0].ID != presale ||
|
|
response.List[0].List[0].Sort != 1 {
|
|
t.Fatalf("presale position = %#v", response.List[0])
|
|
}
|
|
wantVIPProducts := []primitive.ObjectID{secondVIP, firstVIP}
|
|
if response.List[1].Position != "会员卡" || response.List[1].PositionID != "vip" {
|
|
t.Fatalf("member position = %#v", response.List[1])
|
|
}
|
|
if len(response.List[1].List) != len(wantVIPProducts) {
|
|
t.Fatalf("ordered member products = %#v", response.List[1].List)
|
|
}
|
|
for index, productID := range wantVIPProducts {
|
|
if response.List[1].List[index].ID != productID {
|
|
t.Fatalf("ordered member product %d = %s, want %s", index, response.List[1].List[index].ID, productID)
|
|
}
|
|
if response.List[1].List[index].Sort != index+2 {
|
|
t.Fatalf("ordered member product %d sort = %d, want %d", index, response.List[1].List[index].Sort, index+2)
|
|
}
|
|
}
|
|
if response.List[2].Position != "金币" || len(response.List[2].List) != 1 ||
|
|
response.List[2].List[0].ID != unaffected {
|
|
t.Fatalf("unaffected position = %#v", response.List[2])
|
|
}
|
|
}
|
|
|
|
func TestApplyVariantKeepsInterleavedProductsInOnePosition(t *testing.T) {
|
|
firstVIP := primitive.NewObjectID()
|
|
secondVIP := primitive.NewObjectID()
|
|
presale := primitive.NewObjectID()
|
|
response := proto.ProductRes{List: []proto.ProductList{
|
|
{
|
|
Position: "会员卡",
|
|
PositionID: "vip",
|
|
List: []proto.VIPListRes{
|
|
{Product: productmod.Product{ID: firstVIP}},
|
|
{Product: productmod.Product{ID: secondVIP}},
|
|
},
|
|
},
|
|
{
|
|
Position: "预售卡",
|
|
PositionID: "presale",
|
|
List: []proto.VIPListRes{
|
|
{Product: productmod.Product{ID: presale}},
|
|
},
|
|
},
|
|
}}
|
|
config := vipcardexperimentmod.VariantConfig{
|
|
ProductIDs: []primitive.ObjectID{secondVIP, presale, firstVIP},
|
|
}
|
|
|
|
if !applyVariant(&response, config) {
|
|
t.Fatal("applyVariant() = false, want true")
|
|
}
|
|
if len(response.List) != 2 {
|
|
t.Fatalf("positions = %#v, want one member and one presale position", response.List)
|
|
}
|
|
if response.List[0].PositionID != "vip" || len(response.List[0].List) != 2 ||
|
|
response.List[0].List[0].ID != secondVIP || response.List[0].List[0].Sort != 1 ||
|
|
response.List[0].List[1].ID != firstVIP || response.List[0].List[1].Sort != 3 {
|
|
t.Fatalf("member position = %#v", response.List[0])
|
|
}
|
|
if response.List[1].PositionID != "presale" || len(response.List[1].List) != 1 ||
|
|
response.List[1].List[0].ID != presale || response.List[1].List[0].Sort != 2 {
|
|
t.Fatalf("presale position = %#v", response.List[1])
|
|
}
|
|
}
|
|
|
|
func TestFilterOtherVariantProductsRemovesExclusivePresalePosition(t *testing.T) {
|
|
shared := primitive.NewObjectID()
|
|
currentOnly := primitive.NewObjectID()
|
|
otherOnlyPresale := primitive.NewObjectID()
|
|
unaffected := primitive.NewObjectID()
|
|
response := proto.ProductRes{List: []proto.ProductList{
|
|
{
|
|
Position: "vip",
|
|
List: []proto.VIPListRes{
|
|
{Product: productmod.Product{ID: shared}},
|
|
{Product: productmod.Product{ID: currentOnly}},
|
|
},
|
|
},
|
|
{
|
|
Position: "advance-card",
|
|
List: []proto.VIPListRes{
|
|
{Product: productmod.Product{ID: otherOnlyPresale}},
|
|
},
|
|
},
|
|
{
|
|
Position: "coin",
|
|
List: []proto.VIPListRes{
|
|
{Product: productmod.Product{ID: unaffected}},
|
|
},
|
|
},
|
|
}}
|
|
|
|
filterOtherVariantProducts(
|
|
&response,
|
|
[]primitive.ObjectID{shared, currentOnly},
|
|
[]primitive.ObjectID{shared, otherOnlyPresale},
|
|
)
|
|
|
|
if len(response.List) != 2 {
|
|
t.Fatalf("positions = %#v, want vip and unaffected coin positions", response.List)
|
|
}
|
|
if response.List[0].Position != "vip" || len(response.List[0].List) != 2 {
|
|
t.Fatalf("current variant products changed: %#v", response.List[0])
|
|
}
|
|
if response.List[1].Position != "coin" || len(response.List[1].List) != 1 ||
|
|
response.List[1].List[0].ID != unaffected {
|
|
t.Fatalf("unaffected position changed: %#v", response.List[1])
|
|
}
|
|
}
|
|
|
|
func TestApplyVariantFallsBackWhenDefaultProductIsUnavailable(t *testing.T) {
|
|
available := primitive.NewObjectID()
|
|
missingDefault := primitive.NewObjectID()
|
|
response := proto.ProductRes{List: []proto.ProductList{{
|
|
Position: "vip",
|
|
List: []proto.VIPListRes{{
|
|
Product: productmod.Product{ID: available},
|
|
}},
|
|
}}}
|
|
original := response.List[0].List
|
|
config := vipcardexperimentmod.VariantConfig{
|
|
DefaultProductID: missingDefault,
|
|
ProductIDs: []primitive.ObjectID{available, missingDefault},
|
|
}
|
|
if applyVariant(&response, config) {
|
|
t.Fatal("applyVariant() = true, want false")
|
|
}
|
|
if len(response.List[0].List) != len(original) {
|
|
t.Fatalf("legacy response must remain unchanged: %#v", response.List[0].List)
|
|
}
|
|
}
|
|
|
|
func TestApplyVariantAllowsMissingDefaultProduct(t *testing.T) {
|
|
first := primitive.NewObjectID()
|
|
second := primitive.NewObjectID()
|
|
response := proto.ProductRes{List: []proto.ProductList{{
|
|
Position: "vip",
|
|
List: []proto.VIPListRes{
|
|
{Product: productmod.Product{ID: first}},
|
|
{Product: productmod.Product{ID: second}},
|
|
},
|
|
}}}
|
|
config := vipcardexperimentmod.VariantConfig{
|
|
ProductIDs: []primitive.ObjectID{second, first},
|
|
}
|
|
if !applyVariant(&response, config) {
|
|
t.Fatal("applyVariant() = false, want true")
|
|
}
|
|
products := response.List[0].List
|
|
if len(products) != 2 || products[0].ID != second || products[1].ID != first {
|
|
t.Fatalf("VIP product order = %#v", products)
|
|
}
|
|
}
|
|
|
|
func TestApplyExperimentOmitsMissingDefaultProduct(t *testing.T) {
|
|
productID := primitive.NewObjectID()
|
|
response := proto.ProductRes{List: []proto.ProductList{{
|
|
Position: "vip",
|
|
List: []proto.VIPListRes{{
|
|
Product: productmod.Product{ID: productID},
|
|
}},
|
|
}}}
|
|
config := vipcardexperimentmod.VariantConfig{
|
|
ProductIDs: []primitive.ObjectID{productID},
|
|
}
|
|
|
|
if !applyExperiment(
|
|
&response,
|
|
"optional-default-product",
|
|
vipcardexperimentmod.VariantA,
|
|
config,
|
|
) {
|
|
t.Fatal("applyExperiment() = false, want true")
|
|
}
|
|
if response.DefaultProductID != "" {
|
|
t.Fatalf("DefaultProductID = %q, want empty", response.DefaultProductID)
|
|
}
|
|
}
|
|
|
|
func TestApplyExperimentIncludesUIConfig(t *testing.T) {
|
|
productID := primitive.NewObjectID()
|
|
uiConfig := &vipcardexperimentmod.UIConfig{
|
|
BackgroundImage: "vip-card-skin-a.png",
|
|
BadgeStyles: []vipcardexperimentmod.BadgeStyle{{
|
|
BadgeType: vipcardexperimentmod.BadgeNewUser,
|
|
BackgroundColor: "#F04432",
|
|
TextColor: "#FFFFFF",
|
|
}},
|
|
}
|
|
config := vipcardexperimentmod.VariantConfig{
|
|
SkinKey: "vip-card-skin-a",
|
|
DefaultProductID: productID,
|
|
ProductIDs: []primitive.ObjectID{productID},
|
|
UIConfig: uiConfig,
|
|
}
|
|
response := proto.ProductRes{List: []proto.ProductList{{
|
|
Position: "vip",
|
|
List: []proto.VIPListRes{{
|
|
Product: productmod.Product{ID: productID},
|
|
}},
|
|
}}}
|
|
|
|
if !applyExperiment(
|
|
&response,
|
|
"vip-card-test",
|
|
vipcardexperimentmod.VariantA,
|
|
config,
|
|
) {
|
|
t.Fatal("applyExperiment() = false, want true")
|
|
}
|
|
if response.ExperimentID != "vip-card-test" ||
|
|
response.ExperimentStatus != vipcardexperimentmod.StatusActive ||
|
|
response.Variant != vipcardexperimentmod.VariantA ||
|
|
response.SkinKey != "vip-card-skin-a" ||
|
|
response.DefaultProductID != productID.Hex() {
|
|
t.Fatalf("experiment metadata = %#v", response)
|
|
}
|
|
if response.UIConfig == nil ||
|
|
response.UIConfig.BackgroundImage != "vip-card-skin-a.png" ||
|
|
len(response.UIConfig.BadgeStyles) != 1 {
|
|
t.Fatalf("UI config = %#v", response.UIConfig)
|
|
}
|
|
|
|
uiConfig.BackgroundImage = "changed.png"
|
|
uiConfig.BadgeStyles[0].BackgroundColor = "#000000"
|
|
if response.UIConfig.BackgroundImage != "vip-card-skin-a.png" ||
|
|
response.UIConfig.BadgeStyles[0].BackgroundColor != "#F04432" {
|
|
t.Fatalf("response UI config aliases stored config: %#v", response.UIConfig)
|
|
}
|
|
}
|
|
|
|
func TestApplyExperimentOmitsUIConfigForLegacyExperiment(t *testing.T) {
|
|
productID := primitive.NewObjectID()
|
|
response := proto.ProductRes{List: []proto.ProductList{{
|
|
Position: "vip",
|
|
List: []proto.VIPListRes{{
|
|
Product: productmod.Product{ID: productID},
|
|
}},
|
|
}}}
|
|
config := vipcardexperimentmod.VariantConfig{
|
|
DefaultProductID: productID,
|
|
ProductIDs: []primitive.ObjectID{productID},
|
|
}
|
|
|
|
if !applyExperiment(
|
|
&response,
|
|
"legacy-experiment",
|
|
vipcardexperimentmod.VariantA,
|
|
config,
|
|
) {
|
|
t.Fatal("applyExperiment() = false, want true")
|
|
}
|
|
if response.UIConfig != nil {
|
|
t.Fatalf("legacy UI config = %#v, want nil", response.UIConfig)
|
|
}
|
|
}
|
|
|
|
func TestApplyExperimentFailureDoesNotExposeExperimentUI(t *testing.T) {
|
|
available := primitive.NewObjectID()
|
|
missingDefault := primitive.NewObjectID()
|
|
response := proto.ProductRes{List: []proto.ProductList{{
|
|
Position: "vip",
|
|
List: []proto.VIPListRes{{
|
|
Product: productmod.Product{ID: available},
|
|
}},
|
|
}}}
|
|
config := vipcardexperimentmod.VariantConfig{
|
|
SkinKey: "vip-card-skin-a",
|
|
DefaultProductID: missingDefault,
|
|
ProductIDs: []primitive.ObjectID{available, missingDefault},
|
|
UIConfig: &vipcardexperimentmod.UIConfig{
|
|
BackgroundImage: "vip-card-skin-a.png",
|
|
},
|
|
}
|
|
|
|
if applyExperiment(
|
|
&response,
|
|
"unusable-experiment",
|
|
vipcardexperimentmod.VariantA,
|
|
config,
|
|
) {
|
|
t.Fatal("applyExperiment() = true, want false")
|
|
}
|
|
if response.ExperimentID != "" ||
|
|
response.Variant != "" ||
|
|
response.SkinKey != "" ||
|
|
response.DefaultProductID != "" ||
|
|
response.UIConfig != nil {
|
|
t.Fatalf("failed experiment leaked metadata: %#v", response)
|
|
}
|
|
if len(response.List) != 1 ||
|
|
len(response.List[0].List) != 1 ||
|
|
response.List[0].List[0].ID != available {
|
|
t.Fatalf("failed experiment changed product list: %#v", response.List)
|
|
}
|
|
}
|
|
|
|
func TestApplyToProductResponseSkipsAnonymousUser(t *testing.T) {
|
|
response := proto.ProductRes{}
|
|
if err := ApplyToProductResponse(0, &response, time.Now()); err != nil {
|
|
t.Fatalf("ApplyToProductResponse() error = %v", err)
|
|
}
|
|
if response.ExperimentStatus != vipcardexperimentmod.StatusDisabled ||
|
|
response.ExperimentID != "" || response.UIConfig != nil {
|
|
t.Fatalf("anonymous response contains experiment config: %#v", response)
|
|
}
|
|
}
|
|
|
|
func TestValidateEventChecksStableAssignmentAndProduct(t *testing.T) {
|
|
productID := primitive.NewObjectID()
|
|
experiment := &vipcardexperimentmod.Experiment{
|
|
ExperimentID: "experiment",
|
|
TrafficA: 100,
|
|
TrafficB: 0,
|
|
VariantA: vipcardexperimentmod.VariantConfig{
|
|
DefaultProductID: productID,
|
|
ProductIDs: []primitive.ObjectID{productID},
|
|
},
|
|
}
|
|
now := time.Now()
|
|
input := EventInput{
|
|
EventID: "event-1",
|
|
EventName: vipcardexperimentmod.EventProductImpression,
|
|
SessionID: "session-1",
|
|
OccurredAt: now,
|
|
ExperimentID: experiment.ExperimentID,
|
|
Variant: vipcardexperimentmod.VariantA,
|
|
ProductID: productID,
|
|
}
|
|
event, _, err := validateEvent(
|
|
123,
|
|
input,
|
|
now,
|
|
map[string]*vipcardexperimentmod.Experiment{experiment.ExperimentID: experiment},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("validateEvent() error = %v", err)
|
|
}
|
|
if event.UID != 123 || event.ProductID != productID {
|
|
t.Fatalf("event = %#v", event)
|
|
}
|
|
|
|
input.Variant = vipcardexperimentmod.VariantB
|
|
if _, _, err = validateEvent(
|
|
123,
|
|
input,
|
|
now,
|
|
map[string]*vipcardexperimentmod.Experiment{experiment.ExperimentID: experiment},
|
|
); err == nil {
|
|
t.Fatal("expected mismatched assignment to fail")
|
|
}
|
|
}
|
|
|
|
func TestEventOccurredWithinExperiment(t *testing.T) {
|
|
now := time.Now()
|
|
start := now.Add(-time.Hour)
|
|
end := now.Add(time.Hour)
|
|
experiment := &vipcardexperimentmod.Experiment{
|
|
PublishedAt: start,
|
|
EndAt: &end,
|
|
}
|
|
if !eventOccurredWithinExperiment(experiment, now) {
|
|
t.Fatal("event during experiment must be accepted")
|
|
}
|
|
if eventOccurredWithinExperiment(experiment, start.Add(-10*time.Minute)) {
|
|
t.Fatal("event before experiment must be rejected")
|
|
}
|
|
if eventOccurredWithinExperiment(experiment, end.Add(10*time.Minute)) {
|
|
t.Fatal("event after experiment must be rejected")
|
|
}
|
|
|
|
disabled := now.Add(-time.Minute)
|
|
experiment.DisabledAt = &disabled
|
|
if eventOccurredWithinExperiment(experiment, now.Add(10*time.Minute)) {
|
|
t.Fatal("event after disable must be rejected")
|
|
}
|
|
}
|