/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see .
*/
#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER
#include "tc_catch2.h"
#include "BotMovementManager.h"
#include "BotMovementController.h"
#include "PositionValidator.h"
#include "GroundValidator.h"
#include "ValidationResult.h"
#include "BotMovementDefines.h"
#include "Position.h"
TEST_CASE("Phase 1 Integration - BotMovementManager Singleton", "[BotMovement][Integration][Phase1]")
{
SECTION("Manager instance is accessible")
{
BotMovementManager* manager = sBotMovementManager;
REQUIRE(manager != nullptr);
}
SECTION("Multiple calls return same instance")
{
BotMovementManager* manager1 = sBotMovementManager;
BotMovementManager* manager2 = sBotMovementManager;
REQUIRE(manager1 == manager2);
}
SECTION("Manager has valid config")
{
BotMovementManager* manager = sBotMovementManager;
BotMovementConfig const& config = manager->GetConfig();
REQUIRE(config.GetValidationLevel() != ValidationLevel::None);
}
SECTION("Manager has path cache")
{
BotMovementManager* manager = sBotMovementManager;
PathCache* cache = manager->GetPathCache();
REQUIRE(cache != nullptr);
}
}
TEST_CASE("Phase 1 Integration - PositionValidator Bounds", "[BotMovement][Integration][Phase1][Validation]")
{
SECTION("Valid position within normal bounds")
{
Position pos(0.0f, 0.0f, 0.0f);
ValidationResult result = PositionValidator::ValidateBounds(pos);
REQUIRE(result.isValid == true);
REQUIRE(result.failureReason == ValidationFailureReason::None);
}
SECTION("Valid position with normal coordinates")
{
Position pos(1000.0f, 1000.0f, 100.0f);
ValidationResult result = PositionValidator::ValidateBounds(pos);
REQUIRE(result.isValid == true);
REQUIRE(result.failureReason == ValidationFailureReason::None);
}
SECTION("Invalid position - NaN X coordinate")
{
float nanValue = std::numeric_limits::quiet_NaN();
ValidationResult result = PositionValidator::ValidateBounds(nanValue, 0.0f, 0.0f);
REQUIRE(result.isValid == false);
REQUIRE(result.failureReason == ValidationFailureReason::InvalidPosition);
REQUIRE_FALSE(result.errorMessage.empty());
}
SECTION("Invalid position - NaN Y coordinate")
{
float nanValue = std::numeric_limits::quiet_NaN();
ValidationResult result = PositionValidator::ValidateBounds(0.0f, nanValue, 0.0f);
REQUIRE(result.isValid == false);
REQUIRE(result.failureReason == ValidationFailureReason::InvalidPosition);
}
SECTION("Invalid position - NaN Z coordinate")
{
float nanValue = std::numeric_limits::quiet_NaN();
ValidationResult result = PositionValidator::ValidateBounds(0.0f, 0.0f, nanValue);
REQUIRE(result.isValid == false);
REQUIRE(result.failureReason == ValidationFailureReason::InvalidPosition);
}
SECTION("Invalid position - Infinity X coordinate")
{
float infValue = std::numeric_limits::infinity();
ValidationResult result = PositionValidator::ValidateBounds(infValue, 0.0f, 0.0f);
REQUIRE(result.isValid == false);
REQUIRE(result.failureReason == ValidationFailureReason::OutOfBounds);
}
SECTION("Invalid position - Negative infinity")
{
float negInfValue = -std::numeric_limits::infinity();
ValidationResult result = PositionValidator::ValidateBounds(negInfValue, 0.0f, 0.0f);
REQUIRE(result.isValid == false);
REQUIRE(result.failureReason == ValidationFailureReason::OutOfBounds);
}
SECTION("Invalid position - extremely large coordinates")
{
ValidationResult result = PositionValidator::ValidateBounds(1000000.0f, 1000000.0f, 100000.0f);
REQUIRE(result.isValid == false);
REQUIRE(result.failureReason == ValidationFailureReason::OutOfBounds);
}
SECTION("Invalid position - extremely small coordinates")
{
ValidationResult result = PositionValidator::ValidateBounds(-1000000.0f, -1000000.0f, -100000.0f);
REQUIRE(result.isValid == false);
REQUIRE(result.failureReason == ValidationFailureReason::OutOfBounds);
}
}
TEST_CASE("Phase 1 Integration - PositionValidator Map ID", "[BotMovement][Integration][Phase1][Validation]")
{
SECTION("Valid map ID 0 (Eastern Kingdoms)")
{
ValidationResult result = PositionValidator::ValidateMapId(0);
REQUIRE(result.isValid == true);
REQUIRE(result.failureReason == ValidationFailureReason::None);
}
SECTION("Valid map ID 1 (Kalimdor)")
{
ValidationResult result = PositionValidator::ValidateMapId(1);
REQUIRE(result.isValid == true);
REQUIRE(result.failureReason == ValidationFailureReason::None);
}
SECTION("Invalid map ID - out of range")
{
ValidationResult result = PositionValidator::ValidateMapId(999999);
REQUIRE(result.isValid == false);
REQUIRE(result.failureReason == ValidationFailureReason::InvalidMapId);
REQUIRE_FALSE(result.errorMessage.empty());
}
}
TEST_CASE("Phase 1 Integration - PositionValidator Combined Validation", "[BotMovement][Integration][Phase1][Validation]")
{
SECTION("Valid position on valid map")
{
Position pos(0.0f, 0.0f, 0.0f);
ValidationResult result = PositionValidator::ValidatePosition(0, pos);
REQUIRE(result.isValid == true);
REQUIRE(result.failureReason == ValidationFailureReason::None);
}
SECTION("Invalid position on valid map - NaN coordinates")
{
float nanValue = std::numeric_limits::quiet_NaN();
ValidationResult result = PositionValidator::ValidatePosition(0, nanValue, 0.0f, 0.0f);
REQUIRE(result.isValid == false);
REQUIRE(result.failureReason == ValidationFailureReason::InvalidPosition);
}
SECTION("Valid position on invalid map")
{
Position pos(0.0f, 0.0f, 0.0f);
ValidationResult result = PositionValidator::ValidatePosition(999999, pos);
REQUIRE(result.isValid == false);
REQUIRE(result.failureReason == ValidationFailureReason::InvalidMapId);
}
SECTION("Invalid position on invalid map - both checks fail")
{
float nanValue = std::numeric_limits::quiet_NaN();
ValidationResult result = PositionValidator::ValidatePosition(999999, nanValue, 0.0f, 0.0f);
REQUIRE(result.isValid == false);
REQUIRE((result.failureReason == ValidationFailureReason::InvalidPosition ||
result.failureReason == ValidationFailureReason::InvalidMapId));
}
}
TEST_CASE("Phase 1 Integration - ValidationResult Structure", "[BotMovement][Integration][Phase1][Validation]")
{
SECTION("Success factory method")
{
ValidationResult result = ValidationResult::Success();
REQUIRE(result.isValid == true);
REQUIRE(result.failureReason == ValidationFailureReason::None);
REQUIRE(result.errorMessage.empty());
}
SECTION("Failure factory method")
{
ValidationResult result = ValidationResult::Failure(
ValidationFailureReason::CollisionDetected,
"Wall collision detected"
);
REQUIRE(result.isValid == false);
REQUIRE(result.failureReason == ValidationFailureReason::CollisionDetected);
REQUIRE(result.errorMessage == "Wall collision detected");
}
SECTION("Default constructor")
{
ValidationResult result;
REQUIRE(result.isValid == false);
REQUIRE(result.failureReason == ValidationFailureReason::None);
}
}
TEST_CASE("Phase 1 Integration - BotMovementDefines Enums", "[BotMovement][Integration][Phase1]")
{
SECTION("MovementStateType values are distinct")
{
REQUIRE(MovementStateType::Idle != MovementStateType::Ground);
REQUIRE(MovementStateType::Ground != MovementStateType::Swimming);
REQUIRE(MovementStateType::Swimming != MovementStateType::Flying);
REQUIRE(MovementStateType::Flying != MovementStateType::Falling);
REQUIRE(MovementStateType::Falling != MovementStateType::Stuck);
}
SECTION("ValidationFailureReason values are distinct")
{
REQUIRE(ValidationFailureReason::None != ValidationFailureReason::InvalidPosition);
REQUIRE(ValidationFailureReason::InvalidPosition != ValidationFailureReason::OutOfBounds);
REQUIRE(ValidationFailureReason::OutOfBounds != ValidationFailureReason::InvalidMapId);
REQUIRE(ValidationFailureReason::CollisionDetected != ValidationFailureReason::PathBlocked);
}
SECTION("ValidationLevel values are ordered correctly")
{
REQUIRE(static_cast(ValidationLevel::None) < static_cast(ValidationLevel::Basic));
REQUIRE(static_cast(ValidationLevel::Basic) < static_cast(ValidationLevel::Standard));
REQUIRE(static_cast(ValidationLevel::Standard) < static_cast(ValidationLevel::Strict));
}
SECTION("RecoveryLevel values are ordered correctly")
{
REQUIRE(static_cast(RecoveryLevel::Level1_RecalculatePath) <
static_cast(RecoveryLevel::Level2_BackupAndRetry));
REQUIRE(static_cast(RecoveryLevel::Level2_BackupAndRetry) <
static_cast(RecoveryLevel::Level3_RandomNearbyPosition));
REQUIRE(static_cast(RecoveryLevel::Level3_RandomNearbyPosition) <
static_cast(RecoveryLevel::Level4_TeleportToSafePosition));
REQUIRE(static_cast(RecoveryLevel::Level4_TeleportToSafePosition) <
static_cast(RecoveryLevel::Level5_EvadeAndReset));
}
}
TEST_CASE("Phase 1 Integration - PositionSnapshot Structure", "[BotMovement][Integration][Phase1]")
{
SECTION("Default constructor initializes correctly")
{
PositionSnapshot snapshot;
REQUIRE(snapshot.timestamp == 0);
}
SECTION("Parameterized constructor")
{
Position pos(100.0f, 200.0f, 50.0f);
uint32 time = 12345;
PositionSnapshot snapshot(pos, time);
REQUIRE(snapshot.pos.GetPositionX() == 100.0f);
REQUIRE(snapshot.pos.GetPositionY() == 200.0f);
REQUIRE(snapshot.pos.GetPositionZ() == 50.0f);
REQUIRE(snapshot.timestamp == time);
}
SECTION("Copy position data correctly")
{
Position pos1(1.0f, 2.0f, 3.0f);
PositionSnapshot snapshot1(pos1, 100);
Position pos2(10.0f, 20.0f, 30.0f);
PositionSnapshot snapshot2(pos2, 200);
REQUIRE(snapshot1.pos.GetPositionX() != snapshot2.pos.GetPositionX());
REQUIRE(snapshot1.timestamp != snapshot2.timestamp);
}
}
TEST_CASE("Phase 1 Integration - Validation Pipeline Correctness", "[BotMovement][Integration][Phase1][Validation]")
{
SECTION("Sequential validation - first check passes, all checks run")
{
Position validPos(100.0f, 100.0f, 10.0f);
ValidationResult boundsResult = PositionValidator::ValidateBounds(validPos);
REQUIRE(boundsResult.isValid == true);
ValidationResult mapResult = PositionValidator::ValidateMapId(0);
REQUIRE(mapResult.isValid == true);
ValidationResult combinedResult = PositionValidator::ValidatePosition(0, validPos);
REQUIRE(combinedResult.isValid == true);
}
SECTION("Sequential validation - first check fails, error captured")
{
float nanValue = std::numeric_limits::quiet_NaN();
ValidationResult boundsResult = PositionValidator::ValidateBounds(nanValue, 0.0f, 0.0f);
REQUIRE(boundsResult.isValid == false);
REQUIRE(boundsResult.failureReason == ValidationFailureReason::InvalidPosition);
ValidationResult combinedResult = PositionValidator::ValidatePosition(0, nanValue, 0.0f, 0.0f);
REQUIRE(combinedResult.isValid == false);
REQUIRE(combinedResult.failureReason == ValidationFailureReason::InvalidPosition);
}
SECTION("Multiple validation failures - first failure is reported")
{
float nanValue = std::numeric_limits::quiet_NaN();
uint32 invalidMapId = 999999;
ValidationResult result = PositionValidator::ValidatePosition(invalidMapId, nanValue, 0.0f, 0.0f);
REQUIRE(result.isValid == false);
}
}
TEST_CASE("Phase 1 Integration - Config and Manager Integration", "[BotMovement][Integration][Phase1]")
{
SECTION("Manager config can be reloaded")
{
BotMovementManager* manager = sBotMovementManager;
REQUIRE(manager != nullptr);
REQUIRE_NOTHROW(manager->ReloadConfig());
BotMovementConfig const& config = manager->GetConfig();
REQUIRE(config.GetValidationLevel() != ValidationLevel::None);
}
SECTION("Manager metrics are accessible")
{
BotMovementManager* manager = sBotMovementManager;
REQUIRE(manager != nullptr);
REQUIRE_NOTHROW(manager->GetGlobalMetrics());
}
SECTION("Manager metrics can be reset")
{
BotMovementManager* manager = sBotMovementManager;
REQUIRE(manager != nullptr);
REQUIRE_NOTHROW(manager->ResetMetrics());
}
}
TEST_CASE("Phase 1 Integration - Edge Cases and Boundary Conditions", "[BotMovement][Integration][Phase1][Validation]")
{
SECTION("Position at origin is valid")
{
Position origin(0.0f, 0.0f, 0.0f);
ValidationResult result = PositionValidator::ValidateBounds(origin);
REQUIRE(result.isValid == true);
}
SECTION("Position with very small positive values")
{
Position pos(0.001f, 0.001f, 0.001f);
ValidationResult result = PositionValidator::ValidateBounds(pos);
REQUIRE(result.isValid == true);
}
SECTION("Position with very small negative values")
{
Position pos(-0.001f, -0.001f, -0.001f);
ValidationResult result = PositionValidator::ValidateBounds(pos);
REQUIRE(result.isValid == true);
}
SECTION("Position with mixed positive and negative coordinates")
{
Position pos(-100.0f, 100.0f, -50.0f);
ValidationResult result = PositionValidator::ValidateBounds(pos);
REQUIRE(result.isValid == true);
}
SECTION("Large but valid coordinates")
{
Position pos(10000.0f, 10000.0f, 1000.0f);
ValidationResult result = PositionValidator::ValidateBounds(pos);
REQUIRE(result.isValid == true);
}
SECTION("Z coordinate at extremes")
{
Position highZ(0.0f, 0.0f, 5000.0f);
ValidationResult result = PositionValidator::ValidateBounds(highZ);
REQUIRE(result.isValid == true);
}
}
TEST_CASE("Phase 1 Integration - Error Message Quality", "[BotMovement][Integration][Phase1][Validation]")
{
SECTION("Invalid position error has descriptive message")
{
float nanValue = std::numeric_limits::quiet_NaN();
ValidationResult result = PositionValidator::ValidateBounds(nanValue, 0.0f, 0.0f);
REQUIRE(result.isValid == false);
REQUIRE_FALSE(result.errorMessage.empty());
REQUIRE(result.errorMessage.length() > 10);
}
SECTION("Out of bounds error has descriptive message")
{
ValidationResult result = PositionValidator::ValidateBounds(1000000.0f, 0.0f, 0.0f);
REQUIRE(result.isValid == false);
REQUIRE_FALSE(result.errorMessage.empty());
REQUIRE(result.errorMessage.length() > 10);
}
SECTION("Invalid map ID error has descriptive message")
{
ValidationResult result = PositionValidator::ValidateMapId(999999);
REQUIRE(result.isValid == false);
REQUIRE_FALSE(result.errorMessage.empty());
REQUIRE(result.errorMessage.length() > 10);
}
}