1
0
Protocol/opcodes_test.go
2025-09-01 14:03:38 -05:00

344 lines
7.8 KiB
Go

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