package structs import ( "os" "path/filepath" "testing" ) // TestParseLoginRequest tests parsing the LoginRequest XML func TestParseLoginRequest(t *testing.T) { // Create a temporary directory with test XML tmpDir := t.TempDir() xmlDir := filepath.Join(tmpDir, "xml", "login") os.MkdirAll(xmlDir, 0755) // Write test LoginRequest XML loginXML := ` ` xmlFile := filepath.Join(xmlDir, "LoginRequest.xml") if err := os.WriteFile(xmlFile, []byte(loginXML), 0644); err != nil { t.Fatalf("Failed to write test XML: %v", err) } // Create parser and load file parser := NewParser(tmpDir) if err := parser.LoadFile(xmlFile); err != nil { t.Fatalf("Failed to load XML: %v", err) } // Check packet was loaded def, exists := parser.GetPacket("LoginRequest") if !exists { t.Fatal("LoginRequest packet not found") } if def.IsSubstruct { t.Error("LoginRequest should not be a substruct") } // Check versions if len(def.Versions) != 2 { t.Errorf("Expected 2 versions, got %d", len(def.Versions)) } // Check version 1 fields if def.Versions[0].Version != 1 { t.Errorf("Expected version 1, got %d", def.Versions[0].Version) } if len(def.Versions[0].Fields) != 7 { t.Errorf("Expected 7 fields in version 1, got %d", len(def.Versions[0].Fields)) } // Check specific fields field := def.Versions[0].Fields[2] if field.Name != "username" { t.Errorf("Expected field name 'username', got '%s'", field.Name) } if field.Type != FieldTypeString16 { t.Errorf("Expected field type String16, got %v", field.Type) } // Check version 562 if def.Versions[1].Version != 562 { t.Errorf("Expected version 562, got %d", def.Versions[1].Version) } // Check array field arrayField := def.Versions[1].Fields[4] if arrayField.Name != "unknown2" { t.Errorf("Expected field name 'unknown2', got '%s'", arrayField.Name) } if arrayField.Size != 8 { t.Errorf("Expected size 8, got %d", arrayField.Size) } } // TestPacketStruct tests packet struct serialization/deserialization func TestPacketStruct(t *testing.T) { // Create a simple packet version for testing version := &PacketVersion{ Version: 1, Fields: []Field{ {Name: "id", Type: FieldTypeUInt32}, {Name: "name", Type: FieldTypeString16}, {Name: "level", Type: FieldTypeUInt8}, {Name: "x", Type: FieldTypeFloat}, {Name: "y", Type: FieldTypeFloat}, {Name: "z", Type: FieldTypeFloat}, }, } // Create packet struct ps := NewPacketStruct(version) // Set values ps.Set("id", uint32(12345)) ps.Set("name", "TestPlayer") ps.Set("level", uint8(50)) ps.Set("x", float32(100.5)) ps.Set("y", float32(200.5)) ps.Set("z", float32(300.5)) // Serialize data, err := ps.Serialize() if err != nil { t.Fatalf("Failed to serialize: %v", err) } // Create new packet struct and deserialize ps2 := NewPacketStruct(version) if err := ps2.Deserialize(data); err != nil { t.Fatalf("Failed to deserialize: %v", err) } // Check values if val, _ := ps2.Get("id"); val.(uint32) != 12345 { t.Errorf("Expected id 12345, got %v", val) } if val, _ := ps2.Get("name"); val.(string) != "TestPlayer" { t.Errorf("Expected name 'TestPlayer', got %v", val) } if val, _ := ps2.Get("level"); val.(uint8) != 50 { t.Errorf("Expected level 50, got %v", val) } if val, _ := ps2.Get("x"); val.(float32) != 100.5 { t.Errorf("Expected x 100.5, got %v", val) } } // TestConfigReader tests the ConfigReader interface func TestConfigReader(t *testing.T) { // Create temporary directory with test XML tmpDir := t.TempDir() structsDir := filepath.Join(tmpDir, "structs", "xml", "test") os.MkdirAll(structsDir, 0755) // Write test packet XML testXML := ` ` xmlFile := filepath.Join(structsDir, "TestPacket.xml") if err := os.WriteFile(xmlFile, []byte(testXML), 0644); err != nil { t.Fatalf("Failed to write test XML: %v", err) } // Create config reader cr := NewConfigReader(tmpDir) if err := cr.LoadFiles([]string{xmlFile}); err != nil { t.Fatalf("Failed to load structs: %v", err) } // Test GetStruct with version selection tests := []struct { version uint16 expectedFields int }{ {1, 2}, // Exact match {50, 2}, // Should use version 1 {100, 3}, // Exact match {150, 3}, // Should use version 100 {200, 4}, // Exact match {300, 4}, // Should use version 200 } for _, test := range tests { ps, err := cr.GetStruct("TestPacket", test.version) if err != nil { t.Errorf("Failed to get struct for version %d: %v", test.version, err) continue } if len(ps.definition.Fields) != test.expectedFields { t.Errorf("Version %d: expected %d fields, got %d", test.version, test.expectedFields, len(ps.definition.Fields)) } } // Test GetStructByVersion (exact match only) ps, err := cr.GetStructByVersion("TestPacket", 100) if err != nil { t.Errorf("Failed to get struct by exact version: %v", err) } if ps != nil && len(ps.definition.Fields) != 3 { t.Errorf("Expected 3 fields for version 100, got %d", len(ps.definition.Fields)) } // This should fail (no exact match) _, err = cr.GetStructByVersion("TestPacket", 150) if err == nil { t.Error("Expected error for non-existent exact version 150") } // Test GetStructVersion version, err := cr.GetStructVersion("TestPacket", 150) if err != nil { t.Errorf("Failed to get struct version: %v", err) } if version != 100 { t.Errorf("Expected version 100 for client version 150, got %d", version) } } // TestStringFields tests various string field types func TestStringFields(t *testing.T) { version := &PacketVersion{ Version: 1, Fields: []Field{ {Name: "str8", Type: FieldTypeString8}, {Name: "str16", Type: FieldTypeString16}, {Name: "str32", Type: FieldTypeString32}, {Name: "char_fixed", Type: FieldTypeChar, Size: 10}, {Name: "char_null", Type: FieldTypeChar, Size: 0}, }, } ps := NewPacketStruct(version) // Set string values ps.Set("str8", "Short") ps.Set("str16", "Medium length string") ps.Set("str32", "This is a much longer string that might be used for descriptions") ps.Set("char_fixed", "Fixed") ps.Set("char_null", "Null-term") // Serialize data, err := ps.Serialize() if err != nil { t.Fatalf("Failed to serialize: %v", err) } // Deserialize ps2 := NewPacketStruct(version) if err := ps2.Deserialize(data); err != nil { t.Fatalf("Failed to deserialize: %v", err) } // Verify strings if val, _ := ps2.Get("str8"); val.(string) != "Short" { t.Errorf("str8 mismatch: got %v", val) } if val, _ := ps2.Get("str16"); val.(string) != "Medium length string" { t.Errorf("str16 mismatch: got %v", val) } if val, _ := ps2.Get("str32"); val.(string) != "This is a much longer string that might be used for descriptions" { t.Errorf("str32 mismatch: got %v", val) } if val, _ := ps2.Get("char_fixed"); val.(string) != "Fixed" { t.Errorf("char_fixed mismatch: got %v", val) } if val, _ := ps2.Get("char_null"); val.(string) != "Null-term" { t.Errorf("char_null mismatch: got %v", val) } } // TestConditionalFields tests fields with if-set/if-not-set conditions func TestConditionalFields(t *testing.T) { version := &PacketVersion{ Version: 1, Fields: []Field{ {Name: "hasData", Type: FieldTypeUInt8}, {Name: "data", Type: FieldTypeUInt32, IfSet: "hasData"}, {Name: "noData", Type: FieldTypeUInt16, IfNotSet: "hasData"}, }, } // Test with hasData set ps1 := NewPacketStruct(version) ps1.Set("hasData", uint8(1)) ps1.Set("data", uint32(999)) ps1.Set("noData", uint16(111)) data1, err := ps1.Serialize() if err != nil { t.Fatalf("Failed to serialize: %v", err) } // Should contain: uint8(1), uint32(999) // Should NOT contain: uint16(111) expectedSize := 1 + 4 if len(data1) != expectedSize { t.Errorf("Expected serialized size %d, got %d", expectedSize, len(data1)) } // Test with hasData not set ps2 := NewPacketStruct(version) ps2.Set("data", uint32(999)) ps2.Set("noData", uint16(111)) data2, err := ps2.Serialize() if err != nil { t.Fatalf("Failed to serialize: %v", err) } // Should contain: uint8(0), uint16(111) // Should NOT contain: uint32(999) expectedSize = 1 + 2 if len(data2) != expectedSize { t.Errorf("Expected serialized size %d, got %d", expectedSize, len(data2)) } } // BenchmarkSerialization benchmarks packet serialization func BenchmarkSerialization(b *testing.B) { version := &PacketVersion{ Version: 1, Fields: []Field{ {Name: "id", Type: FieldTypeUInt32}, {Name: "name", Type: FieldTypeString16}, {Name: "level", Type: FieldTypeUInt8}, {Name: "x", Type: FieldTypeFloat}, {Name: "y", Type: FieldTypeFloat}, {Name: "z", Type: FieldTypeFloat}, {Name: "flags", Type: FieldTypeUInt32}, {Name: "timestamp", Type: FieldTypeUInt64}, }, } ps := NewPacketStruct(version) ps.Set("id", uint32(12345)) ps.Set("name", "BenchmarkPlayer") ps.Set("level", uint8(50)) ps.Set("x", float32(100.5)) ps.Set("y", float32(200.5)) ps.Set("z", float32(300.5)) ps.Set("flags", uint32(0xFF00FF00)) ps.Set("timestamp", uint64(1234567890)) b.ResetTimer() for i := 0; i < b.N; i++ { data, err := ps.Serialize() if err != nil { b.Fatal(err) } _ = data } } // BenchmarkDeserialization benchmarks packet deserialization func BenchmarkDeserialization(b *testing.B) { version := &PacketVersion{ Version: 1, Fields: []Field{ {Name: "id", Type: FieldTypeUInt32}, {Name: "name", Type: FieldTypeString16}, {Name: "level", Type: FieldTypeUInt8}, {Name: "x", Type: FieldTypeFloat}, {Name: "y", Type: FieldTypeFloat}, {Name: "z", Type: FieldTypeFloat}, }, } // Create test data ps := NewPacketStruct(version) ps.Set("id", uint32(12345)) ps.Set("name", "TestPlayer") ps.Set("level", uint8(50)) ps.Set("x", float32(100.5)) ps.Set("y", float32(200.5)) ps.Set("z", float32(300.5)) data, _ := ps.Serialize() b.ResetTimer() for i := 0; i < b.N; i++ { ps2 := NewPacketStruct(version) err := ps2.Deserialize(data) if err != nil { b.Fatal(err) } } }