330 lines
11 KiB
Go

package rechargereward
import (
"context"
"fmt"
"testing"
"chatapp3-golang/internal/config"
"chatapp3-golang/internal/integration"
"chatapp3-golang/internal/model"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
func newTestService(t *testing.T, java rechargeRewardJavaGateway) (*Service, *gorm.DB) {
t.Helper()
db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{})
if err != nil {
t.Fatalf("open sqlite: %v", err)
}
if err := db.AutoMigrate(
&model.RechargeRewardConfig{},
&model.RechargeRewardLevel{},
&model.UserBaseInfo{},
); err != nil {
t.Fatalf("auto migrate: %v", err)
}
service := NewService(config.Config{
RechargeReward: config.RechargeRewardConfig{
DefaultSysOrigin: "LIKEI",
Timezone: "Asia/Riyadh",
},
}, db, java)
return service, db
}
func TestGetConfigReturnsEmptyDraftWhenMissing(t *testing.T) {
service, _ := newTestService(t, nil)
resp, err := service.GetConfig(context.Background(), "")
if err != nil {
t.Fatalf("GetConfig() error = %v", err)
}
if resp.Configured || resp.Enabled {
t.Fatalf("GetConfig() configured/enabled = %v/%v, want false/false", resp.Configured, resp.Enabled)
}
if resp.SysOrigin != "LIKEI" || resp.Timezone != "Asia/Riyadh" {
t.Fatalf("GetConfig() sys/timezone = %q/%q", resp.SysOrigin, resp.Timezone)
}
if len(resp.LevelConfigs) != 0 {
t.Fatalf("default level count = %d, want 0", len(resp.LevelConfigs))
}
}
func TestSaveConfigRejectsDuplicateRechargeAmount(t *testing.T) {
service, _ := newTestService(t, nil)
_, err := service.SaveConfig(context.Background(), SaveRechargeRewardConfigRequest{
SysOrigin: "LIKEI",
Enabled: true,
Timezone: "Asia/Riyadh",
LevelConfigs: []RechargeRewardLevelInput{
{Level: 1, RechargeAmount: mustAmount(t, "100"), RewardGold: 1000, Enabled: true},
{Level: 2, RechargeAmount: mustAmount(t, "100.00"), RewardGold: 2000, Enabled: true},
},
})
if err == nil {
t.Fatalf("SaveConfig() error = nil, want duplicate amount error")
}
}
func TestSaveConfigUpsertsBySysOrigin(t *testing.T) {
service, _ := newTestService(t, nil)
ctx := context.Background()
first, err := service.SaveConfig(ctx, SaveRechargeRewardConfigRequest{
SysOrigin: "likei",
Enabled: true,
Timezone: "Asia/Riyadh",
LevelConfigs: []RechargeRewardLevelInput{
{Level: 1, RechargeAmount: mustAmount(t, "100"), RewardGold: 1000, Enabled: true},
},
})
if err != nil {
t.Fatalf("first SaveConfig() error = %v", err)
}
second, err := service.SaveConfig(ctx, SaveRechargeRewardConfigRequest{
SysOrigin: "LIKEI",
Enabled: true,
Timezone: "Asia/Riyadh",
LevelConfigs: []RechargeRewardLevelInput{
{Level: 1, RechargeAmount: mustAmount(t, "200.50"), RewardGold: 3000, Enabled: true},
},
})
if err != nil {
t.Fatalf("second SaveConfig() error = %v", err)
}
if first.ID != second.ID {
t.Fatalf("config ID changed on upsert: first=%d second=%d", first.ID, second.ID)
}
if got := second.LevelConfigs[0]; got.RechargeAmount != "200.50" || got.RewardGold != 3000 {
t.Fatalf("upserted level = %+v", got)
}
}
func TestGetHomeUsesJavaRechargeAndMarksLevels(t *testing.T) {
groupID := int64(9001)
java := rechargeRewardTestGateway{
monthly: integration.DecimalString("150.50"),
total: map[int64][]integration.UserTotalRecharge{
42: {
{UserID: integration.Int64Value(42), Type: "GOOGLE", Amount: integration.DecimalString("5000.00")},
{UserID: integration.Int64Value(42), Type: "APPLE", Amount: integration.DecimalString("998.00")},
},
},
profile: integration.UserProfile{
ID: integration.Int64Value(42),
Account: "12345678",
UserNickname: "nanfangjian",
UserAvatar: "https://example.com/avatar.png",
},
groups: map[int64]integration.RewardGroupDetail{
groupID: {
ID: integration.Int64Value(groupID),
Name: "Gift Box",
RewardConfigList: []integration.RewardGroupItem{
{ID: integration.Int64Value(1), Type: "PROP", Name: "Frame", Quantity: integration.Int64Value(1), Cover: "https://example.com/frame.png"},
},
},
},
}
service, _ := newTestService(t, java)
if _, err := service.SaveConfig(context.Background(), SaveRechargeRewardConfigRequest{
SysOrigin: "LIKEI",
Enabled: true,
Timezone: "Asia/Riyadh",
LevelConfigs: []RechargeRewardLevelInput{
{Level: 1, RechargeAmount: mustAmount(t, "100"), RewardGroupID: ptrFlexibleInt64(groupID), Enabled: true},
{Level: 2, RechargeAmount: mustAmount(t, "200"), RewardGold: 3000, Enabled: true},
},
}); err != nil {
t.Fatalf("SaveConfig() error = %v", err)
}
resp, err := service.GetHome(context.Background(), AuthUser{UserID: 42, SysOrigin: "LIKEI"})
if err != nil {
t.Fatalf("GetHome() error = %v", err)
}
if resp.CurrentRechargeAmount != "150.50" || resp.AccumulatedRechargeAmount != "5998.00" {
t.Fatalf("amounts = current %q accumulated %q", resp.CurrentRechargeAmount, resp.AccumulatedRechargeAmount)
}
if resp.MatchedLevel != 1 || resp.NextLevel != 2 {
t.Fatalf("levels = matched %d next %d", resp.MatchedLevel, resp.NextLevel)
}
if len(resp.LevelConfigs) != 2 || !resp.LevelConfigs[0].Reached || resp.LevelConfigs[1].Reached {
t.Fatalf("level reach state = %+v", resp.LevelConfigs)
}
if len(resp.LevelConfigs[0].RewardItems) != 1 || resp.LevelConfigs[0].RewardItems[0].Name != "Frame" {
t.Fatalf("reward items = %+v", resp.LevelConfigs[0].RewardItems)
}
}
func TestPageClaimRecordsUsesActualReceiveRecords(t *testing.T) {
groupID := int64(9001)
service, db := newTestService(t, rechargeRewardTestGateway{
groups: map[int64]integration.RewardGroupDetail{
groupID: {
ID: integration.Int64Value(groupID),
Name: "Gift Box",
RewardConfigList: []integration.RewardGroupItem{
{ID: integration.Int64Value(1), Type: "PROP", Name: "Frame", Quantity: integration.Int64Value(1)},
},
},
},
})
if err := db.Exec(`
CREATE TABLE user_monthly_recharge_v2 (
user_id INTEGER NOT NULL,
type TEXT NOT NULL,
amount DECIMAL(12,2) NOT NULL,
recharge_date INTEGER NOT NULL,
update_time DATETIME NOT NULL
)`).Error; err != nil {
t.Fatalf("create monthly recharge table: %v", err)
}
if err := db.Exec(`
CREATE TABLE user_activity_reward_receive_record (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
recharge_date INTEGER NOT NULL,
activity_id INTEGER NOT NULL,
create_time DATETIME NOT NULL
)`).Error; err != nil {
t.Fatalf("create receive record table: %v", err)
}
if err := db.Exec(`
CREATE TABLE props_activity_rule_config (
id INTEGER PRIMARY KEY,
sys_origin TEXT NOT NULL,
activity_type TEXT NOT NULL,
resource_group_id INTEGER NOT NULL,
json_data TEXT NOT NULL,
sort INTEGER NOT NULL,
rule_description TEXT NOT NULL,
is_del INTEGER NOT NULL
)`).Error; err != nil {
t.Fatalf("create activity rule table: %v", err)
}
if _, err := service.SaveConfig(context.Background(), SaveRechargeRewardConfigRequest{
SysOrigin: "LIKEI",
Enabled: true,
Timezone: "Asia/Riyadh",
LevelConfigs: []RechargeRewardLevelInput{
{Level: 1, RechargeAmount: mustAmount(t, "100"), RewardGroupID: ptrFlexibleInt64(groupID), Enabled: true},
{Level: 2, RechargeAmount: mustAmount(t, "200"), RewardGold: 3000, Enabled: true},
},
}); err != nil {
t.Fatalf("SaveConfig() error = %v", err)
}
if err := db.Create(&model.UserBaseInfo{
ID: 42,
Account: "1000042",
UserNickname: "Nina",
OriginSys: "LIKEI",
CountryCode: "SA",
CountryName: "Saudi Arabia",
}).Error; err != nil {
t.Fatalf("create user: %v", err)
}
if err := db.Exec(`
INSERT INTO user_monthly_recharge_v2 (user_id, type, amount, recharge_date, update_time) VALUES
(42, 'GOOGLE', 150.50, 202605, '2026-05-07 10:00:00'),
(42, 'MIFA_PAY', 70.00, 202605, '2026-05-07 11:00:00'),
(43, 'GOOGLE', 300.00, 202605, '2026-05-07 12:00:00')
`).Error; err != nil {
t.Fatalf("insert monthly recharge: %v", err)
}
if err := db.Exec(`
INSERT INTO props_activity_rule_config (id, sys_origin, activity_type, resource_group_id, json_data, sort, rule_description, is_del) VALUES
(7001, 'LIKEI', 'CUMULATIVE_RECHARGE', 9001, '{"quantity":100}', 1, 'Gift Box', 0)
`).Error; err != nil {
t.Fatalf("insert activity rule: %v", err)
}
if err := db.Exec(`
INSERT INTO user_activity_reward_receive_record (id, user_id, recharge_date, activity_id, create_time) VALUES
(8001, 42, 202605, 7001, '2026-05-07 10:30:00')
`).Error; err != nil {
t.Fatalf("insert receive record: %v", err)
}
resp, err := service.PageClaimRecords(context.Background(), "LIKEI", "2026-05", 0, 1, 20)
if err != nil {
t.Fatalf("PageClaimRecords() error = %v", err)
}
if resp.Total != 1 || len(resp.Records) != 1 {
t.Fatalf("page result total=%d records=%d", resp.Total, len(resp.Records))
}
record := resp.Records[0]
if record.RechargeAmount != "150.50" {
t.Fatalf("RechargeAmount = %q, want 150.50", record.RechargeAmount)
}
if len(record.ReachedLevels) != 1 || record.ReachedLevels[0].Level != 1 {
t.Fatalf("ReachedLevels = %+v, want only level 1", record.ReachedLevels)
}
if record.ReachedLevels[0].RechargeAmount != "100.00" {
t.Fatalf("ReachedLevels[0].RechargeAmount = %q, want 100.00", record.ReachedLevels[0].RechargeAmount)
}
if len(record.ClaimedRewards) != 1 || len(record.ClaimedRewards[0].RewardItems) != 1 {
t.Fatalf("ClaimedRewards = %+v, want level reward item", record.ClaimedRewards)
}
}
func TestParseAmountCents(t *testing.T) {
tests := map[string]int64{
"100": 10000,
"100.5": 10050,
"100.50": 10050,
"5,998.00": 599800,
}
for input, want := range tests {
got, err := parseAmountCents(input)
if err != nil {
t.Fatalf("parseAmountCents(%q) error = %v", input, err)
}
if got != want {
t.Fatalf("parseAmountCents(%q) = %d, want %d", input, got, want)
}
}
}
func mustAmount(t *testing.T, raw string) flexibleAmount {
t.Helper()
cents, err := parseAmountCents(raw)
if err != nil {
t.Fatalf("parse amount %q: %v", raw, err)
}
return flexibleAmount(cents)
}
func ptrFlexibleInt64(value int64) flexibleOptionalInt64 {
return flexibleOptionalInt64{value: &value}
}
type rechargeRewardTestGateway struct {
monthly integration.DecimalString
total map[int64][]integration.UserTotalRecharge
profile integration.UserProfile
groups map[int64]integration.RewardGroupDetail
}
func (g rechargeRewardTestGateway) GetUserProfile(context.Context, int64) (integration.UserProfile, error) {
return g.profile, nil
}
func (g rechargeRewardTestGateway) GetRewardGroupDetail(_ context.Context, groupID int64) (integration.RewardGroupDetail, error) {
if detail, ok := g.groups[groupID]; ok {
return detail, nil
}
return integration.RewardGroupDetail{}, fmt.Errorf("group not found")
}
func (g rechargeRewardTestGateway) MapUserTotalRecharge(context.Context, []int64) (map[int64][]integration.UserTotalRecharge, error) {
return g.total, nil
}
func (g rechargeRewardTestGateway) GetThisMonthTotalPersonalRecharge(context.Context, int64) (integration.DecimalString, error) {
return g.monthly, nil
}