352 lines
9.8 KiB
Go
352 lines
9.8 KiB
Go
package apppopup
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"chatapp3-golang/internal/config"
|
|
"chatapp3-golang/internal/model"
|
|
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func TestQueryEntryPopupsReturnsConfigWithoutUserCooldown(t *testing.T) {
|
|
db := newTestDB(t)
|
|
now := time.Date(2026, 5, 7, 12, 0, 0, 0, time.UTC)
|
|
mustCreatePopupConfig(t, db, model.AppPopupConfig{
|
|
ID: 1001,
|
|
SysOrigin: "LIKEI",
|
|
PopupKey: "invite",
|
|
Name: "邀请活动弹窗",
|
|
Scene: defaultScene,
|
|
Enabled: true,
|
|
LimitDays: 7,
|
|
Priority: 100,
|
|
Version: 3,
|
|
ContentJSON: `{"title":"Invite friends","image":"https://cdn.example.com/invite.png"}`,
|
|
JumpType: "H5",
|
|
JumpURL: "/h5/app-invite/landing.html",
|
|
})
|
|
mustCreatePopupConfig(t, db, model.AppPopupConfig{
|
|
ID: 1002,
|
|
SysOrigin: "LIKEI",
|
|
PopupKey: "game",
|
|
Name: "游戏引导弹窗",
|
|
Scene: defaultScene,
|
|
Enabled: true,
|
|
LimitDays: 0,
|
|
Priority: 90,
|
|
Version: 1,
|
|
ContentJSON: `{"title":"Play games","image":"https://cdn.example.com/game.png"}`,
|
|
JumpType: "APP_ROUTE",
|
|
JumpURL: "/game-center",
|
|
})
|
|
|
|
service := NewService(config.Config{}, db, nil)
|
|
service.now = func() time.Time { return now }
|
|
resp, err := service.QueryEntryPopups(context.Background(), AuthUser{
|
|
UserID: 123,
|
|
SysOrigin: "LIKEI",
|
|
}, EntryQueryRequest{Scene: defaultScene, Platform: "ios", AppVersion: "3.12.0"})
|
|
if err != nil {
|
|
t.Fatalf("QueryEntryPopups() error = %v", err)
|
|
}
|
|
|
|
if len(resp.Queue) != 2 || resp.Queue[0] != "invite" || resp.Queue[1] != "game" {
|
|
t.Fatalf("queue = %#v, want [invite game]", resp.Queue)
|
|
}
|
|
invite := resp.Items["invite"]
|
|
if !invite.Value || invite.Limit != 7 || invite.PopupID != 1001 || invite.Version != 3 {
|
|
t.Fatalf("invite = %+v, want enabled limit 7 id 1001 version 3", invite)
|
|
}
|
|
if invite.Title != "Invite friends" || invite.Image != "https://cdn.example.com/invite.png" {
|
|
t.Fatalf("invite content = title %q image %q", invite.Title, invite.Image)
|
|
}
|
|
game := resp.Items["game"]
|
|
if !game.Value || game.Limit != 0 {
|
|
t.Fatalf("game = %+v, want enabled limit 0", game)
|
|
}
|
|
}
|
|
|
|
func TestQueryEntryPopupsFiltersByConfigRulesOnly(t *testing.T) {
|
|
db := newTestDB(t)
|
|
now := time.Date(2026, 5, 7, 12, 0, 0, 0, time.UTC)
|
|
future := now.Add(time.Hour)
|
|
past := now.Add(-time.Hour)
|
|
mustCreatePopupConfig(t, db, model.AppPopupConfig{
|
|
ID: 1001,
|
|
SysOrigin: "LIKEI",
|
|
PopupKey: "invite",
|
|
Scene: defaultScene,
|
|
Enabled: false,
|
|
LimitDays: 7,
|
|
Priority: 100,
|
|
Version: 1,
|
|
UserScopeType: "ALL",
|
|
})
|
|
mustCreatePopupConfig(t, db, model.AppPopupConfig{
|
|
ID: 1002,
|
|
SysOrigin: "LIKEI",
|
|
PopupKey: "game",
|
|
Scene: defaultScene,
|
|
Enabled: true,
|
|
LimitDays: 7,
|
|
Priority: 90,
|
|
Version: 1,
|
|
PlatformsJSON: `["android"]`,
|
|
UserScopeType: "ALL",
|
|
})
|
|
mustCreatePopupConfig(t, db, model.AppPopupConfig{
|
|
ID: 1003,
|
|
SysOrigin: "LIKEI",
|
|
PopupKey: "future",
|
|
Scene: defaultScene,
|
|
Enabled: true,
|
|
LimitDays: 7,
|
|
Priority: 80,
|
|
Version: 1,
|
|
StartTime: &future,
|
|
UserScopeType: "ALL",
|
|
})
|
|
mustCreatePopupConfig(t, db, model.AppPopupConfig{
|
|
ID: 1004,
|
|
SysOrigin: "LIKEI",
|
|
PopupKey: "expired",
|
|
Scene: defaultScene,
|
|
Enabled: true,
|
|
LimitDays: 7,
|
|
Priority: 70,
|
|
Version: 1,
|
|
EndTime: &past,
|
|
UserScopeType: "ALL",
|
|
})
|
|
|
|
service := NewService(config.Config{}, db, nil)
|
|
service.now = func() time.Time { return now }
|
|
resp, err := service.QueryEntryPopups(context.Background(), AuthUser{
|
|
UserID: 123,
|
|
SysOrigin: "LIKEI",
|
|
}, EntryQueryRequest{Platform: "ios", AppVersion: "3.12.0"})
|
|
if err != nil {
|
|
t.Fatalf("QueryEntryPopups() error = %v", err)
|
|
}
|
|
|
|
if len(resp.Queue) != 0 {
|
|
t.Fatalf("queue = %#v, want empty", resp.Queue)
|
|
}
|
|
assertReason(t, resp.Items["invite"], reasonDisabled)
|
|
assertReason(t, resp.Items["game"], reasonPlatform)
|
|
assertReason(t, resp.Items["future"], reasonNotStarted)
|
|
assertReason(t, resp.Items["expired"], reasonExpired)
|
|
}
|
|
|
|
func TestQueryEntryPopupsVersionBoundaries(t *testing.T) {
|
|
db := newTestDB(t)
|
|
mustCreatePopupConfig(t, db, model.AppPopupConfig{
|
|
ID: 1001,
|
|
SysOrigin: "LIKEI",
|
|
PopupKey: "invite",
|
|
Scene: defaultScene,
|
|
Enabled: true,
|
|
LimitDays: 7,
|
|
Priority: 100,
|
|
Version: 1,
|
|
MinAppVersion: "3.9.8",
|
|
MaxAppVersion: "3.13.0",
|
|
UserScopeType: "ALL",
|
|
})
|
|
|
|
service := NewService(config.Config{}, db, nil)
|
|
resp, err := service.QueryEntryPopups(context.Background(), AuthUser{
|
|
UserID: 123,
|
|
SysOrigin: "LIKEI",
|
|
}, EntryQueryRequest{AppVersion: "3.12.0"})
|
|
if err != nil {
|
|
t.Fatalf("QueryEntryPopups() error = %v", err)
|
|
}
|
|
if !resp.Items["invite"].Value {
|
|
t.Fatalf("invite = %+v, want value true", resp.Items["invite"])
|
|
}
|
|
|
|
resp, err = service.QueryEntryPopups(context.Background(), AuthUser{
|
|
UserID: 123,
|
|
SysOrigin: "LIKEI",
|
|
}, EntryQueryRequest{AppVersion: "3.8.9"})
|
|
if err != nil {
|
|
t.Fatalf("QueryEntryPopups() error = %v", err)
|
|
}
|
|
assertReason(t, resp.Items["invite"], reasonMinVersion)
|
|
|
|
resp, err = service.QueryEntryPopups(context.Background(), AuthUser{
|
|
UserID: 123,
|
|
SysOrigin: "LIKEI",
|
|
}, EntryQueryRequest{AppVersion: "3.14.0"})
|
|
if err != nil {
|
|
t.Fatalf("QueryEntryPopups() error = %v", err)
|
|
}
|
|
assertReason(t, resp.Items["invite"], reasonMaxVersion)
|
|
}
|
|
|
|
func TestEntryQueryResponseMarshalFlattensPopupKeys(t *testing.T) {
|
|
resp := EntryQueryResponse{
|
|
RequestID: "req-1",
|
|
Queue: []string{"invite"},
|
|
Items: map[string]PopupItem{
|
|
"invite": {Value: true, Limit: 7},
|
|
},
|
|
}
|
|
raw, err := json.Marshal(resp)
|
|
if err != nil {
|
|
t.Fatalf("MarshalJSON() error = %v", err)
|
|
}
|
|
var decoded map[string]any
|
|
if err := json.Unmarshal(raw, &decoded); err != nil {
|
|
t.Fatalf("Unmarshal response error = %v", err)
|
|
}
|
|
if decoded["requestId"] != "req-1" {
|
|
t.Fatalf("requestId = %v, want req-1", decoded["requestId"])
|
|
}
|
|
invite, ok := decoded["invite"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("invite field missing in %s", raw)
|
|
}
|
|
if invite["value"] != true || invite["limit"] != float64(7) {
|
|
t.Fatalf("invite = %#v, want value true limit 7", invite)
|
|
}
|
|
}
|
|
|
|
func TestAdminConfigCRUD(t *testing.T) {
|
|
db := newTestDB(t)
|
|
service := NewService(config.Config{}, db, nil)
|
|
|
|
saved, err := service.SaveAdminConfig(context.Background(), SaveAdminConfigRequest{
|
|
SysOrigin: "LIKEI",
|
|
Scene: defaultScene,
|
|
PopupKey: "invite",
|
|
Name: "邀请弹窗",
|
|
Enabled: true,
|
|
LimitDays: 7,
|
|
Priority: 100,
|
|
Version: 1,
|
|
Title: "Invite friends",
|
|
Image: "https://cdn.example.com/invite.png",
|
|
JumpType: "H5",
|
|
JumpURL: "/h5/app-invite/landing.html",
|
|
Description: "邀请活动首页弹窗",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("SaveAdminConfig() error = %v", err)
|
|
}
|
|
if saved.ID == "" || saved.PopupKey != "invite" || saved.LimitDays != 7 || saved.Description != "邀请活动首页弹窗" {
|
|
t.Fatalf("saved = %+v", saved)
|
|
}
|
|
savedID, err := strconv.ParseInt(saved.ID, 10, 64)
|
|
if err != nil {
|
|
t.Fatalf("parse saved id: %v", err)
|
|
}
|
|
|
|
page, err := service.PageAdminConfigs(context.Background(), "LIKEI", defaultScene, "inv", 1, 20)
|
|
if err != nil {
|
|
t.Fatalf("PageAdminConfigs() error = %v", err)
|
|
}
|
|
if page.Total != 1 || len(page.Records) != 1 {
|
|
t.Fatalf("page = %+v, want one record", page)
|
|
}
|
|
|
|
updated, err := service.SaveAdminConfig(context.Background(), SaveAdminConfigRequest{
|
|
ID: flexibleInt64{value: savedID},
|
|
SysOrigin: "LIKEI",
|
|
Scene: defaultScene,
|
|
PopupKey: "invite",
|
|
Name: "邀请弹窗",
|
|
Enabled: false,
|
|
LimitDays: 0,
|
|
Priority: 90,
|
|
Version: 2,
|
|
Description: "每次进入都弹",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("SaveAdminConfig(update) error = %v", err)
|
|
}
|
|
if updated.Enabled || updated.LimitDays != 0 || updated.Version != 2 {
|
|
t.Fatalf("updated = %+v, want disabled limit 0 version 2", updated)
|
|
}
|
|
|
|
if err := service.DeleteAdminConfig(context.Background(), savedID); err != nil {
|
|
t.Fatalf("DeleteAdminConfig() error = %v", err)
|
|
}
|
|
page, err = service.PageAdminConfigs(context.Background(), "LIKEI", defaultScene, "", 1, 20)
|
|
if err != nil {
|
|
t.Fatalf("PageAdminConfigs(after delete) error = %v", err)
|
|
}
|
|
if page.Total != 0 {
|
|
t.Fatalf("total = %d, want 0", page.Total)
|
|
}
|
|
}
|
|
|
|
func TestAdminConfigRejectsDuplicateKey(t *testing.T) {
|
|
db := newTestDB(t)
|
|
service := NewService(config.Config{}, db, nil)
|
|
_, err := service.SaveAdminConfig(context.Background(), SaveAdminConfigRequest{
|
|
SysOrigin: "LIKEI",
|
|
Scene: defaultScene,
|
|
PopupKey: "invite",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("SaveAdminConfig(first) error = %v", err)
|
|
}
|
|
_, err = service.SaveAdminConfig(context.Background(), SaveAdminConfigRequest{
|
|
SysOrigin: "LIKEI",
|
|
Scene: defaultScene,
|
|
PopupKey: "invite",
|
|
})
|
|
if err == nil {
|
|
t.Fatalf("SaveAdminConfig(duplicate) error = nil, want error")
|
|
}
|
|
}
|
|
|
|
func newTestDB(t *testing.T) *gorm.DB {
|
|
t.Helper()
|
|
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
if err != nil {
|
|
t.Fatalf("open sqlite: %v", err)
|
|
}
|
|
if err := db.AutoMigrate(&model.AppPopupConfig{}); err != nil {
|
|
t.Fatalf("migrate app popup config: %v", err)
|
|
}
|
|
return db
|
|
}
|
|
|
|
func mustCreatePopupConfig(t *testing.T, db *gorm.DB, row model.AppPopupConfig) {
|
|
t.Helper()
|
|
if row.Scene == "" {
|
|
row.Scene = defaultScene
|
|
}
|
|
if row.SysOrigin == "" {
|
|
row.SysOrigin = defaultSysOrigin
|
|
}
|
|
if row.Version == 0 {
|
|
row.Version = 1
|
|
}
|
|
if row.UserScopeType == "" {
|
|
row.UserScopeType = "ALL"
|
|
}
|
|
if err := db.Create(&row).Error; err != nil {
|
|
t.Fatalf("create popup config %s: %v", row.PopupKey, err)
|
|
}
|
|
}
|
|
|
|
func assertReason(t *testing.T, item PopupItem, want string) {
|
|
t.Helper()
|
|
if item.Value {
|
|
t.Fatalf("item = %+v, want value false", item)
|
|
}
|
|
if item.Reason != want {
|
|
t.Fatalf("reason = %q, want %q", item.Reason, want)
|
|
}
|
|
}
|