mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-27 01:53:19 +01:00
fix(channelserver): post-RC1 stabilization sprint
Fix rasta_id=0 overwriting NULL in SaveMercenary, which prevented game state saving for characters without a mercenary (#163). Also includes: - CHANGELOG updated with all 10 post-RC1 commits - Setup wizard fmt.Printf replaced with zap structured logging - technical-debt.md updated with 6 newly completed items - Scenario binary format documented (docs/scenario-format.md) - Tests: alliance nil-guard (#171), handler dispatch table, migrations (sorted/SQL/baseline), setup wizard (10 tests), protbot protocol sign/entrance/channel (23 tests)
This commit is contained in:
196
cmd/protbot/protocol/sign_test.go
Normal file
196
cmd/protbot/protocol/sign_test.go
Normal file
@@ -0,0 +1,196 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
)
|
||||
|
||||
// buildSignResponse constructs a binary sign server response for testing.
|
||||
// Format mirrors Erupe server/signserver/dsgn_resp.go:makeSignResponse.
|
||||
func buildSignResponse(resultCode uint8, tokenID uint32, tokenString [16]byte, timestamp uint32, patchURLs []string, entranceAddr string, chars []testCharEntry) []byte {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(resultCode)
|
||||
bf.WriteUint8(uint8(len(patchURLs))) // patchCount
|
||||
bf.WriteUint8(1) // entranceCount (always 1 in tests)
|
||||
bf.WriteUint8(uint8(len(chars))) // charCount
|
||||
bf.WriteUint32(tokenID)
|
||||
bf.WriteBytes(tokenString[:])
|
||||
bf.WriteUint32(timestamp)
|
||||
|
||||
// Patch server URLs (pascal strings with uint8 length prefix)
|
||||
for _, url := range patchURLs {
|
||||
bf.WriteUint8(uint8(len(url)))
|
||||
bf.WriteBytes([]byte(url))
|
||||
}
|
||||
|
||||
// Entrance server address (pascal string with null terminator included in length)
|
||||
bf.WriteUint8(uint8(len(entranceAddr) + 1))
|
||||
bf.WriteBytes([]byte(entranceAddr))
|
||||
bf.WriteUint8(0) // null terminator
|
||||
|
||||
// Character entries
|
||||
for _, c := range chars {
|
||||
bf.WriteUint32(c.charID)
|
||||
bf.WriteUint16(c.hr)
|
||||
bf.WriteUint16(c.weaponType)
|
||||
bf.WriteUint32(c.lastLogin)
|
||||
bf.WriteUint8(c.isFemale)
|
||||
bf.WriteUint8(c.isNewChar)
|
||||
bf.WriteUint8(c.oldGR)
|
||||
bf.WriteUint8(c.useU16GR)
|
||||
// Name: 16 bytes padded
|
||||
name := make([]byte, 16)
|
||||
copy(name, []byte(c.name))
|
||||
bf.WriteBytes(name)
|
||||
// Desc: 32 bytes padded
|
||||
desc := make([]byte, 32)
|
||||
copy(desc, []byte(c.desc))
|
||||
bf.WriteBytes(desc)
|
||||
bf.WriteUint16(c.gr)
|
||||
bf.WriteUint8(c.unk1)
|
||||
bf.WriteUint8(c.unk2)
|
||||
}
|
||||
|
||||
return bf.Data()
|
||||
}
|
||||
|
||||
type testCharEntry struct {
|
||||
charID uint32
|
||||
hr uint16
|
||||
weaponType uint16
|
||||
lastLogin uint32
|
||||
isFemale uint8
|
||||
isNewChar uint8
|
||||
oldGR uint8
|
||||
useU16GR uint8
|
||||
name string
|
||||
desc string
|
||||
gr uint16
|
||||
unk1 uint8
|
||||
unk2 uint8
|
||||
}
|
||||
|
||||
func TestParseSignResponse_Success(t *testing.T) {
|
||||
tokenID := uint32(12345)
|
||||
var tokenString [16]byte
|
||||
copy(tokenString[:], []byte("ABCDEFGHIJKLMNOP"))
|
||||
timestamp := uint32(1700000000)
|
||||
patchURLs := []string{"http://patch1.example.com", "http://patch2.example.com"}
|
||||
entranceAddr := "192.168.1.1:53310"
|
||||
chars := []testCharEntry{
|
||||
{
|
||||
charID: 100,
|
||||
hr: 999,
|
||||
weaponType: 3,
|
||||
lastLogin: 1699999999,
|
||||
isFemale: 1,
|
||||
isNewChar: 0,
|
||||
oldGR: 50,
|
||||
useU16GR: 1,
|
||||
name: "Hunter",
|
||||
desc: "A brave hunter",
|
||||
gr: 200,
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
},
|
||||
}
|
||||
|
||||
data := buildSignResponse(1, tokenID, tokenString, timestamp, patchURLs, entranceAddr, chars)
|
||||
|
||||
result, err := parseSignResponse(data)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if result.TokenID != tokenID {
|
||||
t.Errorf("TokenID: got %d, want %d", result.TokenID, tokenID)
|
||||
}
|
||||
if result.TokenString != string(tokenString[:]) {
|
||||
t.Errorf("TokenString: got %q, want %q", result.TokenString, string(tokenString[:]))
|
||||
}
|
||||
if result.Timestamp != timestamp {
|
||||
t.Errorf("Timestamp: got %d, want %d", result.Timestamp, timestamp)
|
||||
}
|
||||
if result.EntranceAddr != entranceAddr {
|
||||
t.Errorf("EntranceAddr: got %q, want %q", result.EntranceAddr, entranceAddr)
|
||||
}
|
||||
if len(result.CharIDs) != 1 {
|
||||
t.Fatalf("CharIDs length: got %d, want 1", len(result.CharIDs))
|
||||
}
|
||||
if result.CharIDs[0] != 100 {
|
||||
t.Errorf("CharIDs[0]: got %d, want 100", result.CharIDs[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSignResponse_MultipleCharacters(t *testing.T) {
|
||||
var tokenString [16]byte
|
||||
copy(tokenString[:], []byte("0123456789ABCDEF"))
|
||||
chars := []testCharEntry{
|
||||
{charID: 10, name: "Char1"},
|
||||
{charID: 20, name: "Char2"},
|
||||
{charID: 30, name: "Char3"},
|
||||
}
|
||||
|
||||
data := buildSignResponse(1, 42, tokenString, 0, nil, "127.0.0.1:53310", chars)
|
||||
|
||||
result, err := parseSignResponse(data)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(result.CharIDs) != 3 {
|
||||
t.Fatalf("CharIDs length: got %d, want 3", len(result.CharIDs))
|
||||
}
|
||||
expectedIDs := []uint32{10, 20, 30}
|
||||
for i, want := range expectedIDs {
|
||||
if result.CharIDs[i] != want {
|
||||
t.Errorf("CharIDs[%d]: got %d, want %d", i, result.CharIDs[i], want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSignResponse_NoCharacters(t *testing.T) {
|
||||
var tokenString [16]byte
|
||||
data := buildSignResponse(1, 1, tokenString, 0, nil, "127.0.0.1:53310", nil)
|
||||
|
||||
result, err := parseSignResponse(data)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(result.CharIDs) != 0 {
|
||||
t.Errorf("CharIDs length: got %d, want 0", len(result.CharIDs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSignResponse_FailCode(t *testing.T) {
|
||||
// resultCode=0 means failure; the rest of the data is irrelevant
|
||||
// but we still need the 3 count bytes for the parser to read before checking
|
||||
data := []byte{0} // resultCode = 0
|
||||
|
||||
_, err := parseSignResponse(data)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for failure result code, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSignResponse_FailCode5(t *testing.T) {
|
||||
data := []byte{5} // resultCode = 5 (some other failure code)
|
||||
|
||||
_, err := parseSignResponse(data)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for result code 5, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSignResponse_Empty(t *testing.T) {
|
||||
_, err := parseSignResponse(nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for nil data, got nil")
|
||||
}
|
||||
|
||||
_, err = parseSignResponse([]byte{})
|
||||
if err == nil {
|
||||
t.Fatal("expected error for empty data, got nil")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user