package commands import ( "testing" "eq2emu/internal/entity" "eq2emu/internal/spawn" ) // Mock implementations for testing type mockClient struct { player *entity.Entity accountID int32 characterID int32 adminLevel int name string zone *mockZone messages []mockMessage } type mockMessage struct { channel int color int message string } func (mc *mockClient) GetPlayer() *entity.Entity { return mc.player } func (mc *mockClient) GetAccountID() int32 { return mc.accountID } func (mc *mockClient) GetCharacterID() int32 { return mc.characterID } func (mc *mockClient) GetAdminLevel() int { return mc.adminLevel } func (mc *mockClient) GetName() string { return mc.name } func (mc *mockClient) IsInZone() bool { return mc.zone != nil } func (mc *mockClient) GetZone() ZoneInterface { return mc.zone } func (mc *mockClient) SendMessage(channel, color int, message string) { mc.messages = append(mc.messages, mockMessage{channel, color, message}) } func (mc *mockClient) SendPopupMessage(message string) {} func (mc *mockClient) Disconnect() {} type mockZone struct { id int32 name string description string players []*entity.Entity } func (mz *mockZone) GetID() int32 { return mz.id } func (mz *mockZone) GetName() string { return mz.name } func (mz *mockZone) GetDescription() string { return mz.description } func (mz *mockZone) GetPlayers() []*entity.Entity { return mz.players } func (mz *mockZone) Shutdown() {} func (mz *mockZone) SendZoneMessage(channel, color int, message string) {} func (mz *mockZone) GetSpawnByName(name string) *spawn.Spawn { return nil } func (mz *mockZone) GetSpawnByID(id int32) *spawn.Spawn { return nil } func TestCommandManager_Register(t *testing.T) { cm := NewCommandManager() // Test registering a valid command cmd := &Command{ Name: "test", Type: CommandTypePlayer, Description: "Test command", Usage: "/test", RequiredLevel: AdminLevelPlayer, Handler: func(ctx *CommandContext) error { ctx.AddStatusMessage("Test executed") return nil }, } err := cm.Register(cmd) if err != nil { t.Errorf("Failed to register valid command: %v", err) } // Test registering duplicate command err = cm.Register(cmd) if err == nil { t.Error("Expected error when registering duplicate command") } // Test registering nil command err = cm.Register(nil) if err == nil { t.Error("Expected error when registering nil command") } // Test registering command with empty name invalidCmd := &Command{ Name: "", Handler: func(ctx *CommandContext) error { return nil }, } err = cm.Register(invalidCmd) if err == nil { t.Error("Expected error when registering command with empty name") } // Test registering command with nil handler invalidCmd2 := &Command{ Name: "invalid", Handler: nil, } err = cm.Register(invalidCmd2) if err == nil { t.Error("Expected error when registering command with nil handler") } } func TestCommandManager_HasCommand(t *testing.T) { cm := NewCommandManager() cmd := &Command{ Name: "testcmd", Type: CommandTypePlayer, RequiredLevel: AdminLevelPlayer, Handler: func(ctx *CommandContext) error { return nil }, } // Test command doesn't exist initially if cm.HasCommand("testcmd") { t.Error("Command should not exist initially") } // Register command err := cm.Register(cmd) if err != nil { t.Fatalf("Failed to register command: %v", err) } // Test command exists after registration if !cm.HasCommand("testcmd") { t.Error("Command should exist after registration") } // Test case insensitivity if !cm.HasCommand("TESTCMD") { t.Error("Command lookup should be case insensitive") } } func TestCommandManager_GetCommand(t *testing.T) { cm := NewCommandManager() cmd := &Command{ Name: "getcmd", Type: CommandTypeAdmin, RequiredLevel: AdminLevelGM, Handler: func(ctx *CommandContext) error { return nil }, } // Test getting non-existent command _, exists := cm.GetCommand("getcmd") if exists { t.Error("Command should not exist initially") } // Register command err := cm.Register(cmd) if err != nil { t.Fatalf("Failed to register command: %v", err) } // Test getting existing command retrievedCmd, exists := cm.GetCommand("getcmd") if !exists { t.Error("Command should exist after registration") } if retrievedCmd.Name != "getcmd" { t.Errorf("Expected command name 'getcmd', got '%s'", retrievedCmd.Name) } if retrievedCmd.Type != CommandTypeAdmin { t.Errorf("Expected command type Admin, got %v", retrievedCmd.Type) } // Test case insensitivity _, exists = cm.GetCommand("GETCMD") if !exists { t.Error("Command lookup should be case insensitive") } } func TestCommandManager_Execute(t *testing.T) { cm := NewCommandManager() executed := false cmd := &Command{ Name: "exectest", Type: CommandTypePlayer, RequiredLevel: AdminLevelPlayer, Handler: func(ctx *CommandContext) error { executed = true ctx.AddStatusMessage("Command executed successfully") return nil }, } err := cm.Register(cmd) if err != nil { t.Fatalf("Failed to register command: %v", err) } // Test successful execution ctx := NewCommandContext(CommandTypePlayer, "exectest", []string{}) ctx.AdminLevel = AdminLevelPlayer err = cm.Execute(ctx) if err != nil { t.Errorf("Command execution failed: %v", err) } if !executed { t.Error("Command handler was not executed") } // Test insufficient admin level ctx2 := NewCommandContext(CommandTypePlayer, "exectest", []string{}) ctx2.AdminLevel = 0 // Register admin-only command adminCmd := &Command{ Name: "admintest", Type: CommandTypeAdmin, RequiredLevel: AdminLevelAdmin, Handler: func(ctx *CommandContext) error { return nil }, } err = cm.Register(adminCmd) if err != nil { t.Fatalf("Failed to register admin command: %v", err) } ctx3 := NewCommandContext(CommandTypeAdmin, "admintest", []string{}) ctx3.AdminLevel = AdminLevelPlayer err = cm.Execute(ctx3) if err == nil { t.Error("Expected error for insufficient admin level") } // Test unknown command ctx4 := NewCommandContext(CommandTypePlayer, "unknown", []string{}) err = cm.Execute(ctx4) if err == nil { t.Error("Expected error for unknown command") } } func TestCommandManager_RegisterSubCommand(t *testing.T) { cm := NewCommandManager() // Register parent command parentCmd := &Command{ Name: "parent", Type: CommandTypeAdmin, RequiredLevel: AdminLevelGM, Handler: func(ctx *CommandContext) error { return nil }, } err := cm.Register(parentCmd) if err != nil { t.Fatalf("Failed to register parent command: %v", err) } // Register subcommand subCmd := &Command{ Name: "sub", Type: CommandTypeAdmin, RequiredLevel: AdminLevelGM, Handler: func(ctx *CommandContext) error { ctx.AddStatusMessage("Subcommand executed") return nil }, } err = cm.RegisterSubCommand("parent", subCmd) if err != nil { t.Errorf("Failed to register subcommand: %v", err) } // Test registering subcommand for non-existent parent err = cm.RegisterSubCommand("nonexistent", subCmd) if err == nil { t.Error("Expected error when registering subcommand for non-existent parent") } // Test registering duplicate subcommand err = cm.RegisterSubCommand("parent", subCmd) if err == nil { t.Error("Expected error when registering duplicate subcommand") } } func TestCommandManager_ParseCommand(t *testing.T) { cm := NewCommandManager() client := &mockClient{ name: "TestPlayer", adminLevel: AdminLevelPlayer, } // Test parsing valid command ctx := cm.ParseCommand("/say Hello world", client) if ctx == nil { t.Error("Expected non-nil context for valid command") } if ctx.CommandName != "say" { t.Errorf("Expected command name 'say', got '%s'", ctx.CommandName) } if len(ctx.Arguments) != 2 { t.Errorf("Expected 2 arguments, got %d", len(ctx.Arguments)) } if ctx.Arguments[0] != "Hello" || ctx.Arguments[1] != "world" { t.Errorf("Unexpected arguments: %v", ctx.Arguments) } if ctx.RawArguments != "Hello world" { t.Errorf("Expected raw arguments 'Hello world', got '%s'", ctx.RawArguments) } // Test parsing command without slash ctx2 := cm.ParseCommand("tell Player Hi there", client) if ctx2 == nil { t.Error("Expected non-nil context for command without slash") } if ctx2.CommandName != "tell" { t.Errorf("Expected command name 'tell', got '%s'", ctx2.CommandName) } // Test parsing empty command ctx3 := cm.ParseCommand("", client) if ctx3 != nil { t.Error("Expected nil context for empty command") } // Test parsing whitespace-only command ctx4 := cm.ParseCommand(" ", client) if ctx4 != nil { t.Error("Expected nil context for whitespace-only command") } } func TestCommandContext_ValidateArgumentCount(t *testing.T) { ctx := NewCommandContext(CommandTypePlayer, "test", []string{"arg1", "arg2", "arg3"}) // Test valid argument count (exactly 3) err := ctx.ValidateArgumentCount(3, 3) if err != nil { t.Errorf("Expected no error for exact argument count, got: %v", err) } // Test valid argument count (range 2-4) err = ctx.ValidateArgumentCount(2, 4) if err != nil { t.Errorf("Expected no error for argument count in range, got: %v", err) } // Test too few arguments err = ctx.ValidateArgumentCount(5, 6) if err == nil { t.Error("Expected error for too few arguments") } // Test too many arguments err = ctx.ValidateArgumentCount(1, 2) if err == nil { t.Error("Expected error for too many arguments") } // Test unlimited max arguments (-1) err = ctx.ValidateArgumentCount(2, -1) if err != nil { t.Errorf("Expected no error for unlimited max arguments, got: %v", err) } } func TestCommandContext_GetArgument(t *testing.T) { ctx := NewCommandContext(CommandTypePlayer, "test", []string{"first", "second", "third"}) // Test valid indices arg, exists := ctx.GetArgument(0) if !exists || arg != "first" { t.Errorf("Expected 'first', got '%s' (exists: %v)", arg, exists) } arg, exists = ctx.GetArgument(2) if !exists || arg != "third" { t.Errorf("Expected 'third', got '%s' (exists: %v)", arg, exists) } // Test invalid indices _, exists = ctx.GetArgument(-1) if exists { t.Error("Expected false for negative index") } _, exists = ctx.GetArgument(3) if exists { t.Error("Expected false for out-of-bounds index") } } func TestCommandContext_GetArgumentInt(t *testing.T) { ctx := NewCommandContext(CommandTypePlayer, "test", []string{"123", "not_a_number", "-456"}) // Test valid integer val := ctx.GetArgumentInt(0, 999) if val != 123 { t.Errorf("Expected 123, got %d", val) } // Test invalid integer (should return default) val = ctx.GetArgumentInt(1, 999) if val != 999 { t.Errorf("Expected default value 999, got %d", val) } // Test negative integer val = ctx.GetArgumentInt(2, 999) if val != -456 { t.Errorf("Expected -456, got %d", val) } // Test out-of-bounds index (should return default) val = ctx.GetArgumentInt(5, 999) if val != 999 { t.Errorf("Expected default value 999, got %d", val) } } func TestCommandContext_GetArgumentFloat(t *testing.T) { ctx := NewCommandContext(CommandTypePlayer, "test", []string{"12.34", "not_a_number", "-56.78"}) // Test valid float val := ctx.GetArgumentFloat(0, 999.0) if val != 12.34 { t.Errorf("Expected 12.34, got %f", val) } // Test invalid float (should return default) val = ctx.GetArgumentFloat(1, 999.0) if val != 999.0 { t.Errorf("Expected default value 999.0, got %f", val) } // Test negative float val = ctx.GetArgumentFloat(2, 999.0) if val != -56.78 { t.Errorf("Expected -56.78, got %f", val) } } func TestCommandContext_GetArgumentBool(t *testing.T) { ctx := NewCommandContext(CommandTypePlayer, "test", []string{"true", "false", "yes", "no", "on", "off", "1", "0", "invalid"}) tests := []struct { index int expected bool }{ {0, true}, // "true" {1, false}, // "false" {2, true}, // "yes" {3, false}, // "no" {4, true}, // "on" {5, false}, // "off" {6, true}, // "1" {7, false}, // "0" } for _, test := range tests { val := ctx.GetArgumentBool(test.index, !test.expected) // Use opposite as default if val != test.expected { t.Errorf("Index %d: expected %v, got %v", test.index, test.expected, val) } } // Test invalid boolean (should return default) val := ctx.GetArgumentBool(8, true) if val != true { t.Errorf("Expected default value true for invalid boolean, got %v", val) } } func TestCommandContext_Messages(t *testing.T) { ctx := NewCommandContext(CommandTypePlayer, "test", []string{}) // Test adding messages ctx.AddDefaultMessage("Default message") ctx.AddErrorMessage("Error message") ctx.AddStatusMessage("Status message") ctx.AddMessage(ChannelSay, ColorWhite, "Custom message") if len(ctx.Messages) != 4 { t.Errorf("Expected 4 messages, got %d", len(ctx.Messages)) } // Check message types expectedMessages := []struct { channel int color int message string }{ {ChannelDefault, ColorWhite, "Default message"}, {ChannelError, ColorRed, "Error message"}, {ChannelStatus, ColorYellow, "Status message"}, {ChannelSay, ColorWhite, "Custom message"}, } for i, expected := range expectedMessages { msg := ctx.Messages[i] if msg.Channel != expected.channel { t.Errorf("Message %d: expected channel %d, got %d", i, expected.channel, msg.Channel) } if msg.Color != expected.color { t.Errorf("Message %d: expected color %d, got %d", i, expected.color, msg.Color) } if msg.Message != expected.message { t.Errorf("Message %d: expected message '%s', got '%s'", i, expected.message, msg.Message) } } // Test clearing messages ctx.ClearMessages() if len(ctx.Messages) != 0 { t.Errorf("Expected 0 messages after clearing, got %d", len(ctx.Messages)) } } func TestInitializeCommands(t *testing.T) { cm, err := InitializeCommands() if err != nil { t.Fatalf("Failed to initialize commands: %v", err) } if cm == nil { t.Fatal("Expected non-nil command manager") } // Test that some basic commands are registered expectedCommands := []string{"say", "tell", "kick", "ban", "reload", "shutdown", "console_ban", "console_kick"} for _, cmdName := range expectedCommands { if !cm.HasCommand(cmdName) { t.Errorf("Expected command '%s' to be registered", cmdName) } } // Test command counts by type playerCommands := cm.ListCommandsByType(CommandTypePlayer) adminCommands := cm.ListCommandsByType(CommandTypeAdmin) consoleCommands := cm.ListCommandsByType(CommandTypeConsole) if len(playerCommands) == 0 { t.Error("Expected player commands to be registered") } if len(adminCommands) == 0 { t.Error("Expected admin commands to be registered") } if len(consoleCommands) == 0 { t.Error("Expected console commands to be registered") } } func TestGetAvailableCommands(t *testing.T) { cm, err := InitializeCommands() if err != nil { t.Fatalf("Failed to initialize commands: %v", err) } // Test player level commands playerCommands := GetAvailableCommands(cm, AdminLevelPlayer) if len(playerCommands) == 0 { t.Error("Expected player-level commands to be available") } // Test admin level commands adminCommands := GetAvailableCommands(cm, AdminLevelAdmin) if len(adminCommands) <= len(playerCommands) { t.Error("Expected admin to have more commands available than player") } // Verify all player commands are also available to admin playerCommandMap := make(map[string]bool) for _, cmd := range playerCommands { playerCommandMap[cmd] = true } for _, cmd := range playerCommands { found := false for _, adminCmd := range adminCommands { if adminCmd == cmd { found = true break } } if !found { t.Errorf("Player command '%s' should be available to admin", cmd) } } }