Files
bd2/go/internal/server/gamedata/costume_burst_test.go
T
Flechazo 992fa47fd5 feat(all): add game SDK and harden client login and game rules
- Add costume burst upgrades with durable replay and fix permanent SR equipment gacha.
- Add server game.json rules for optional collaboration UR weapons, with packaging and documentation.
- Add GameNames/GameSdk NuGet tooling, readable types, reobfuscation, complete source/PDB navigation and shared-runtime installation.
- Split plugin responsibilities, prefer typeof/nameof, centralize MSBuild settings and ignore local paths; remove the unused LoginUI test project.
- Fix login recovery and TLS through native platform HTTP and a private game relay; read device region directly from the OS time zone.

Validation: go test ./... -count=1, go vet ./..., plugin Release builds, SDK/runtime self-tests, Roslyn embedded-source navigation, gofmt check and git diff --check.
2026-10-03 23:04:32 +08:00

120 lines
3.5 KiB
Go

package gamedata
import (
"database/sql"
"os"
"strings"
"testing"
)
func TestCostumeBurstDesignAndUpgradeRule(t *testing.T) {
db, err := sql.Open("sqlite", ":memory:")
if err != nil {
t.Fatal(err)
}
defer db.Close()
if _, err := db.Exec(`CREATE TABLE CostumeBurstTable (groupId INTEGER,id INTEGER,ProtoBuf BLOB,PRIMARY KEY(groupId,id))`); err != nil {
t.Fatal(err)
}
for level := 1; level <= 3; level++ {
raw := burstProto(8, 42, 9, uint64(level), 10, 60, 11, 710, 12, 8)
if _, err := db.Exec(`INSERT INTO CostumeBurstTable(groupId,id,ProtoBuf) VALUES(?,?,?)`, 42, level, raw); err != nil {
t.Fatal(err)
}
}
design, err := loadCostumeBurstDesign(db)
if err != nil {
t.Fatal(err)
}
rule, err := design.UpgradeRule(42, 1)
if err != nil {
t.Fatal(err)
}
if rule.NextLevel != 2 || rule.MaxLevel != 3 || len(rule.Costs) != 1 || rule.Costs[0] != (PromotionCost{Type: 8, ID: 710, Count: 60}) {
t.Fatalf("unexpected rule: %+v", rule)
}
if _, err := design.UpgradeRule(42, 3); err == nil {
t.Fatal("expected max-level error")
}
}
func TestCostumeBurstRejectsInvalidIdentityCostAndGap(t *testing.T) {
cases := []struct {
name string
raw []byte
rows [][2]int
}{
{"identity", burstProto(8, 99, 9, 1, 10, 1, 11, 710, 12, 8), [][2]int{{42, 1}}},
{"zero-count", burstProto(8, 42, 9, 1, 10, 0, 11, 710, 12, 8), [][2]int{{42, 1}}},
{"gold-id", burstProto(8, 42, 9, 1, 10, 1, 11, 710, 12, 4), [][2]int{{42, 1}}},
{"gap", burstProto(8, 42, 9, 2, 10, 1, 11, 710, 12, 8), [][2]int{{42, 2}}},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
db, err := sql.Open("sqlite", ":memory:")
if err != nil {
t.Fatal(err)
}
defer db.Close()
if _, err := db.Exec(`CREATE TABLE CostumeBurstTable (groupId INTEGER,id INTEGER,ProtoBuf BLOB,PRIMARY KEY(groupId,id))`); err != nil {
t.Fatal(err)
}
for _, row := range tc.rows {
if _, err := db.Exec(`INSERT INTO CostumeBurstTable(groupId,id,ProtoBuf) VALUES(?,?,?)`, row[0], row[1], tc.raw); err != nil {
t.Fatal(err)
}
}
if _, err := loadCostumeBurstDesign(db); err == nil || !strings.Contains(err.Error(), "costume burst") {
t.Fatalf("expected costume burst validation error, got %v", err)
}
})
}
}
func TestCostumeBurstAgainstInstalledCurrentVersion(t *testing.T) {
root := os.Getenv("BD2_REAL_GAMEDATA")
if root == "" {
t.Skip("set BD2_REAL_GAMEDATA for installed GameData integration test")
}
design, err := LoadCostumeBurstDesign(root, "20260923193640")
if err != nil {
t.Fatal(err)
}
if len(design.Levels) != 56 {
t.Fatalf("installed burst groups=%d, want 56", len(design.Levels))
}
rows := 0
for _, levels := range design.Levels {
rows += len(levels)
}
if rows != 168 {
t.Fatalf("installed burst rows=%d, want 168", rows)
}
rule, err := design.UpgradeRule(4202, 0)
if err != nil {
t.Fatal(err)
}
want := []PromotionCost{{Type: 4, Count: 1_500_000}, {Type: 8, ID: 710, Count: 60}}
if rule.NextLevel != 1 || rule.MaxLevel != 3 || len(rule.Costs) != len(want) || rule.Costs[0] != want[0] || rule.Costs[1] != want[1] {
t.Fatalf("installed rule=%+v", rule)
}
}
// burstProto builds the small varint-only fields used by the loader tests.
func burstProto(fields ...uint64) []byte {
if len(fields)%2 != 0 {
panic("burstProto requires field/value pairs")
}
var out []byte
for i := 0; i < len(fields); i += 2 {
field, value := fields[i], fields[i+1]
out = append(out, byte(field<<3))
for value >= 0x80 {
out = append(out, byte(value)|0x80)
value >>= 7
}
out = append(out, byte(value))
}
return out
}