package appconfig import ( "testing" "time" "hyapp-admin-server/internal/model" ) func TestH5LinkModelFromPayloadValidatesDynamicConfig(t *testing.T) { item, err := h5LinkModelFromPayload(h5LinkPayload{ Key: "host-center.v2", Label: "Host Center", URL: "https://h5.example.com/host", }) if err != nil { t.Fatalf("h5 config should be valid: %v", err) } if item.Group != h5LinkGroup || item.Key != "host-center.v2" || item.Description != "Host Center" || item.Value != "https://h5.example.com/host" { t.Fatalf("h5 config model mismatch: %+v", item) } } func TestH5LinkModelFromPayloadRejectsInvalidKey(t *testing.T) { _, err := h5LinkModelFromPayload(h5LinkPayload{ Key: "host center", Label: "Host Center", URL: "https://h5.example.com/host", }) if err == nil { t.Fatal("expected invalid key to fail") } } func TestBannerModelFromRequestRequiresRoomSmallImageForRoomScope(t *testing.T) { _, err := bannerModelFromRequest("lalu", bannerRequest{ CoverURL: "https://cdn.example.com/banner.png", BannerType: "h5", DisplayScope: bannerDisplayScopeRoom, Param: "https://h5.example.com/activity", Status: bannerStatusActive, Platform: "android", EndsAtMs: time.Now().UTC().Add(time.Hour).UnixMilli(), }) if err == nil { t.Fatal("expected room scope without room small image to fail") } } func TestBannerModelFromRequestKeepsRoomScopeScheduleFields(t *testing.T) { startsAtMs := time.Now().UTC().Add(time.Hour).UnixMilli() endsAtMs := startsAtMs + int64(time.Hour/time.Millisecond) item, err := bannerModelFromRequest("lalu", bannerRequest{ CoverURL: "https://cdn.example.com/banner.png", RoomSmallImageURL: "https://cdn.example.com/banner-small.png", BannerType: "h5", DisplayScopes: []string{bannerDisplayScopeHome, bannerDisplayScopeRoom}, Param: "https://h5.example.com/activity", Status: bannerStatusActive, Platform: "android", StartsAtMs: startsAtMs, EndsAtMs: endsAtMs, }) if err != nil { t.Fatalf("banner model should be valid: %v", err) } if item.DisplayScope != "home,room" || item.RoomSmallImageURL == "" { t.Fatalf("room display fields mismatch: %+v", item) } if item.StartsAtMS != startsAtMs || item.EndsAtMS != endsAtMs || item.Status != bannerStatusActive { t.Fatalf("schedule fields mismatch: %+v", item) } } func TestBannerModelFromRequestExpiresEndedActiveBanner(t *testing.T) { item, err := bannerModelFromRequest("lalu", bannerRequest{ CoverURL: "https://cdn.example.com/banner.png", RoomSmallImageURL: "https://cdn.example.com/ignored.png", BannerType: "app", DisplayScope: bannerDisplayScopeHome, Status: bannerStatusActive, Platform: "ios", EndsAtMs: 1, }) if err != nil { t.Fatalf("banner model should accept ended active banner: %v", err) } if item.Status != bannerStatusExpired { t.Fatalf("ended active banner must become expired: %+v", item) } if item.RoomSmallImageURL != "" { t.Fatalf("non-room banner must not retain room small image: %+v", item) } } func TestAppBannerFromModelReturnsDisplayScopes(t *testing.T) { item := appBannerFromModel(bannerModel("home,room,recharge")) if item.DisplayScope != "home,room,recharge" { t.Fatalf("display scope should keep stored value: %+v", item) } if len(item.DisplayScopes) != 3 || item.DisplayScopes[0] != bannerDisplayScopeHome || item.DisplayScopes[1] != bannerDisplayScopeRoom || item.DisplayScopes[2] != bannerDisplayScopeRecharge { t.Fatalf("display scopes mismatch: %+v", item.DisplayScopes) } } func TestSplashScreenModelFromRequestValidatesH5Config(t *testing.T) { startsAtMs := time.Now().UTC().Add(time.Hour).UnixMilli() endsAtMs := startsAtMs + int64(time.Hour/time.Millisecond) item, err := splashScreenModelFromRequest("lalu", splashScreenRequest{ CoverURL: "https://cdn.example.com/splash.png", SplashType: "h5", Param: "https://h5.example.com/splash", Status: bannerStatusActive, Platform: "android", SortOrder: 7, RegionID: 1001, CountryCode: "us", Description: "launch campaign", DisplayDurationMs: 4200, StartsAtMs: startsAtMs, EndsAtMs: endsAtMs, }) if err != nil { t.Fatalf("splash screen model should be valid: %v", err) } if item.AppCode != "lalu" || item.SplashType != "h5" || item.CountryCode != "US" || item.DisplayDurationMS != 4200 || item.StartsAtMS != startsAtMs || item.EndsAtMS != endsAtMs { t.Fatalf("splash screen model mismatch: %+v", item) } } func TestSplashScreenModelFromRequestDefaultsDisplayDuration(t *testing.T) { item, err := splashScreenModelFromRequest("lalu", splashScreenRequest{ CoverURL: "https://cdn.example.com/splash.png", SplashType: "app", Status: bannerStatusActive, Platform: "android", }) if err != nil { t.Fatalf("splash screen model should accept missing display duration: %v", err) } if item.DisplayDurationMS != splashDefaultDisplayDurationMS { t.Fatalf("display duration should default to %d, got %+v", splashDefaultDisplayDurationMS, item) } } func TestSplashScreenModelFromRequestRejectsNegativeDisplayDuration(t *testing.T) { _, err := splashScreenModelFromRequest("lalu", splashScreenRequest{ CoverURL: "https://cdn.example.com/splash.png", SplashType: "app", Status: bannerStatusActive, Platform: "android", DisplayDurationMs: -1, }) if err == nil { t.Fatal("expected negative display duration to fail") } } func TestSplashScreenModelFromRequestRequiresH5Link(t *testing.T) { _, err := splashScreenModelFromRequest("lalu", splashScreenRequest{ CoverURL: "https://cdn.example.com/splash.png", SplashType: "h5", Status: bannerStatusActive, Platform: "android", }) if err == nil { t.Fatal("expected h5 splash screen without link to fail") } } func TestSplashScreenModelFromRequestExpiresEndedActiveConfig(t *testing.T) { item, err := splashScreenModelFromRequest("lalu", splashScreenRequest{ CoverURL: "https://cdn.example.com/splash.png", SplashType: "app", Status: bannerStatusActive, Platform: "ios", EndsAtMs: 1, }) if err != nil { t.Fatalf("ended app splash screen should be accepted: %v", err) } if item.Status != bannerStatusExpired { t.Fatalf("ended active splash screen must become expired: %+v", item) } } func TestAppSplashScreenFromModelMapsDTOFields(t *testing.T) { item := appSplashScreenFromModel(model.AppSplashScreen{ ID: 9, AppCode: "lalu", CoverURL: "https://cdn.example.com/splash.png", SplashType: "app", Param: "profile", Status: bannerStatusDisabled, Platform: "ios", SortOrder: 3, RegionID: 2002, CountryCode: "BR", Description: "disabled campaign", DisplayDurationMS: 5000, StartsAtMS: 1700000000000, EndsAtMS: 1800000000000, CreatedAtMS: 1690000000000, UpdatedAtMS: 1700000002000, }) if item.ID != 9 || item.SplashType != "app" || item.Param != "profile" || item.RegionID != 2002 || item.DisplayDurationMs != 5000 || item.UpdatedAtMs != 1700000002000 { t.Fatalf("splash screen dto mismatch: %+v", item) } } func TestPopupModelFromRequestValidatesOptionalImageAndPeriod(t *testing.T) { startsAtMs := time.Now().UTC().Add(time.Hour).UnixMilli() endsAtMs := startsAtMs + int64(time.Hour/time.Millisecond) item, err := popupModelFromRequest("lalu", popupRequest{ Code: "new-user-popup", Name: "新人活动", ImageURL: "", JumpType: "h5", JumpURL: "https://h5.example.com/new-user", DisplayPeriodDays: 2, Status: bannerStatusActive, SortOrder: 4, StartsAtMs: startsAtMs, EndsAtMs: endsAtMs, }) if err != nil { t.Fatalf("popup model should be valid: %v", err) } if item.AppCode != "lalu" || item.Code != "new-user-popup" || item.Name != "新人活动" || item.ImageURL != "" || item.JumpType != "h5" || item.DisplayPeriodDays != 2 || item.StartsAtMS != startsAtMs || item.EndsAtMS != endsAtMs { t.Fatalf("popup model mismatch: %+v", item) } } func TestPopupModelFromRequestRejectsInvalidJumpURL(t *testing.T) { _, err := popupModelFromRequest("lalu", popupRequest{ Code: "activity-popup", Name: "活动弹窗", JumpType: "h5", JumpURL: "https://h5.example.com/new user", Status: bannerStatusActive, }) if err == nil { t.Fatal("expected popup h5 jump url with whitespace to fail") } } func TestPopupModelFromRequestRejectsInvalidCode(t *testing.T) { _, err := popupModelFromRequest("lalu", popupRequest{ Code: "activity popup", Name: "活动弹窗", JumpType: "app", JumpURL: "activity/detail", Status: bannerStatusActive, }) if err == nil { t.Fatal("expected popup code with whitespace to fail") } } func TestPopupModelFromRequestExpiresEndedActivePopup(t *testing.T) { item, err := popupModelFromRequest("lalu", popupRequest{ Code: "history-popup", Name: "历史活动", JumpType: "app", JumpURL: "activity/detail", Status: bannerStatusActive, EndsAtMs: 1, }) if err != nil { t.Fatalf("ended popup should be accepted: %v", err) } if item.Status != bannerStatusExpired { t.Fatalf("ended active popup must become expired: %+v", item) } } func TestAppPopupFromModelMapsDTOFields(t *testing.T) { item := appPopupFromModel(model.AppPopup{ ID: 10, AppCode: "lalu", Code: "recharge-popup", Name: "充值活动", ImageURL: "https://cdn.example.com/popup.png", JumpType: "app", JumpURL: "recharge", DisplayPeriodDays: 0, Status: bannerStatusDisabled, SortOrder: 8, StartsAtMS: 1700000000000, EndsAtMS: 1800000000000, CreatedAtMS: 1690000000000, UpdatedAtMS: 1700000002000, }) if item.ID != 10 || item.Code != "recharge-popup" || item.Name != "充值活动" || item.JumpType != "app" || item.JumpURL != "recharge" || item.DisplayPeriodDays != 0 || item.UpdatedAtMs != 1700000002000 { t.Fatalf("popup dto mismatch: %+v", item) } } func bannerModel(displayScope string) model.AppBanner { return model.AppBanner{ ID: 1, AppCode: "lalu", CoverURL: "https://cdn.example.com/banner.png", BannerType: "h5", DisplayScope: displayScope, Param: "https://h5.example.com/activity", Status: bannerStatusActive, Platform: "android", } } func TestExploreTabModelFromRequestValidatesRequiredFields(t *testing.T) { item, err := exploreTabModelFromRequest("lalu", exploreTabRequest{ Enabled: true, H5URL: "https://h5.example.com/explore/live", SortOrder: 3, Tab: "Live", }) if err != nil { t.Fatalf("explore tab should be valid: %v", err) } if item.AppCode != "lalu" || item.Tab != "Live" || item.H5URL != "https://h5.example.com/explore/live" || !item.Enabled || item.SortOrder != 3 { t.Fatalf("explore tab model mismatch: %+v", item) } } func TestExploreTabModelFromRequestRejectsWhitespaceURL(t *testing.T) { _, err := exploreTabModelFromRequest("lalu", exploreTabRequest{ Enabled: true, H5URL: "https://h5.example.com/explore live", Tab: "Live", }) if err == nil { t.Fatal("expected whitespace h5 url to fail") } }