package packets import ( "eq2emu/internal/packets/parser" "fmt" "path/filepath" "sort" "strings" "testing" ) func TestParseAllXMLDefinitions(t *testing.T) { // Get all available packet names packetNames := GetPacketNames() if len(packetNames) == 0 { t.Fatal("No packet definitions loaded") } sort.Strings(packetNames) var failed []string var passed []string var skipped []string t.Logf("Testing %d packet definitions...", len(packetNames)) for _, name := range packetNames { t.Run(name, func(t *testing.T) { def, exists := GetPacket(name) if !exists { t.Errorf("Packet definition '%s' not found", name) failed = append(failed, name) return } // Validate packet definition structure err := validatePacketDefinition(name, def) if err != nil { t.Errorf("Invalid packet definition '%s': %v", name, err) failed = append(failed, name) return } // Try to create sample data and test parsing round-trip err = testPacketRoundTrip(name, def) if err != nil { if strings.Contains(err.Error(), "complex condition") { t.Logf("Skipping '%s': %v", name, err) skipped = append(skipped, name) return } t.Errorf("Round-trip test failed for '%s': %v", name, err) failed = append(failed, name) return } passed = append(passed, name) t.Logf("Successfully validated '%s'", name) }) } // Summary report t.Logf("\n=== PACKET VALIDATION SUMMARY ===") t.Logf("Total packets: %d", len(packetNames)) t.Logf("Passed: %d", len(passed)) t.Logf("Failed: %d", len(failed)) t.Logf("Skipped (complex conditions): %d", len(skipped)) if len(failed) > 0 { t.Logf("\nFailed packets:") for _, name := range failed { t.Logf(" - %s", name) } } if len(skipped) > 0 { t.Logf("\nSkipped packets (complex conditions):") for _, name := range skipped { t.Logf(" - %s", name) } } // Report by category categories := make(map[string]int) for _, name := range passed { category := getPacketCategory(name) categories[category]++ } t.Logf("\nPassed packets by category:") var cats []string for cat := range categories { cats = append(cats, cat) } sort.Strings(cats) for _, cat := range cats { t.Logf(" %s: %d", cat, categories[cat]) } // Only fail the test if we have actual parsing failures, not skipped ones if len(failed) > 0 { t.Errorf("%d packet definitions failed validation", len(failed)) } } func TestPacketBuilderBasicFunctionality(t *testing.T) { // Test with a simple packet that we know should work testPackets := []string{ "LoginRequest", "PlayRequest", "CreateCharacter", } for _, packetName := range testPackets { t.Run(packetName, func(t *testing.T) { def, exists := GetPacket(packetName) if !exists { t.Skipf("Packet '%s' not found - may not exist in current definitions", packetName) return } // Create minimal test data data := createMinimalTestData(def) // Test builder - just ensure it can build without error builder := NewPacketBuilder(def, 1, 0) packetData, err := builder.Build(data) if err != nil { t.Errorf("Failed to build packet '%s': %v", packetName, err) return } if len(packetData) == 0 { t.Errorf("Built packet '%s' is empty", packetName) return } t.Logf("Successfully built packet '%s' (%d bytes)", packetName, len(packetData)) // Skip parsing back for now due to complex conditions - just test building }) } } func TestPacketDefinitionCoverage(t *testing.T) { // Test that we have reasonable coverage of packet types packetNames := GetPacketNames() categories := map[string]int{ "login": 0, "world": 0, "item": 0, "spawn": 0, "common": 0, "other": 0, } for _, name := range packetNames { category := getPacketCategory(name) if count, exists := categories[category]; exists { categories[category] = count + 1 } else { categories["other"]++ } } t.Logf("Packet coverage by category:") for cat, count := range categories { t.Logf(" %s: %d packets", cat, count) } // Ensure we have some packets in each major category if categories["world"] == 0 { t.Error("No world packets found") } if categories["login"] == 0 { t.Error("No login packets found") } if categories["item"] == 0 { t.Error("No item packets found") } } // Helper functions func validatePacketDefinition(name string, def *parser.PacketDef) error { if def == nil { return fmt.Errorf("packet definition is nil") } if len(def.Fields) == 0 { return fmt.Errorf("packet has no fields defined") } if len(def.Orders) == 0 { return fmt.Errorf("packet has no field ordering defined") } // Check that all fields referenced in orders exist for version, order := range def.Orders { for _, fieldName := range order { if _, exists := def.Fields[fieldName]; !exists { return fmt.Errorf("field '%s' referenced in version %d order but not defined", fieldName, version) } } } // Validate field definitions for fieldName, field := range def.Fields { err := validateFieldDefinition(fieldName, field) if err != nil { return fmt.Errorf("invalid field '%s': %w", fieldName, err) } } return nil } func validateFieldDefinition(name string, field parser.FieldDesc) error { // Check that field type is valid if field.Type < 0 || field.Type > 25 { // Adjust range based on actual enum return fmt.Errorf("invalid field type %d", field.Type) } // If Type2 is set, Type2Cond should also be set if field.Type2 != 0 && field.Type2Cond == "" { return fmt.Errorf("field has Type2 but no Type2Cond") } return nil } func testPacketRoundTrip(name string, def *parser.PacketDef) error { // Skip packets with complex conditions that would be hard to satisfy if hasComplexConditions(def) { return fmt.Errorf("complex condition detected - skipping round-trip test") } // For now, just validate the structure without round-trip testing // This is safer until we have better condition handling return nil } func hasComplexConditions(def *parser.PacketDef) bool { for _, field := range def.Fields { if strings.Contains(field.Condition, ">=") || strings.Contains(field.Condition, "<=") || strings.Contains(field.Condition, "!=") || strings.Contains(field.Condition, "&&") || strings.Contains(field.Condition, "||") || strings.Contains(field.Type2Cond, ">=") || strings.Contains(field.Type2Cond, "<=") { return true } } return false } func createMinimalTestData(def *parser.PacketDef) map[string]any { data := make(map[string]any) // Get the field order for version 1 (or first available version) var version uint32 = 1 if len(def.Orders) > 0 { for v := range def.Orders { version = v break } } order, exists := def.Orders[version] if !exists && len(def.Orders) > 0 { // Take first available version for v, o := range def.Orders { version = v order = o break } } for _, fieldName := range order { field, fieldExists := def.Fields[fieldName] if !fieldExists { continue } // Skip fields with complex conditions if field.Condition != "" && (strings.Contains(field.Condition, ">=") || strings.Contains(field.Condition, "<=") || strings.Contains(field.Condition, "!=")) { continue } // Create minimal test data based on field type switch field.Type { case 1: // TypeInt8 data[fieldName] = uint8(1) case 2: // TypeInt16 data[fieldName] = uint16(1) case 3: // TypeInt32 data[fieldName] = uint32(1) case 4: // TypeInt64 data[fieldName] = uint64(1) case 5: // TypeFloat data[fieldName] = float32(1.0) case 6: // TypeDouble data[fieldName] = float64(1.0) case 8: // TypeSInt8 data[fieldName] = int8(1) case 9: // TypeSInt16 data[fieldName] = int16(1) case 10: // TypeSInt32 data[fieldName] = int32(1) case 12: // TypeChar if field.Length > 0 { data[fieldName] = make([]byte, field.Length) } else { data[fieldName] = []byte("test") } case 13, 14, 15: // String types data[fieldName] = "test" case 17: // TypeArray data[fieldName] = []map[string]any{} case 25: // TypeSInt64 data[fieldName] = int64(1) } } return data } func getPacketCategory(packetName string) string { name := strings.ToLower(packetName) if strings.Contains(name, "login") || strings.Contains(name, "play") || strings.Contains(name, "world") && (strings.Contains(name, "list") || strings.Contains(name, "update")) { return "login" } if strings.Contains(name, "item") || strings.Contains(name, "inventory") || strings.Contains(name, "merchant") || strings.Contains(name, "loot") { return "item" } if strings.Contains(name, "spawn") || strings.Contains(name, "position") { return "spawn" } if strings.Contains(name, "character") || strings.Contains(name, "create") { return "common" } // Determine by file path if we have it dir := filepath.Dir(packetName) switch { case strings.Contains(dir, "login"): return "login" case strings.Contains(dir, "world"): return "world" case strings.Contains(dir, "item"): return "item" case strings.Contains(dir, "spawn"): return "spawn" case strings.Contains(dir, "common"): return "common" default: return "world" // Most packets are world packets } }