Files
90CNServer/pvf/internal/pvf/batch_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

282 lines
8.7 KiB
Go

package pvf
import (
"strings"
"testing"
)
func applyStructuredForTest(t *testing.T, text string, operations ...StructuredBatchOperation) string {
t.Helper()
a := New()
index, err := a.AddFileText("test/item.equ", text, TypeScript)
if err != nil {
t.Fatal(err)
}
raw, err := a.RawBytes(index)
if err != nil {
t.Fatal(err)
}
result, err := a.TransformStructuredBatch(raw, operations)
if err != nil {
t.Fatal(err)
}
if err := a.SetRawBytes(index, result.Raw()); err != nil {
t.Fatal(err)
}
got, err := a.Text(index)
if err != nil {
t.Fatal(err)
}
return got
}
func TestStructuredBatchSetsAllDuplicateSections(t *testing.T) {
got := applyStructuredForTest(t,
"[price]\n100\n[name]\n`one`\n[price]\n200",
StructuredBatchOperation{Kind: "set", Section: "price", TokenIndex: 0, Value: "500"},
)
if strings.Count(got, "500") != 2 || strings.Contains(got, "100") || strings.Contains(got, "200") {
t.Fatalf("updated text = %q", got)
}
}
func TestStructuredBatchSetsNestedSection(t *testing.T) {
got := applyStructuredForTest(t,
"[parent]\n0\n[keep]\n2",
StructuredBatchOperation{
Kind: "set",
Section: "parent",
TokenIndex: 0,
Value: "[child]\n1\n[/child]",
},
)
for _, fragment := range []string{"[parent]", "[child]", "1", "[/child]", "[keep]"} {
if !strings.Contains(got, fragment) {
t.Fatalf("nested set result missing %q: %q", fragment, got)
}
}
if strings.Contains(got, "\n0\n") ||
strings.Index(got, "[parent]") > strings.Index(got, "[child]") ||
strings.Index(got, "[/child]") > strings.Index(got, "[keep]") {
t.Fatalf("nested set result is invalid: %q", got)
}
}
func TestStructuredBatchSetMissingSectionCanInsert(t *testing.T) {
got := applyStructuredForTest(t,
"[name]\n`one`",
StructuredBatchOperation{
Kind: "set",
Section: "new section",
TokenIndex: 0,
Value: "[child]\n1\n[/child]",
CreateIfMissing: true,
HasEndTag: true,
},
)
for _, fragment := range []string{"[new section]", "[child]", "1", "[/child]", "[/new section]"} {
if !strings.Contains(got, fragment) {
t.Fatalf("missing-section set result missing %q: %q", fragment, got)
}
}
if strings.Index(got, "[name]") > strings.Index(got, "[new section]") {
t.Fatalf("new section was not appended: %q", got)
}
}
func TestStructuredBatchSetAllValuesWarnsOnCountMismatch(t *testing.T) {
a := New()
index, err := a.AddFileText("test/item.equ", "[values]\n1\n2\n[keep]\n3", TypeScript)
if err != nil {
t.Fatal(err)
}
raw, err := a.RawBytes(index)
if err != nil {
t.Fatal(err)
}
result, err := a.TransformStructuredBatch(raw, []StructuredBatchOperation{{
Kind: "set", Section: "values", TokenIndex: -1, Value: "10",
}})
if err != nil {
t.Fatal(err)
}
if !result.Changed() || len(result.Warnings()) != 1 || !strings.Contains(result.Warnings()[0], "数量") {
t.Fatalf("all-value set result = %#v", result)
}
if err := a.SetRawBytes(index, result.Raw()); err != nil {
t.Fatal(err)
}
got, err := a.Text(index)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(got, "10") || strings.Contains(got, "\n1\n") || strings.Contains(got, "\n2\n") {
t.Fatalf("all-value set text = %q", got)
}
typeResult, err := a.TransformStructuredBatch(raw, []StructuredBatchOperation{{
Kind: "set", Section: "values", TokenIndex: -1, Value: "10\n`new`",
}})
if err != nil {
t.Fatal(err)
}
if len(typeResult.Warnings()) != 1 || !strings.Contains(typeResult.Warnings()[0], "类型不一致") {
t.Fatalf("type-mismatch warning = %#v", typeResult.Warnings())
}
}
func TestStructuredBatchNumericOperators(t *testing.T) {
got := applyStructuredForTest(t,
"[price]\n100\n[rate]\n1.25",
StructuredBatchOperation{Kind: "number", Section: "price", TokenIndex: 0, Operator: "+%", Operand: "10"},
StructuredBatchOperation{Kind: "number", Section: "rate", TokenIndex: 0, Operator: "round", Operand: "1"},
)
if !strings.Contains(got, "110") || !strings.Contains(got, "1.3") {
t.Fatalf("numeric result = %q", got)
}
}
func TestStructuredBatchNumericBoundariesAndDivideByZero(t *testing.T) {
got := applyStructuredForTest(t,
"[value]\n20\n[min]\n8\n[max]\n3\n[clamp]\n50",
StructuredBatchOperation{Kind: "number", Section: "value", TokenIndex: 0, Operator: "min", Operand: "10"},
StructuredBatchOperation{Kind: "number", Section: "min", TokenIndex: 0, Operator: "max", Operand: "12"},
StructuredBatchOperation{Kind: "number", Section: "clamp", TokenIndex: 0, Operator: "clamp", Operand: "0", OperandEnd: "25"},
)
if !strings.Contains(got, "10") || !strings.Contains(got, "12") || !strings.Contains(got, "25") {
t.Fatalf("boundary result = %q", got)
}
a := New()
index, err := a.AddFileText("test/item.equ", "[value]\n20", TypeScript)
if err != nil {
t.Fatal(err)
}
raw, err := a.RawBytes(index)
if err != nil {
t.Fatal(err)
}
if _, err := a.TransformStructuredBatch(raw, []StructuredBatchOperation{{
Kind: "number", Section: "value", TokenIndex: 0, Operator: "÷", Operand: "0",
}}); err == nil {
t.Fatal("expected divide by zero error")
}
}
func TestStructuredBatchDeletePairedAndFlatSections(t *testing.T) {
got := applyStructuredForTest(t,
"[parent]\n1\n[child]\n2\n[/child]\n3\n[/parent]\n[keep]\n4",
StructuredBatchOperation{Kind: "delete", Section: "parent"},
)
if strings.Contains(got, "parent") || strings.Contains(got, "child") || !strings.Contains(got, "[keep]") {
t.Fatalf("paired delete result = %q", got)
}
got = applyStructuredForTest(t,
"[remove]\n1\n[name]\n`keep`\n[remove]\n2",
StructuredBatchOperation{Kind: "delete", Section: "remove"},
)
if strings.Contains(got, "remove") || !strings.Contains(got, "keep") {
t.Fatalf("flat delete result = %q", got)
}
}
func TestStructuredBatchInsertAfterAnchorAndAtEnd(t *testing.T) {
got := applyStructuredForTest(t,
"[name]\n`one`\n[price]\n100",
StructuredBatchOperation{Kind: "insert", Section: "custom flag", AnchorSection: "name", Value: "1", HasEndTag: true},
)
if !strings.Contains(got, "[custom flag]") || !strings.Contains(got, "[/custom flag]") {
t.Fatalf("insert after anchor result = %q", got)
}
if strings.Index(got, "[custom flag]") < strings.Index(got, "[name]") {
t.Fatalf("inserted section appeared before anchor: %q", got)
}
got = applyStructuredForTest(t,
"[name]\n`one`",
StructuredBatchOperation{Kind: "insert", Section: "tail", Value: "`done`"},
)
if !strings.HasSuffix(strings.TrimSpace(got), "`done`") {
t.Fatalf("append result = %q", got)
}
}
func TestStructuredBatchInsertNestedSection(t *testing.T) {
got := applyStructuredForTest(t,
"[name]\n`one`",
StructuredBatchOperation{
Kind: "insert",
Section: "parent",
AnchorSection: "name",
Value: "[child]\n1\n[/child]\n`tail`",
HasEndTag: true,
},
)
for _, fragment := range []string{"[parent]", "[child]", "1", "[/child]", "`tail`", "[/parent]"} {
if !strings.Contains(got, fragment) {
t.Fatalf("nested insert result missing %q: %q", fragment, got)
}
}
if strings.Index(got, "[parent]") > strings.Index(got, "[child]") ||
strings.Index(got, "[/child]") > strings.Index(got, "[/parent]") {
t.Fatalf("nested section order is invalid: %q", got)
}
}
func TestStructuredBatchRejectsMalformedAndNonNumeric(t *testing.T) {
a := New()
index, err := a.AddFileText("test/item.equ", "[price]\n`100`", TypeScript)
if err != nil {
t.Fatal(err)
}
raw, err := a.RawBytes(index)
if err != nil {
t.Fatal(err)
}
if _, err := a.TransformStructuredBatch(raw, []StructuredBatchOperation{{
Kind: "number", Section: "price", TokenIndex: 0, Operator: "+", Operand: "1",
}}); err == nil {
t.Fatal("expected non-numeric error")
}
if _, err := a.TransformStructuredBatch([]byte{3, 1}, nil); err == nil {
t.Fatal("expected malformed payload error")
}
}
func TestBatchCloneAndCommitDoNotMutatePreviewSource(t *testing.T) {
a := New()
index, err := a.AddFileText("test/item.equ", "[price]\n100", TypeScript)
if err != nil {
t.Fatal(err)
}
original, err := a.Text(index)
if err != nil {
t.Fatal(err)
}
stage := a.CloneForBatch()
raw, err := stage.RawBytes(index)
if err != nil {
t.Fatal(err)
}
result, err := stage.TransformStructuredBatch(raw, []StructuredBatchOperation{{
Kind: "set", Section: "price", TokenIndex: 0, Value: "500",
}})
if err != nil {
t.Fatal(err)
}
if err := stage.SetRawBytes(index, result.Raw()); err != nil {
t.Fatal(err)
}
previewSource, err := a.Text(index)
if err != nil || previewSource != original {
t.Fatalf("preview mutated source: text=%q err=%v", previewSource, err)
}
if err := a.CommitBatch(stage, map[int32]struct{}{index: {}}); err != nil {
t.Fatal(err)
}
committed, err := a.Text(index)
if err != nil || !strings.Contains(committed, "500") {
t.Fatalf("committed text = %q err=%v", committed, err)
}
}