// SPDX-License-Identifier: Apache-2.0 // Copyright Pionix GmbH and Contributors to EVerest #include "ErrorHandling.hpp" #include "EvseManagerStub.hpp" #include "evse_board_supportIntfStub.hpp" #include #include #include namespace Everest::error { bool operator<(const Error& lhs, const Error& rhs) { return lhs.type < rhs.type; } bool operator==(const Error& lhs, const Error& rhs) { return (lhs.type == rhs.type) && (lhs.sub_type == rhs.sub_type); } } // namespace Everest::error namespace { struct ErrorHandlingTest : public module::ErrorHandling { using module::ErrorHandling::ErrorHandling; using module::ErrorHandling::raise_inoperative_error; }; struct ErrorDatabaseStub : public Everest::error::ErrorDatabase { using Error = Everest::error::Error; using ErrorPtr = Everest::error::ErrorPtr; using ErrorFilter = Everest::error::ErrorFilter; std::list& active_errors; ErrorDatabaseStub(std::list& active) : Everest::error::ErrorDatabase(), active_errors(active) { } virtual void add_error(ErrorPtr error) { } virtual std::list get_errors(const std::list& filters) const { std::list result; for (const auto& error : active_errors) { result.push_back(std::make_shared(error)); } return result; } virtual std::list edit_errors(const std::list& filters, EditErrorFunc edit_func) { return {}; } virtual std::list remove_errors(const std::list& filters) { return {}; } }; struct EvseManagerModuleAdapterStub : public module::stub::EvseManagerModuleAdapter { std::shared_ptr error_type_map; std::shared_ptr error_database; std::list error_list; std::list active_errors; EvseManagerModuleAdapterStub() : module::stub::EvseManagerModuleAdapter(), error_type_map(std::make_shared()), error_database(std::make_shared(active_errors)), error_list{} { } virtual std::shared_ptr get_error_manager_impl_fn(const std::string& str) { return std::make_shared( error_type_map, std::make_shared(), error_list, [this](const Everest::error::Error& error) { if (error_raise.find(error.type) == error_raise.end()) { throw std::runtime_error("Error type " + error.type + " not found"); } error_raise[error.type](error); active_errors.push_back(error); }, [this](const Everest::error::Error& error) { if (error_raise.find(error.type) == error_raise.end()) { throw std::runtime_error("Error type " + error.type + " not found"); } error_clear[error.type](error); active_errors.remove(error); }, false); } virtual std::shared_ptr get_error_manager_req_fn(const Requirement& req) { return std::make_shared( error_type_map, std::make_shared(), error_list, [this](const Everest::error::ErrorType& error_type, const Everest::error::ErrorCallback& callback, const Everest::error::ErrorCallback& clear_callback) { error_raise[error_type] = callback; error_clear[error_type] = clear_callback; }); } virtual std::shared_ptr get_error_factory_fn(const std::string&) { return std::make_shared(error_type_map); } virtual std::shared_ptr get_error_state_monitor_impl_fn(const std::string&) { return std::make_shared(error_database); } }; struct ErrorHandlingTesting : public testing::Test { EvseManagerModuleAdapterStub adapter; std::map error_types_map; std::unique_ptr r_bsp{}; const std::vector> r_hlc{}; const std::vector> r_connector_lock{}; const std::vector> r_ac_rcd{}; std::unique_ptr p_evse{}; const std::vector> _r_imd{}; const std::vector> _r_powersupply{}; const std::vector> _r_powermeter{}; const std::vector> _r_over_voltage_monitor{}; std::unique_ptr error_handler; void construct(bool _inoperative_error_use_vendor_id) { error_types_map = {{"evse_board_support/VendorWarning", "warning"}, {"evse_manager/Inoperative", "inoperative"}}; adapter.error_type_map->load_error_types_map(error_types_map); adapter.active_errors.clear(); for (const auto& [error, description] : error_types_map) { adapter.error_list.push_back(error); } r_bsp = std::make_unique(adapter); p_evse = std::make_unique(&adapter, "manager"); error_handler = std::make_unique(r_bsp, r_hlc, r_connector_lock, r_ac_rcd, p_evse, _r_imd, _r_powersupply, _r_powermeter, _r_over_voltage_monitor, _inoperative_error_use_vendor_id); } static constexpr std::string_view default_description{"description"}; static constexpr std::string_view default_no_vendor_id{"EVerest"}; static constexpr std::string_view default_vendor_id{"vendor_id"}; Everest::error::Error test_description(const std::string& type, const std::string& subtype) { Everest::error::Error error{}; error.type = type; error.sub_type = subtype; error.message = "message"; error.description = default_description; error.vendor_id = default_vendor_id; error_handler->raise_inoperative_error(error); EXPECT_EQ(adapter.active_errors.size(), 1); auto& active = adapter.active_errors.front(); EXPECT_EQ(active.type, "evse_manager/Inoperative"); EXPECT_EQ(active.sub_type, ""); EXPECT_EQ(active.message, error.type); return active; } }; TEST_F(ErrorHandlingTesting, NoType) { construct(true); auto error = test_description("", "sub-type"); EXPECT_EQ(error.description, default_description); EXPECT_EQ(error.vendor_id, default_vendor_id); construct(false); error = test_description("", "sub-type"); EXPECT_EQ(error.description, default_description); EXPECT_EQ(error.vendor_id, default_no_vendor_id); } TEST_F(ErrorHandlingTesting, TypeNoSlashA) { construct(true); auto error = test_description("type", "sub-type"); EXPECT_EQ(error.description, default_description); EXPECT_EQ(error.vendor_id, default_vendor_id); construct(false); error = test_description("type", "sub-type"); EXPECT_EQ(error.description, default_description); EXPECT_EQ(error.vendor_id, default_no_vendor_id); } TEST_F(ErrorHandlingTesting, TypeNoSlashB) { construct(true); auto error = test_description("type", ""); EXPECT_EQ(error.description, default_description); EXPECT_EQ(error.vendor_id, default_vendor_id); construct(false); error = test_description("type", ""); EXPECT_EQ(error.description, default_description); EXPECT_EQ(error.vendor_id, default_no_vendor_id); } TEST_F(ErrorHandlingTesting, TypeSlashEndA) { construct(true); auto error = test_description("type/", "sub-type"); EXPECT_EQ(error.description, "type/sub-type"); EXPECT_EQ(error.vendor_id, default_vendor_id); construct(false); error = test_description("type/", "sub-type"); EXPECT_EQ(error.description, "type/sub-type"); EXPECT_EQ(error.vendor_id, default_no_vendor_id); } TEST_F(ErrorHandlingTesting, TypeSlashEndB) { construct(true); auto error = test_description("type/", ""); EXPECT_EQ(error.description, "type"); EXPECT_EQ(error.vendor_id, default_vendor_id); construct(false); error = test_description("type/", ""); EXPECT_EQ(error.description, "type"); EXPECT_EQ(error.vendor_id, default_no_vendor_id); } TEST_F(ErrorHandlingTesting, TypeSlashBeginA) { construct(true); auto error = test_description("/type", "sub-type"); EXPECT_EQ(error.description, "type/sub-type"); EXPECT_EQ(error.vendor_id, default_vendor_id); construct(false); error = test_description("/type", "sub-type"); EXPECT_EQ(error.description, "type/sub-type"); EXPECT_EQ(error.vendor_id, default_no_vendor_id); } TEST_F(ErrorHandlingTesting, TypeSlashBeginB) { construct(true); auto error = test_description("/type", ""); EXPECT_EQ(error.description, "type"); EXPECT_EQ(error.vendor_id, default_vendor_id); construct(false); error = test_description("/type", ""); EXPECT_EQ(error.description, "type"); EXPECT_EQ(error.vendor_id, default_no_vendor_id); } TEST_F(ErrorHandlingTesting, TypeFullA) { construct(true); auto error = test_description("module/type", "sub-type"); EXPECT_EQ(error.description, "type/sub-type"); EXPECT_EQ(error.vendor_id, default_vendor_id); construct(false); error = test_description("module/type", "sub-type"); EXPECT_EQ(error.description, "type/sub-type"); EXPECT_EQ(error.vendor_id, default_no_vendor_id); } TEST_F(ErrorHandlingTesting, TypeFullB) { construct(true); auto error = test_description("module/type", ""); EXPECT_EQ(error.description, "type"); EXPECT_EQ(error.vendor_id, default_vendor_id); construct(false); error = test_description("module/type", ""); EXPECT_EQ(error.description, "type"); EXPECT_EQ(error.vendor_id, default_no_vendor_id); } TEST_F(ErrorHandlingTesting, TypeFullC) { construct(true); auto error = test_description("module/type/", "sub-type"); EXPECT_EQ(error.description, "type/sub-type"); EXPECT_EQ(error.vendor_id, default_vendor_id); construct(false); error = test_description("module/type/", "sub-type"); EXPECT_EQ(error.description, "type/sub-type"); EXPECT_EQ(error.vendor_id, default_no_vendor_id); } TEST_F(ErrorHandlingTesting, TypeFullD) { construct(true); auto error = test_description("module/type/", ""); EXPECT_EQ(error.description, "type"); EXPECT_EQ(error.vendor_id, default_vendor_id); construct(false); error = test_description("module/type/", ""); EXPECT_EQ(error.description, "type"); EXPECT_EQ(error.vendor_id, default_no_vendor_id); } TEST_F(ErrorHandlingTesting, NoVendorId) { Everest::error::Error raised_error{}; raised_error.type = "module/type"; raised_error.sub_type = "sub-type"; raised_error.message = "message"; raised_error.description = default_description; raised_error.vendor_id = ""; construct(true); error_handler->raise_inoperative_error(raised_error); EXPECT_EQ(adapter.active_errors.size(), 1); auto& error = adapter.active_errors.front(); EXPECT_EQ(error.type, "evse_manager/Inoperative"); EXPECT_EQ(error.sub_type, ""); EXPECT_EQ(error.message, raised_error.type); EXPECT_EQ(error.description, "type/sub-type"); EXPECT_EQ(error.vendor_id, default_no_vendor_id); construct(false); error_handler->raise_inoperative_error(raised_error); EXPECT_EQ(adapter.active_errors.size(), 1); error = adapter.active_errors.front(); EXPECT_EQ(error.type, "evse_manager/Inoperative"); EXPECT_EQ(error.sub_type, ""); EXPECT_EQ(error.message, raised_error.type); EXPECT_EQ(error.description, "type/sub-type"); EXPECT_EQ(error.vendor_id, default_no_vendor_id); } TEST_F(ErrorHandlingTesting, IsActive) { Everest::error::Error first_error{}; first_error.type = "module/type"; first_error.sub_type = "sub-type"; first_error.message = "message"; first_error.description = "description"; first_error.vendor_id = "vendor_id"; construct(true); EXPECT_FALSE(p_evse->error_state_monitor->is_error_active("evse_manager/Inoperative", "")); error_handler->raise_inoperative_error(first_error); EXPECT_EQ(adapter.active_errors.size(), 1); auto error = adapter.active_errors.front(); EXPECT_EQ(error.type, "evse_manager/Inoperative"); EXPECT_EQ(error.sub_type, ""); EXPECT_EQ(error.message, first_error.type); EXPECT_EQ(error.description, "type/sub-type"); EXPECT_EQ(error.vendor_id, first_error.vendor_id); EXPECT_TRUE(p_evse->error_state_monitor->is_error_active("evse_manager/Inoperative", "")); Everest::error::Error raised_error{}; raised_error.type = "module/new-type"; raised_error.sub_type = "new-sub-type"; raised_error.message = "new-message"; raised_error.description = "new-description"; raised_error.vendor_id = "new-vendor_id"; // should not be a new error error_handler->raise_inoperative_error(raised_error); EXPECT_EQ(adapter.active_errors.size(), 1); error = adapter.active_errors.front(); // should be first error EXPECT_EQ(error.type, "evse_manager/Inoperative"); EXPECT_EQ(error.sub_type, ""); EXPECT_EQ(error.message, first_error.type); EXPECT_EQ(error.description, "type/sub-type"); EXPECT_EQ(error.vendor_id, first_error.vendor_id); } } // namespace