Files
Erupe/cmd/protbot/protocol/sign_test.go
Houmgaor 03adb21e99 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)
2026-03-05 16:39:15 +01:00

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")
}
}