服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
167 lines
6.2 KiB
Go
167 lines
6.2 KiB
Go
package guild
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
dnfrepo "longheng.io/server/internal/modules/dnf/repository"
|
|
dnfrepomemory "longheng.io/server/internal/modules/dnf/repository/memory"
|
|
)
|
|
|
|
func TestRegistryLifecyclePersistsAndEnforcesAuthority(t *testing.T) {
|
|
ctx := context.Background()
|
|
repositories := dnfrepomemory.NewMemoryGroup()
|
|
now := time.Date(2026, 8, 19, 10, 0, 0, 0, time.Local)
|
|
master := dnfrepo.CharacterRecord{CharacterID: "1", AccountID: "dnf:1", Name: "master", Level: 90}
|
|
applicant := dnfrepo.CharacterRecord{CharacterID: "2", AccountID: "dnf:1", Name: "member", Level: 90}
|
|
|
|
registry := NewRegistry()
|
|
created, err := registry.Create(master, "guild-one", now)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if created.ID != 1 || registry.NameAvailable("GUILD-ONE") {
|
|
t.Fatalf("created=%+v available=%t", created, registry.NameAvailable("GUILD-ONE"))
|
|
}
|
|
if err := registry.Apply(created.ID, applicant, "join", now.Add(time.Minute)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := registry.DecideApplication(created.ID, applicant.CharacterID, applicant.CharacterID, true, now); !errors.Is(err, ErrPermissionDenied) {
|
|
t.Fatalf("unauthorized approval error=%v", err)
|
|
}
|
|
member, err := registry.DecideApplication(created.ID, master.CharacterID, applicant.CharacterID, true, now.Add(2*time.Minute))
|
|
if err != nil || member.Grade != DefaultMemberGrade {
|
|
t.Fatalf("approved member=%+v error=%v", member, err)
|
|
}
|
|
if err := registry.ChangeGrade(master.CharacterID, applicant.CharacterID, 2, now.Add(3*time.Minute)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := registry.Delegate(master.CharacterID, applicant.CharacterID, now.Add(4*time.Minute)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := Save(ctx, repositories.Settings, registry, now); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
reloaded, err := Load(ctx, repositories.Settings)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, _, found := reloaded.GuildForCharacter(applicant.CharacterID)
|
|
if !found || got.MasterCharacterID != applicant.CharacterID || got.MasterName != applicant.Name {
|
|
t.Fatalf("reloaded guild=%+v found=%t", got, found)
|
|
}
|
|
}
|
|
|
|
func TestRegistryInviteReplyAndDailyAttendance(t *testing.T) {
|
|
now := time.Date(2026, 8, 19, 9, 0, 0, 0, time.Local)
|
|
master := dnfrepo.CharacterRecord{CharacterID: "1", AccountID: "dnf:1", Name: "master", Level: 90}
|
|
target := dnfrepo.CharacterRecord{CharacterID: "2", AccountID: "dnf:1", Name: "target", Level: 80}
|
|
registry := NewRegistry()
|
|
guild, err := registry.Create(master, "guild-two", now)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := registry.Invite(master.CharacterID, target, now); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
joined, err := registry.ReplyInvitation(target, true, now.Add(time.Minute))
|
|
if err != nil || joined.ID != guild.ID {
|
|
t.Fatalf("joined=%+v error=%v", joined, err)
|
|
}
|
|
if attended, err := registry.Attend(target.CharacterID, now.Add(2*time.Minute)); err != nil || attended {
|
|
t.Fatalf("same-day attendance=%t error=%v", attended, err)
|
|
}
|
|
if attended, err := registry.Attend(target.CharacterID, now.Add(25*time.Hour)); err != nil || !attended {
|
|
t.Fatalf("next-day attendance=%t error=%v", attended, err)
|
|
}
|
|
}
|
|
|
|
func TestRegistryMemoAuthority(t *testing.T) {
|
|
now := time.Date(2026, 8, 19, 9, 0, 0, 0, time.Local)
|
|
master := dnfrepo.CharacterRecord{CharacterID: "1", AccountID: "dnf:1", Name: "master", Level: 90}
|
|
member := dnfrepo.CharacterRecord{CharacterID: "2", AccountID: "dnf:1", Name: "member", Level: 80}
|
|
registry := NewRegistry()
|
|
guild, err := registry.Create(master, "guild-memo", now)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := registry.Invite(master.CharacterID, member, now); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := registry.ReplyInvitation(member, true, now); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := registry.SetMemo(member.CharacterID, member.CharacterID, "self", now); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := registry.SetMemo(member.CharacterID, master.CharacterID, "forged", now); !errors.Is(err, ErrPermissionDenied) {
|
|
t.Fatalf("member editing master memo error=%v", err)
|
|
}
|
|
if err := registry.SetMemo(master.CharacterID, member.CharacterID, "reviewed", now); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := guild.Members[member.CharacterID].Memo; got != "reviewed" {
|
|
t.Fatalf("member memo=%q", got)
|
|
}
|
|
}
|
|
|
|
func TestRegistryMembershipTransitionsClearApplicationsInEveryGuild(t *testing.T) {
|
|
now := time.Date(2026, 8, 22, 14, 0, 0, 0, time.Local)
|
|
masterOne := dnfrepo.CharacterRecord{CharacterID: "1", AccountID: "dnf:1", Name: "master-one", Level: 90}
|
|
applicant := dnfrepo.CharacterRecord{CharacterID: "2", AccountID: "dnf:1", Name: "applicant", Level: 90}
|
|
masterTwo := dnfrepo.CharacterRecord{CharacterID: "3", AccountID: "dnf:1", Name: "master-two", Level: 90}
|
|
|
|
t.Run("create", func(t *testing.T) {
|
|
registry := NewRegistry()
|
|
guildOne, err := registry.Create(masterOne, "guild-one", now)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
guildTwo, err := registry.Create(masterTwo, "guild-two", now)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := registry.Apply(guildOne.ID, applicant, "one", now); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := registry.Apply(guildTwo.ID, applicant, "two", now); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := registry.Create(applicant, "guild-three", now.Add(time.Minute)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, found := guildOne.Applications[applicant.CharacterID]; found {
|
|
t.Fatal("guild-one retained the creator's stale application")
|
|
}
|
|
if _, found := guildTwo.Applications[applicant.CharacterID]; found {
|
|
t.Fatal("guild-two retained the creator's stale application")
|
|
}
|
|
})
|
|
|
|
t.Run("approve", func(t *testing.T) {
|
|
registry := NewRegistry()
|
|
guildOne, err := registry.Create(masterOne, "guild-one", now)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
guildTwo, err := registry.Create(masterTwo, "guild-two", now)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := registry.Apply(guildOne.ID, applicant, "one", now); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := registry.Apply(guildTwo.ID, applicant, "two", now); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := registry.DecideApplication(guildOne.ID, masterOne.CharacterID, applicant.CharacterID, true, now.Add(time.Minute)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, found := guildTwo.Applications[applicant.CharacterID]; found {
|
|
t.Fatal("another guild retained the new member's stale application")
|
|
}
|
|
})
|
|
}
|