Files
90CNServer/server/internal/modules/dnf/dungeontower/owner_test.go
T
hanfuit 645b64fe21 DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
2026-11-04 14:31:55 +08:00

124 lines
5.0 KiB
Go

package dungeontower
import (
"context"
"errors"
"testing"
"time"
dnfdungeonadmission "longheng.io/server/internal/modules/dnf/dungeonadmission"
dnfrepo "longheng.io/server/internal/modules/dnf/repository"
dnfrepomemory "longheng.io/server/internal/modules/dnf/repository/memory"
)
func TestOwnerAdvancesCharacterTowerFloorExactlyOnce(t *testing.T) {
ctx := context.Background()
repositories := dnfrepomemory.NewMemoryGroup()
if err := repositories.Character.Save(ctx, dnfrepo.CharacterRecord{CharacterID: "7", AccountID: "account-1"}); err != nil {
t.Fatal(err)
}
owner, err := NewOwner(repositories)
if err != nil {
t.Fatal(err)
}
route := dnfdungeonadmission.TowerRoute{
Family: dnfdungeonadmission.TowerFamilyGrief,
DungeonIDs: []uint32{5115, 5116, 5117},
}
before, err := owner.Current(ctx, "7", route)
if err != nil || before.Floor != 1 || before.DungeonID != 5115 || before.Completed {
t.Fatalf("before=%+v err=%v", before, err)
}
after, err := owner.Advance(ctx, "7", route, 5115, "clear-1", time.Now())
if err != nil || after.Floor != 2 || after.DungeonID != 5116 || after.Replayed {
t.Fatalf("after=%+v err=%v", after, err)
}
replay, err := owner.Advance(ctx, "7", route, 5115, "clear-1", time.Now())
if err != nil || replay.Floor != 2 || !replay.Replayed {
t.Fatalf("replay=%+v err=%v", replay, err)
}
current, err := owner.Current(ctx, "7", route)
if err != nil || current.Floor != 2 || current.DungeonID != 5116 {
t.Fatalf("current=%+v err=%v", current, err)
}
}
func TestOwnerKeepsTowerFamiliesIndependentAndRejectsStaleClear(t *testing.T) {
ctx := context.Background()
repositories := dnfrepomemory.NewMemoryGroup()
if err := repositories.Character.Save(ctx, dnfrepo.CharacterRecord{CharacterID: "8", AccountID: "account-1"}); err != nil {
t.Fatal(err)
}
owner, err := NewOwner(repositories)
if err != nil {
t.Fatal(err)
}
despair := dnfdungeonadmission.TowerRoute{Family: dnfdungeonadmission.TowerFamilyDespair, DungeonIDs: []uint32{11008, 11009}}
grief := dnfdungeonadmission.TowerRoute{Family: dnfdungeonadmission.TowerFamilyGrief, DungeonIDs: []uint32{5115, 5116}}
if _, err := owner.Advance(ctx, "8", despair, 11008, "despair-clear", time.Now()); err != nil {
t.Fatal(err)
}
if _, err := owner.Advance(ctx, "8", despair, 11008, "different-stale-clear", time.Now()); !errors.Is(err, ErrProgressMismatch) {
t.Fatalf("stale clear error=%v", err)
}
griefCurrent, err := owner.Current(ctx, "8", grief)
if err != nil || griefCurrent.Floor != 1 {
t.Fatalf("grief=%+v err=%v", griefCurrent, err)
}
}
func TestOwnerAcceptsPVFProvedSkippedFloorAndCapsTopLayer(t *testing.T) {
ctx := context.Background()
repositories := dnfrepomemory.NewMemoryGroup()
if err := repositories.Character.Save(ctx, dnfrepo.CharacterRecord{CharacterID: "9", AccountID: "account-1"}); err != nil {
t.Fatal(err)
}
owner, err := NewOwner(repositories)
if err != nil {
t.Fatal(err)
}
route := dnfdungeonadmission.TowerRoute{Family: dnfdungeonadmission.TowerFamilyDespair, DungeonIDs: []uint32{11008, 11009, 11010}}
afterSkip, err := owner.Advance(ctx, "9", route, 11009, "skip-floor-one", time.Now())
if err != nil || afterSkip.Floor != 3 || afterSkip.Completed {
t.Fatalf("after skip=%+v err=%v", afterSkip, err)
}
top, err := owner.Advance(ctx, "9", route, 11010, "top-clear", time.Now())
if err != nil || top.Floor != 3 || !top.Completed {
t.Fatalf("top=%+v err=%v", top, err)
}
}
func TestOwnerCommitsTowerRewardsAndFloorInOneReceipt(t *testing.T) {
ctx := context.Background()
repositories := dnfrepomemory.NewMemoryGroup()
if err := repositories.Character.Save(ctx, dnfrepo.CharacterRecord{CharacterID: "10", AccountID: "account-1"}); err != nil {
t.Fatal(err)
}
owner, err := NewOwner(repositories)
if err != nil {
t.Fatal(err)
}
route := dnfdungeonadmission.TowerRoute{Family: dnfdungeonadmission.TowerFamilyDespair, DungeonIDs: []uint32{11008, 11009}}
command := SettlementCommand{
CharacterID: "10", Route: route, ClearedDungeonID: 11008, Receipt: "tower-settlement-1", MainSlots: 24,
Rewards: []RewardItem{{Stack: dnfrepo.ItemStack{ItemID: 1001, Count: 1, Extra: map[string]string{"pvf_path": "equipment/reward.equ"}}, SlotStart: 3, SlotEnd: 23}},
UpdatedAt: time.Unix(100, 0).UTC(),
}
result, err := owner.CommitSettlement(ctx, command)
if err != nil || result.Snapshot.Floor != 2 || result.Replayed || len(result.ItemSlots) != 1 {
t.Fatalf("result=%+v err=%v", result, err)
}
stack, found := result.Inventory.Slots["0:3"]
if !found || stack.ItemID != 1001 || stack.Count != 1 {
t.Fatalf("committed reward=%+v found=%t", stack, found)
}
replay, err := owner.CommitSettlement(ctx, command)
if err != nil || !replay.Replayed || replay.Snapshot.Floor != 2 || len(replay.ItemSlots) != 1 || replay.ItemSlots[0] != 3 {
t.Fatalf("replay=%+v err=%v", replay, err)
}
inventory, _, err := repositories.Inventory.Load(ctx, "10")
if err != nil || inventory.Slots["0:3"].Count != 1 {
t.Fatalf("inventory after replay=%+v err=%v", inventory, err)
}
}