Files
EVEngine/test/condition.cpp
T

128 lines
5.8 KiB
C++

#include "decision/Condition.h"
#include "zeroerr/assert.h"
#include "zeroerr/unittest.h"
#include <map>
#include <optional>
#include <string>
namespace {
class TestContext final : public eve::decision::EvaluationContext {
public:
std::map<std::string, eve::Value> values;
std::map<std::string, bool> tags;
std::map<std::string, eve::Value> attributes;
std::map<std::string, eve::Value> resources;
std::map<std::string, eve::Value> states;
std::map<std::string, bool> authorities;
std::map<std::string, eve::decision::ConditionResult> policies;
[[nodiscard]] std::optional<eve::Value> value(std::string_view key) const override {
auto it = values.find(std::string(key));
return it == values.end() ? std::nullopt : std::optional<eve::Value>(it->second);
}
[[nodiscard]] std::optional<bool> hasTag(std::string_view tag) const override {
auto it = tags.find(std::string(tag));
return it == tags.end() ? std::nullopt : std::optional<bool>(it->second);
}
[[nodiscard]] std::optional<eve::Value> attribute(std::string_view key) const override {
auto it = attributes.find(std::string(key));
return it == attributes.end() ? std::nullopt : std::optional<eve::Value>(it->second);
}
[[nodiscard]] std::optional<eve::Value> resource(std::string_view key) const override {
auto it = resources.find(std::string(key));
return it == resources.end() ? std::nullopt : std::optional<eve::Value>(it->second);
}
[[nodiscard]] std::optional<eve::Value> state(std::string_view key) const override {
auto it = states.find(std::string(key));
return it == states.end() ? std::nullopt : std::optional<eve::Value>(it->second);
}
[[nodiscard]] std::optional<bool> authority(std::string_view scope) const override {
auto it = authorities.find(std::string(scope));
return it == authorities.end() ? std::nullopt : std::optional<bool>(it->second);
}
[[nodiscard]] std::optional<eve::decision::ConditionResult> policy(std::string_view name,
const eve::Value&) const override {
auto it = policies.find(std::string(name));
return it == policies.end() ? std::nullopt : std::optional<eve::decision::ConditionResult>(it->second);
}
};
} // namespace
TEST_CASE("decision.condition.supportsAllLeafNodes") {
using namespace eve::decision;
TestContext context;
context.values.emplace("level", 7);
context.tags.emplace("fire", true);
context.attributes.emplace("power", 12.0);
context.resources.emplace("mana", 20.0);
context.states.emplace("phase", "ready");
context.authorities.emplace("cast", true);
context.policies.emplace("safe", ConditionResult::success(eve::Value(true)));
const Condition condition = Condition::all({
Condition::compare("level", CompareOperator::GreaterEqual, 7),
Condition::hasTag("fire"),
Condition::hasAttribute("power"),
Condition::hasResource("mana"),
Condition::stateEquals("phase", "ready"),
Condition::authorityCheck("cast"),
Condition::policyCall("safe"),
});
const auto result = condition.evaluate(context);
REQUIRE(result.passed());
CHECK_EQ(static_cast<int>(result.reasonCode()), static_cast<int>(ConditionReasonCode::Passed));
CHECK(result.details().isObject());
}
TEST_CASE("decision.condition.compoundsExplainFailureWithoutMutation") {
using namespace eve::decision;
TestContext context;
context.values.emplace("level", 2);
context.tags.emplace("fire", false);
context.states.emplace("phase", "ready");
const auto before = context.values;
const auto any =
Condition::any({Condition::compare("level", CompareOperator::Greater, 9), Condition::hasTag("fire")});
const auto anyResult = any.evaluate(context);
REQUIRE(!anyResult.passed());
CHECK_EQ(static_cast<int>(anyResult.reasonCode()), static_cast<int>(ConditionReasonCode::NoChildPassed));
CHECK(anyResult.details().isObject());
const auto negated = Condition::not_(Condition::stateEquals("phase", "ready"));
const auto notResult = negated.evaluate(context);
REQUIRE(!notResult.passed());
CHECK_EQ(static_cast<int>(notResult.reasonCode()), static_cast<int>(ConditionReasonCode::Negated));
CHECK_EQ(context.values, before);
}
TEST_CASE("decision.condition.scriptDeclarationIsExplicit") {
using namespace eve::decision;
ScriptConditionDeclaration declaration;
declaration.name = "game.canCast";
declaration.dependencies = {"actor.mana", "world.tick"};
declaration.determinism = DeterminismLevel::TickDeterministic;
const auto condition = Condition::policyCall("game.canCast", eve::Value::Object{{"mode", "safe"}}, declaration);
REQUIRE(condition.isValid());
REQUIRE(condition.scriptDeclaration().has_value());
CHECK_EQ(condition.scriptDeclaration()->name, "game.canCast");
CHECK_EQ(condition.scriptDeclaration()->dependencies.size(), 2);
CHECK_EQ(static_cast<int>(condition.scriptDeclaration()->determinism),
static_cast<int>(DeterminismLevel::TickDeterministic));
}
TEST_CASE("decision.conditionUnavailableIsDifferentFromFalse") {
using namespace eve::decision;
TestContext context;
const auto tag = Condition::hasTag("missing").evaluate(context);
const auto compare = Condition::compare("missing", CompareOperator::Equal, 1).evaluate(context);
REQUIRE(!tag.passed());
REQUIRE(!compare.passed());
CHECK_EQ(static_cast<int>(tag.reasonCode()), static_cast<int>(ConditionReasonCode::TagUnavailable));
CHECK_EQ(static_cast<int>(compare.reasonCode()), static_cast<int>(ConditionReasonCode::MissingValue));
}