服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
127 lines
4.7 KiB
Go
127 lines
4.7 KiB
Go
package cerashop
|
|
|
|
import (
|
|
"errors"
|
|
"math"
|
|
"testing"
|
|
|
|
dnfrepo "longheng.io/server/internal/modules/dnf/repository"
|
|
)
|
|
|
|
// 代币券 / 欢乐代币券必须能落成钱包余额,而不是背包里的一件"材料"。
|
|
// 这里守住券的道具号识别与入账语义(溢出、全有或全无)。
|
|
|
|
func TestVoucherKindOfRecognizesBothVouchers(t *testing.T) {
|
|
cases := []struct {
|
|
itemID int64
|
|
want VoucherKind
|
|
}{
|
|
{2681762, VoucherTokenCera},
|
|
{2681917, VoucherHappyTokenCera},
|
|
{0, VoucherNone},
|
|
{2681762 - 1, VoucherNone},
|
|
{2681762 + 1, VoucherNone},
|
|
{2681917 + 1, VoucherNone},
|
|
{100050, VoucherNone},
|
|
}
|
|
for _, testCase := range cases {
|
|
if got := VoucherKindOf(testCase.itemID); got != testCase.want {
|
|
t.Fatalf("VoucherKindOf(%d)=%v want=%v", testCase.itemID, got, testCase.want)
|
|
}
|
|
if IsVoucher(testCase.itemID) != (testCase.want != VoucherNone) {
|
|
t.Fatalf("IsVoucher(%d) 与 VoucherKindOf 不一致", testCase.itemID)
|
|
}
|
|
}
|
|
if VoucherItemID(VoucherTokenCera) != 2681762 || VoucherItemID(VoucherHappyTokenCera) != 2681917 ||
|
|
VoucherItemID(VoucherNone) != 0 {
|
|
t.Fatal("VoucherItemID 与 VoucherKindOf 不是同一套映射")
|
|
}
|
|
if ids := VoucherItemIDs(); len(ids) != 2 || !IsVoucher(ids[0]) || !IsVoucher(ids[1]) {
|
|
t.Fatalf("VoucherItemIDs=%v", ids)
|
|
}
|
|
}
|
|
|
|
func TestCreditVoucherCountsAddsBothWallets(t *testing.T) {
|
|
account := &dnfrepo.AccountRecord{
|
|
AccountID: "account-1",
|
|
Metadata: map[string]string{"account_cera": "700", "account_token_cera": "5"},
|
|
}
|
|
wallet, err := CreditVoucherCounts(account, VoucherCounts{TokenCera: 100, HappyTokenCera: 250})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if wallet.Cera != 700 || wallet.TokenCera != 105 || wallet.HappyTokenCera != 250 {
|
|
t.Fatalf("钱包=%+v want cera=700 token=105 happy=250", wallet)
|
|
}
|
|
// 点券字段绝不能被券的入账碰到。
|
|
if got := Balance(*account); got != 700 {
|
|
t.Fatalf("点券被改动了: %d", got)
|
|
}
|
|
}
|
|
|
|
// 数量为 0 时是空操作,不该写任何键(否则会给账号留下无意义的新元数据)。
|
|
func TestCreditVoucherCountsIgnoresZeroAndNegative(t *testing.T) {
|
|
account := &dnfrepo.AccountRecord{AccountID: "account-1"}
|
|
wallet, err := CreditVoucherCounts(account, VoucherCounts{})
|
|
if err != nil || wallet.TokenCera != 0 || wallet.HappyTokenCera != 0 {
|
|
t.Fatalf("空入账 err=%v wallet=%+v", err, wallet)
|
|
}
|
|
if len(account.Metadata) != 0 {
|
|
t.Fatalf("空入账不该写元数据: %v", account.Metadata)
|
|
}
|
|
// 负数只可能来自上游 bug,按 0 处理而不是把余额扣下去。
|
|
if _, err := CreditVoucherCounts(account, VoucherCounts{TokenCera: -50, HappyTokenCera: -1}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if wallet := WalletBalance(*account); wallet.TokenCera != 0 || wallet.HappyTokenCera != 0 {
|
|
t.Fatalf("负数不该扣余额: %+v", wallet)
|
|
}
|
|
}
|
|
|
|
// 溢出必须整笔拒绝,且**一个字段都不写**(全有或全无)——否则会出现
|
|
// "代币券溢出失败、欢乐代币券却已经进账"的半截状态。
|
|
func TestCreditVoucherCountsRefusesOverflowAtomically(t *testing.T) {
|
|
account := &dnfrepo.AccountRecord{
|
|
AccountID: "account-1",
|
|
Metadata: map[string]string{"account_token_cera": "2147483647"},
|
|
}
|
|
_, err := CreditVoucherCounts(account, VoucherCounts{TokenCera: 1, HappyTokenCera: 100})
|
|
if !errors.Is(err, ErrWalletOverflow) {
|
|
t.Fatalf("代币券溢出应返回 ErrWalletOverflow,实际=%v", err)
|
|
}
|
|
if wallet := WalletBalance(*account); wallet.TokenCera != math.MaxInt32 || wallet.HappyTokenCera != 0 {
|
|
t.Fatalf("溢出时不该写任何字段: %+v", wallet)
|
|
}
|
|
|
|
// 边界:刚好填满到上限是允许的(不是溢出)。
|
|
account = &dnfrepo.AccountRecord{AccountID: "account-1"}
|
|
wallet, err := CreditVoucherCounts(account, VoucherCounts{TokenCera: math.MaxInt32})
|
|
if err != nil || wallet.TokenCera != math.MaxInt32 {
|
|
t.Fatalf("刚好到上限应当允许: err=%v wallet=%+v", err, wallet)
|
|
}
|
|
// 再 +1 就溢出。
|
|
if _, err := CreditVoucherCounts(account, VoucherCounts{TokenCera: 1}); !errors.Is(err, ErrWalletOverflow) {
|
|
t.Fatalf("超过上限应溢出,实际=%v", err)
|
|
}
|
|
}
|
|
|
|
// nil 账号必须报错而不是 panic:调用方多为事务回调,panic 会连累整条连接。
|
|
func TestCreditVoucherCountsRejectsNilAccount(t *testing.T) {
|
|
if _, err := CreditVoucherCounts(nil, VoucherCounts{TokenCera: 1}); err == nil {
|
|
t.Fatal("nil 账号应报错")
|
|
}
|
|
}
|
|
|
|
func TestVoucherCountsAddAndIsZero(t *testing.T) {
|
|
if !(VoucherCounts{}).IsZero() {
|
|
t.Fatal("空 counts 应当是 zero")
|
|
}
|
|
if (VoucherCounts{TokenCera: 1}).IsZero() {
|
|
t.Fatal("非空 counts 不该是 zero")
|
|
}
|
|
sum := VoucherCounts{TokenCera: 3}.Add(VoucherCounts{TokenCera: 4, HappyTokenCera: 5})
|
|
if sum.TokenCera != 7 || sum.HappyTokenCera != 5 {
|
|
t.Fatalf("Add=%+v", sum)
|
|
}
|
|
}
|