614 lines
16 KiB
Go
614 lines
16 KiB
Go
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)
|
|
}
|
|
}
|
|
} |