mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-21 23:22:34 +01:00
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)
197 lines
5.2 KiB
Go
197 lines
5.2 KiB
Go
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")
|
|
}
|
|
}
|