2026-05-08 18:55:47 +08:00

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)
}
}