服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
126 lines
3.2 KiB
Go
126 lines
3.2 KiB
Go
package services
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"pvfine/internal/pvf"
|
|
)
|
|
|
|
func newEditorSaveFixture(t *testing.T) (string, *core, int32, []byte) {
|
|
t.Helper()
|
|
|
|
built := pvf.New()
|
|
index, err := built.AddFileText("test/example.equ", "[name]\n`before`", pvf.TypeScript)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var encoded bytes.Buffer
|
|
if err := built.SaveTo(&encoded); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
sourcePath := filepath.Join(t.TempDir(), "source.pvf")
|
|
original := encoded.Bytes()
|
|
if err := os.WriteFile(sourcePath, original, 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
archive, err := pvf.Open(sourcePath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
core := NewCore()
|
|
if err := core.setArchive(archive); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(core.closeArchive)
|
|
return sourcePath, core, index, append([]byte(nil), original...)
|
|
}
|
|
|
|
func TestEditorServiceSaveBacksUpSourceByDefault(t *testing.T) {
|
|
sourcePath, core, index, original := newEditorSaveFixture(t)
|
|
editor := NewEditorService(core)
|
|
if err := editor.SetText(index, "[name]\n`after`\n"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := editor.Save(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
backup, err := os.ReadFile(sourcePath + ".bak")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !bytes.Equal(backup, original) {
|
|
t.Fatal("source backup does not contain the original archive")
|
|
}
|
|
saved, err := pvf.Open(sourcePath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
savedIndex, ok := saved.Find("test/example.equ")
|
|
if !ok {
|
|
t.Fatal("saved file is missing")
|
|
}
|
|
text, err := saved.Text(savedIndex)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(text, "after") {
|
|
t.Fatalf("saved text = %q", text)
|
|
}
|
|
}
|
|
|
|
func TestEditorServiceSaveCanDisableSourceBackup(t *testing.T) {
|
|
sourcePath, core, index, _ := newEditorSaveFixture(t)
|
|
settingsService := newSettingsService(filepath.Join(t.TempDir(), "settings.json"))
|
|
settings := DefaultAppSettings()
|
|
settings.BackupSourceOnSave = false
|
|
if err := settingsService.SaveSettings(settings); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
editor := NewEditorService(core, settingsService)
|
|
if err := editor.SetText(index, "[name]\n`after`\n"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := editor.Save(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := os.Stat(sourcePath + ".bak"); !os.IsNotExist(err) {
|
|
t.Fatalf("source backup exists or stat failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestBackupSourceProgressCountsBytes(t *testing.T) {
|
|
sourcePath := filepath.Join(t.TempDir(), "source.pvf")
|
|
original := bytes.Repeat([]byte("backup data"), 300000)
|
|
if err := os.WriteFile(sourcePath, original, 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var lastCount int64
|
|
var synced bool
|
|
if err := backupSourceFileWithProgress(sourcePath, func(p pvf.SaveProgress) {
|
|
if p.Phase == "backup" {
|
|
if p.Total != int64(len(original)) || p.Done < lastCount || p.Done > p.Total {
|
|
t.Fatalf("invalid backup progress: %+v", p)
|
|
}
|
|
lastCount = p.Done
|
|
} else if p.Phase == "backup-sync" {
|
|
if lastCount != int64(len(original)) {
|
|
t.Fatal("sync preceded full copy")
|
|
}
|
|
synced = true
|
|
}
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
backup, err := os.ReadFile(sourcePath + ".bak")
|
|
if err != nil || !bytes.Equal(backup, original) || !synced {
|
|
t.Fatal("backup progress changed backup content or omitted sync")
|
|
}
|
|
}
|