package eq2net import ( "database/sql" "fmt" "testing" ) // Test database configuration const ( testDBHost = "localhost" testDBPort = "3306" testDBUser = "root" testDBPass = "Root12!" testDBName = "eq2test" ) // getTestDB creates a test database connection func getTestDB(t *testing.T) *sql.DB { // Connect without database first to create it if needed dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/", testDBUser, testDBPass, testDBHost, testDBPort) db, err := sql.Open("mysql", dsn) if err != nil { t.Skipf("Cannot connect to MySQL: %v", err) return nil } // Create test database if it doesn't exist _, err = db.Exec("CREATE DATABASE IF NOT EXISTS " + testDBName) if err != nil { t.Skipf("Cannot create test database: %v", err) return nil } db.Close() // Connect to test database dsn = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true", testDBUser, testDBPass, testDBHost, testDBPort, testDBName) db, err = ConnectDB(dsn) if err != nil { t.Skipf("Cannot connect to test database: %v", err) return nil } // Create tables if err := CreateOpcodeTable(db); err != nil { t.Fatalf("Failed to create opcodes table: %v", err) } return db } // cleanupTestDB removes test data func cleanupTestDB(db *sql.DB) { if db != nil { db.Exec("DELETE FROM opcodes WHERE version >= 9999") // Clean test versions db.Close() } } func TestOpcodeManager(t *testing.T) { manager := NewOpcodeManager(1193) // Test loading opcodes opcodes := map[string]uint16{ "OP_LoginRequestMsg": 0x0001, "OP_LoginReplyMsg": 0x0002, "OP_WorldListMsg": 0x0003, "OP_PlayCharacterRequestMsg": 0x0004, } err := manager.LoadOpcodes(opcodes) if err != nil { t.Fatalf("Failed to load opcodes: %v", err) } // Test EmuToEQ conversion eq := manager.EmuToEQ(OP_LoginRequestMsg) if eq != 0x0001 { t.Errorf("Expected EQ opcode 0x0001, got 0x%04x", eq) } // Test EQToEmu conversion emu := manager.EQToEmu(0x0002) if emu != OP_LoginReplyMsg { t.Errorf("Expected emu opcode %v, got %v", OP_LoginReplyMsg, emu) } // Test unknown opcode eq = manager.EmuToEQ(OP_Unknown) if eq != 0xFFFF { t.Errorf("Expected 0xFFFF for unknown opcode, got 0x%04x", eq) } // Test name lookups name := manager.EmuToName(OP_LoginRequestMsg) if name != "OP_LoginRequestMsg" { t.Errorf("Expected 'OP_LoginRequestMsg', got '%s'", name) } name = manager.EQToName(0x0003) if name != "OP_WorldListMsg" { t.Errorf("Expected 'OP_WorldListMsg', got '%s'", name) } } func TestOpcodeService(t *testing.T) { db := getTestDB(t) if db == nil { return // Test skipped } defer cleanupTestDB(db) service := NewOpcodeService(db) // Insert test opcodes testVersion := uint16(9999) testOpcodes := map[string]uint16{ "OP_LoginRequestMsg": 0x1001, "OP_LoginReplyMsg": 0x1002, "OP_WorldListMsg": 0x1003, "OP_PlayCharacterRequestMsg": 0x1004, "OP_DeleteCharacterRequestMsg": 0x1005, } // Import opcodes err := service.ImportOpcodes(testVersion, testOpcodes) if err != nil { t.Fatalf("Failed to import opcodes: %v", err) } // Get manager for version manager, err := service.GetManager(testVersion) if err != nil { t.Fatalf("Failed to get manager: %v", err) } if manager.GetVersion() != testVersion { t.Errorf("Expected version %d, got %d", testVersion, manager.GetVersion()) } // Test opcode conversions eq := manager.EmuToEQ(OP_LoginRequestMsg) if eq != 0x1001 { t.Errorf("Expected EQ opcode 0x1001, got 0x%04x", eq) } emu := manager.EQToEmu(0x1002) if emu != OP_LoginReplyMsg { t.Errorf("Expected emu opcode %v, got %v", OP_LoginReplyMsg, emu) } // Test single opcode save err = service.SaveOpcode(testVersion, "OP_ServerListRequestMsg", 0x1006) if err != nil { t.Fatalf("Failed to save opcode: %v", err) } // Force reload and verify service.mu.Lock() delete(service.managers, testVersion) service.mu.Unlock() manager, err = service.GetManager(testVersion) if err != nil { t.Fatalf("Failed to reload manager: %v", err) } eq = manager.EmuToEQ(OP_ServerListRequestMsg) if eq != 0x1006 { t.Errorf("Expected EQ opcode 0x1006 after save, got 0x%04x", eq) } } func TestGetSupportedVersions(t *testing.T) { db := getTestDB(t) if db == nil { return // Test skipped } defer cleanupTestDB(db) service := NewOpcodeService(db) // Add opcodes for multiple versions versions := []uint16{9999, 9998, 9997} for _, v := range versions { opcodes := map[string]uint16{ "OP_LoginRequestMsg": uint16(v), } if err := service.ImportOpcodes(v, opcodes); err != nil { t.Fatalf("Failed to import opcodes for version %d: %v", v, err) } } // Get supported versions supported, err := service.GetSupportedVersions() if err != nil { t.Fatalf("Failed to get supported versions: %v", err) } // Check that our test versions are included found := make(map[uint16]bool) for _, v := range supported { found[v] = true } for _, v := range versions { if !found[v] { t.Errorf("Version %d not found in supported versions", v) } } } func TestRefreshAll(t *testing.T) { db := getTestDB(t) if db == nil { return // Test skipped } defer cleanupTestDB(db) service := NewOpcodeService(db) // Add opcodes for multiple versions versions := []uint16{9999, 9998} for _, v := range versions { opcodes := map[string]uint16{ "OP_LoginRequestMsg": uint16(v), "OP_LoginReplyMsg": uint16(v + 1000), } if err := service.ImportOpcodes(v, opcodes); err != nil { t.Fatalf("Failed to import opcodes for version %d: %v", v, err) } } // Refresh all err := service.RefreshAll() if err != nil { t.Fatalf("Failed to refresh all: %v", err) } // Verify all versions are loaded for _, v := range versions { manager, err := service.GetManager(v) if err != nil { t.Errorf("Failed to get manager for version %d after refresh: %v", v, err) } eq := manager.EmuToEQ(OP_LoginRequestMsg) if eq != v { t.Errorf("Version %d: expected opcode %d, got %d", v, v, eq) } } } func TestGetOpcodeVersion(t *testing.T) { tests := []struct { clientVersion uint16 expectedOpcode uint16 }{ {800, 1}, {899, 1}, {900, 900}, {1099, 900}, {1100, 1100}, {1192, 1100}, {1193, 1193}, {1199, 1193}, {1200, 1193}, {1300, 1300}, } for _, tt := range tests { result := GetOpcodeVersion(tt.clientVersion) if result != tt.expectedOpcode { t.Errorf("GetOpcodeVersion(%d) = %d, want %d", tt.clientVersion, result, tt.expectedOpcode) } } } func TestOpcodeNames(t *testing.T) { // Verify all defined opcodes have names knownOpcodes := []EmuOpcode{ OP_Unknown, OP_LoginRequestMsg, OP_LoginByNumRequestMsg, OP_WSLoginRequestMsg, OP_ESLoginRequestMsg, OP_LoginReplyMsg, OP_WSStatusReplyMsg, OP_WorldListMsg, OP_WorldStatusMsg, OP_DeleteCharacterRequestMsg, OP_DeleteCharacterReplyMsg, OP_CreateCharacterRequestMsg, OP_CreateCharacterReplyMsg, OP_PlayCharacterRequestMsg, OP_PlayCharacterReplyMsg, OP_ServerListRequestMsg, OP_ServerListReplyMsg, OP_CharacterListRequestMsg, OP_CharacterListReplyMsg, } for _, opcode := range knownOpcodes { name, exists := OpcodeNames[opcode] if !exists { t.Errorf("Opcode %v has no name defined", opcode) } if name == "" { t.Errorf("Opcode %v has empty name", opcode) } } } // BenchmarkOpcodeConversion benchmarks opcode conversion performance func BenchmarkOpcodeConversion(b *testing.B) { manager := NewOpcodeManager(1193) opcodes := make(map[string]uint16) // Add many opcodes for i := uint16(1); i <= 100; i++ { name := fmt.Sprintf("OP_Test%d", i) opcodes[name] = i OpcodeNames[EmuOpcode(i)] = name } manager.LoadOpcodes(opcodes) b.ResetTimer() b.Run("EmuToEQ", func(b *testing.B) { for i := 0; i < b.N; i++ { _ = manager.EmuToEQ(EmuOpcode(i%100 + 1)) } }) b.Run("EQToEmu", func(b *testing.B) { for i := 0; i < b.N; i++ { _ = manager.EQToEmu(uint16(i%100 + 1)) } }) }