Files
cariflex/tools/EVerest-main/modules/API/RpcApi/tests/rpc/RpcHandlerTests.cpp
Eric F d398a6ced2 Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1)
- OpenOCPP extracted (firmware OCPP 1.6J/2.0.1)
- ShapeShifter library installed (pip install -e)
- ShapeShifter specification extracted
- EVerest extracted

TODO updated with progress
2026-06-08 00:38:27 -04:00

878 lines
44 KiB
C++

// SPDX-License-Identifier: Apache-2.0
// Copyright chargebyte GmbH and Contributors to EVerest
#include <gtest/gtest.h>
#include <thread>
#include "../data/DataStore.hpp"
#include "../helpers/ErrorHandler.hpp"
#include "../helpers/JsonRpcUtils.hpp"
#include "../helpers/RequestHandlerDummy.hpp"
#include "../helpers/WebSocketTestClient.hpp"
#include "../rpc/RpcHandler.hpp"
#include "../server/WebsocketServer.hpp"
using namespace server;
using namespace rpc;
using namespace json_rpc_utils;
class RpcHandlerTest : public ::testing::Test {
protected:
int test_port = 8080;
void SetUp() override {
// Start the WebSocket server
m_websocket_server = std::make_unique<server::WebSocketServer>(false, test_port, "lo");
lws_set_log_level(LLL_ERR | LLL_WARN, NULL);
// Create RpcHandler instance. Move the transport interfaces and request handler to the RpcHandler
std::vector<std::shared_ptr<server::TransportInterface>> transport_interfaces;
request_handler = std::make_unique<RequestHandlerDummy>(data_store);
transport_interfaces.push_back(std::shared_ptr<server::TransportInterface>(std::move(m_websocket_server)));
m_rpc_handler =
std::make_unique<RpcHandler>(std::move(transport_interfaces), data_store, std::move(request_handler));
m_rpc_handler->start_server();
initialize_data_store();
}
void TearDown() override {
m_rpc_handler->stop_server();
}
void initialize_data_store() {
// Set up the data store with test data
RPCDataTypes::ChargerInfoObj charger_info;
charger_info.firmware_version = "1.0.0";
charger_info.model = "Test Charger";
charger_info.serial = "123456789";
charger_info.vendor = "Test Vendor";
data_store.chargerinfo.set_data(charger_info);
data_store.everest_version = "2025.1.0";
// Properly initialize EVSE objects
data_store.evses.emplace_back(std::make_unique<data::DataStoreEvse>());
}
void send_req_and_validate_res(WebSocketTestClient& client, const nlohmann::json& request,
const nlohmann::json& expected_response,
bool (*cmp_f)(const nlohmann::json&, const nlohmann::json&) = nullptr) {
// Send the request
client.send(request.dump());
// Wait for the response
std::string data = client.wait_for_data(std::chrono::seconds(1));
// Check if the response is not empty
ASSERT_FALSE(data.empty());
nlohmann::json response = nlohmann::json::parse(data);
// Check if the response is valid
if (cmp_f != nullptr) {
// Compare the response with the expected response using the provided comparison function
bool res = cmp_f(response, expected_response);
if (!res) {
// If the comparison fails, print the response and expected response for debugging
EVLOG_error << "Expected equality of these values: response: " << response.dump();
EVLOG_error << "Expected response: " << expected_response.dump();
}
ASSERT_TRUE(res);
} else {
// Compare the response with the expected response
ASSERT_EQ(response, expected_response);
}
}
std::unique_ptr<server::WebSocketServer> m_websocket_server;
std::unique_ptr<rpc::RpcHandler> m_rpc_handler;
// Condition variable to wait for response
std::condition_variable cv;
std::mutex cv_mutex;
// Data store object used to manage and access charger-related data, including EVSEs, connectors, and charger info.
data::DataStoreCharger data_store;
// Dummy request handler. Needed to create the responses of synchronous requests
std::unique_ptr<request_interface::RequestHandlerInterface> request_handler;
};
// Test: Connect to WebSocket server and check if API.Hello timeout occurs
TEST_F(RpcHandlerTest, ApiHelloTimeout) {
WebSocketTestClient client("localhost", test_port);
ASSERT_TRUE(client.connect());
ASSERT_TRUE(client.wait_until_connected(std::chrono::milliseconds(100)));
// Wait for the client hello timeout
EVLOG_info << "Waiting for client hello timeout...";
std::this_thread::sleep_for(std::chrono::seconds(CLIENT_HELLO_TIMEOUT) + std::chrono::milliseconds(100));
// Check if the client is still connected
ASSERT_FALSE(client.is_connected());
}
// Test: Connect to WebSocket server and send API.Hello request
TEST_F(RpcHandlerTest, ApiHelloReq) {
WebSocketTestClient client("localhost", test_port);
ASSERT_TRUE(client.connect());
ASSERT_TRUE(client.wait_until_connected(std::chrono::milliseconds(100)));
// Set up the expected response
RPCDataTypes::HelloResObj result;
result.authentication_required = false;
result.api_version = API_VERSION;
result.charger_info = data_store.chargerinfo.get_data().value();
result.everest_version = data_store.everest_version;
nlohmann::json expected_response = {{"jsonrpc", JSON_RPC_SPEC_VERSION}, {"result", result}, {"id", 1}};
// Send Api.Hello request
client.send_api_hello_req();
// Wait for the response
std::string data = client.wait_for_data(std::chrono::seconds(1));
// Check if the response is not empty
ASSERT_FALSE(data.empty());
// Check if the response is valid
nlohmann::json response = nlohmann::json::parse(data);
ASSERT_EQ(response, expected_response);
// Check if the client is still connected
ASSERT_TRUE(client.wait_until_connected(std::chrono::milliseconds(100)));
}
// Test: Connect to WebSocket server and send EVSEInfo request
TEST_F(RpcHandlerTest, ChargePointGetEVSEInfosReq) {
WebSocketTestClient client("localhost", test_port);
ASSERT_TRUE(client.connect());
ASSERT_TRUE(client.wait_until_connected(std::chrono::milliseconds(100)));
// Set up the data store with test data
RPCDataTypes::ChargePointGetEVSEInfosResObj result; // Expected response
data_store.evses.emplace_back(std::make_unique<data::DataStoreEvse>());
data_store.evses.emplace_back(std::make_unique<data::DataStoreEvse>());
RPCDataTypes::EVSEInfoObj evse_info;
evse_info.index = 1;
evse_info.available_connectors.emplace_back();
evse_info.available_connectors[0].index = 1;
evse_info.available_connectors[0].type = types::json_rpc_api::ConnectorTypeEnum::cCCS2;
evse_info.available_connectors.emplace_back();
evse_info.available_connectors[1].index = 2;
evse_info.available_connectors[1].type = types::json_rpc_api::ConnectorTypeEnum::cCCS1;
evse_info.description = "Test EVSE 1";
result.error = RPCDataTypes::ResponseErrorEnum::NoError; ///< No error
// Set up request and expected response
nlohmann::json charge_point_get_evse_infos_req = create_json_rpc_request("ChargePoint.GetEVSEInfos", {}, 1);
nlohmann::json expected_error_no_data = {
{"error", response_error_enum_to_string(RPCDataTypes::ResponseErrorEnum::ErrorNoDataAvailable)}};
// Send Api.Hello request
client.send_api_hello_req();
client.wait_for_data(std::chrono::seconds(1));
// Send ChargePoint.GetEVSEInfos request and validate response, no data available
send_req_and_validate_res(client, charge_point_get_evse_infos_req, expected_error_no_data,
is_key_value_in_json_rpc_result);
// Set up the data store with test data
data_store.evses[0]->evseinfo.set_data(evse_info);
result.infos.push_back(evse_info);
evse_info.index = 2;
evse_info.description = "Test EVSE 2";
data_store.evses[1]->evseinfo.set_data(evse_info);
result.infos.push_back(evse_info);
// Set up expected response
nlohmann::json expected_response = create_json_rpc_response(result, 1);
// Send ChargePoint.GetEVSEInfos request and validate response
send_req_and_validate_res(client, charge_point_get_evse_infos_req, expected_response);
}
// Test: Connect to WebSocket server and send ChargePoint.GetActiveErrors request
TEST_F(RpcHandlerTest, ChargePointGetActiveErrorsReq) {
WebSocketTestClient client("localhost", test_port);
ASSERT_TRUE(client.connect());
ASSERT_TRUE(client.wait_until_connected(std::chrono::milliseconds(100)));
// Set up the data store with test data
RPCDataTypes::EVSEInfoObj evse_info;
evse_info.index = 1; ///< Unique identifier
evse_info.available_connectors.emplace_back();
evse_info.available_connectors[0].index = 1;
evse_info.available_connectors[0].type = types::json_rpc_api::ConnectorTypeEnum::cCCS2;
evse_info.description = "Test EVSE 1";
data_store.evses[0]->evseinfo.set_data(evse_info);
// Add a second EVSE with a different index
data_store.evses.emplace_back(std::make_unique<data::DataStoreEvse>());
evse_info.index = 2; ///< Unique identifier
evse_info.description = "Test EVSE 2";
data_store.evses[1]->evseinfo.set_data(evse_info);
// Set up the EVSE status for both EVSEs
RPCDataTypes::EVSEStatusObj evse_status1, evse_status2;
evse_status1.error_present = false;
evse_status2.error_present = false;
data_store.evses[0]->evsestatus.set_data(evse_status1);
data_store.evses[1]->evsestatus.set_data(evse_status2);
types::json_rpc_api::ErrorObj error0, error1, error2;
error0.origin.evse_index = 1;
error0.origin.connector_index = 0;
error0.origin.module_id = "evse_1";
error0.origin.implementation_id = "board_support";
error0.message = "Test error message";
error0.description = "Test error description";
error0.uuid = "6db8758b-194d-48e1-99af-c8f0b1d2e3f3";
error0.severity = types::json_rpc_api::Severity::Low;
error0.timestamp = "2025-01-01T12:00:00Z";
error0.type = "TestErrorType";
error1.origin.evse_index = 2;
error1.origin.connector_index = 1;
error1.origin.module_id = "evse_2";
error1.origin.implementation_id = "board_support";
error1.message = "Test error message";
error1.description = "Test error description";
error1.uuid = "7db8758b-194d-48e1-99af-c8f0b1d2e3f4";
error1.severity = types::json_rpc_api::Severity::Medium;
error1.timestamp = "2025-01-01T12:00:00Z";
error1.type = "TestErrorType";
error2.origin.evse_index = 2;
error2.origin.connector_index = 1;
error2.origin.module_id = "evse_2";
error2.origin.implementation_id = "board_support";
error2.message = "Another test error message";
error2.description = "Another test error description";
error2.uuid = "8db8758b-194d-48e1-99af-c8f0b1d2e3f5";
error2.severity = types::json_rpc_api::Severity::High;
error2.timestamp = "2025-01-01T12:00:01Z";
error2.type = "AnotherTestErrorType";
// Set up the data store with test data
RPCDataTypes::ChargePointGetActiveErrorsResObj result; // Expected response
result.active_errors.push_back(error1);
result.active_errors.push_back(error2);
result.error = RPCDataTypes::ResponseErrorEnum::NoError; ///< No error
// Set up request and expected response
nlohmann::json charge_point_get_active_errors_req = create_json_rpc_request("ChargePoint.GetActiveErrors", {}, 1);
nlohmann::json expected_response = create_json_rpc_response(result, 1);
// Send Api.Hello request
client.send_api_hello_req();
client.wait_for_data(std::chrono::seconds(1));
// Raise the error in the data store for the first EVSE
helpers::handle_error_raised(data_store, error0);
// Check if the error is set to present in the EVSE status
auto tmp_evse_store_1 = data_store.get_evse_store(1);
ASSERT_TRUE(tmp_evse_store_1 != nullptr);
ASSERT_TRUE(tmp_evse_store_1->evsestatus.get_data().value().error_present);
// Clear the error in the data store for the first EVSE
helpers::handle_error_cleared(data_store, error0);
// Check if the error is cleared in the EVSE status
ASSERT_FALSE(tmp_evse_store_1->evsestatus.get_data().value().error_present);
// Raise the second error in the data store for the second EVSE
helpers::handle_error_raised(data_store, error1);
helpers::handle_error_raised(data_store, error2);
// Check if error is set to present in the EVSE status
ASSERT_FALSE(tmp_evse_store_1->evsestatus.get_data().value().error_present);
auto tmp_evse_store_2 = data_store.get_evse_store(2);
ASSERT_TRUE(tmp_evse_store_2 != nullptr);
ASSERT_TRUE(tmp_evse_store_2->evsestatus.get_data().value().error_present);
// Send ChargePoint.GetActiveErrors request and validate response
send_req_and_validate_res(client, charge_point_get_active_errors_req, expected_response);
// Clear the errors for the second EVSE
helpers::handle_error_cleared(data_store, error1);
// Check if the error is still present in the EVSE status
ASSERT_TRUE(tmp_evse_store_2->evsestatus.get_data().value().error_present);
// Clear the second error
helpers::handle_error_cleared(data_store, error2);
// Check if error is cleared in the EVSE status
ASSERT_FALSE(tmp_evse_store_1->evsestatus.get_data().value().error_present);
ASSERT_FALSE(tmp_evse_store_2->evsestatus.get_data().value().error_present);
}
// Test: Connect to WebSocket server and send EVSE.Infos request with valid and invalid index
TEST_F(RpcHandlerTest, EvseGetEVSEInfosReq) {
WebSocketTestClient client("localhost", test_port);
ASSERT_TRUE(client.connect());
ASSERT_TRUE(client.wait_until_connected(std::chrono::milliseconds(100)));
// Set up requests
nlohmann::json evse_get_evse_infos_req_1 = create_json_rpc_request("EVSE.GetInfo", {{"evse_index", 1}}, 1);
nlohmann::json evse_get_evse_infos_req_2 = create_json_rpc_request("EVSE.GetInfo", {{"evse_index", 2}}, 1);
nlohmann::json evse_get_infos_req_invalid_index = create_json_rpc_request("EVSE.GetInfo", {{"evse_index", 99}}, 1);
// Set up the data store with test data
RPCDataTypes::EVSEInfoObj evse_info;
evse_info.index = 1; ///< Unique identifier
evse_info.available_connectors.emplace_back();
evse_info.available_connectors[0].index = 1;
evse_info.available_connectors[0].type = types::json_rpc_api::ConnectorTypeEnum::cCCS2;
evse_info.available_connectors.emplace_back();
evse_info.available_connectors[1].index = 2;
evse_info.available_connectors[1].type = types::json_rpc_api::ConnectorTypeEnum::cCCS1;
evse_info.description = "Test EVSE 1";
data_store.evses[0]->evseinfo.set_data(evse_info);
// Expected response 1
RPCDataTypes::EVSEGetInfoResObj result_1;
result_1.info = evse_info;
result_1.error = RPCDataTypes::ResponseErrorEnum::NoError; ///< No error
// Set up the second EVSE info
evse_info.index = 2;
evse_info.available_connectors[0].type = types::json_rpc_api::ConnectorTypeEnum::cType2;
evse_info.available_connectors[1].type = types::json_rpc_api::ConnectorTypeEnum::sType2;
data_store.evses.emplace_back(std::make_unique<data::DataStoreEvse>());
data_store.evses[1]->evseinfo.set_data(evse_info);
// Set up the expected responses
nlohmann::json expected_response_index_1 = create_json_rpc_response(result_1, 1);
// Expected response 2
RPCDataTypes::EVSEGetInfoResObj result_2;
result_2.info = evse_info;
result_2.error = RPCDataTypes::ResponseErrorEnum::NoError; ///< No error
nlohmann::json expected_response_index_2 = create_json_rpc_response(result_2, 1);
// Expected error object in case of invalid ID
nlohmann::json expected_error = {
{"error", response_error_enum_to_string(RPCDataTypes::ResponseErrorEnum::ErrorInvalidEVSEIndex)}};
// Send Api.Hello request
client.send_api_hello_req();
client.wait_for_data(std::chrono::seconds(1));
// Send EVSE.GetEVSEInfos request 1 and validate response
send_req_and_validate_res(client, evse_get_evse_infos_req_1, expected_response_index_1);
// Send EVSE.GetEVSEInfos request 2 and validate response
send_req_and_validate_res(client, evse_get_evse_infos_req_2, expected_response_index_2);
// Send EVSE.GetEVSEInfos request with invalid ID and validate response
send_req_and_validate_res(client, evse_get_infos_req_invalid_index, expected_error,
is_key_value_in_json_rpc_result);
}
// Test: Connect to WebSocket server and send Evse.GetStatusReq request with valid and invalid index
TEST_F(RpcHandlerTest, EvseGetStatusReq) {
WebSocketTestClient client("localhost", test_port);
ASSERT_TRUE(client.connect());
ASSERT_TRUE(client.wait_until_connected(std::chrono::milliseconds(100)));
// Set up the requests
nlohmann::json evse_get_status_req_valid_index = create_json_rpc_request("EVSE.GetStatus", {{"evse_index", 1}}, 1);
nlohmann::json evse_get_status_req_invalid_index =
create_json_rpc_request("EVSE.GetStatus", {{"evse_index", 99}}, 1);
// Set up the data store with test data
RPCDataTypes::EVSEInfoObj evse_info;
evse_info.index = 1; ///< Unique identifier
data_store.evses[0]->evseinfo.set_data(evse_info);
RPCDataTypes::EVSEStatusObj evse_status;
evse_status.charged_energy_wh = 123.45;
evse_status.discharged_energy_wh = 123.45;
evse_status.charging_duration_s = 600;
evse_status.charging_allowed = true;
evse_status.available = true;
evse_status.active_connector_index = 1;
evse_status.error_present = false;
evse_status.charge_protocol = types::json_rpc_api::ChargeProtocolEnum::ISO15118; ///< charge_protocol
evse_status.state = types::json_rpc_api::EVSEStateEnum::Charging;
evse_status.ac_charge_status.emplace().evse_active_phase_count = 3;
// Set up the expected responses
RPCDataTypes::EVSEGetStatusResObj res_valid_id;
res_valid_id.status = evse_status;
res_valid_id.error = RPCDataTypes::ResponseErrorEnum::NoError;
nlohmann::json expected_error_no_data = {
{"error", response_error_enum_to_string(RPCDataTypes::ResponseErrorEnum::ErrorNoDataAvailable)}};
nlohmann::json res_obj_invalid_index = {
{"error", response_error_enum_to_string(RPCDataTypes::ResponseErrorEnum::ErrorInvalidEVSEIndex)}};
nlohmann::json expected_response = create_json_rpc_response(res_valid_id, 1);
// Send Api.Hello request
client.send_api_hello_req();
client.wait_for_data(std::chrono::seconds(1));
// Send EVSE.GetStatus request with valid ID, but no data available
send_req_and_validate_res(client, evse_get_status_req_valid_index, expected_error_no_data,
is_key_value_in_json_rpc_result);
// Set the EVSE status in the data store
data_store.evses[0]->evsestatus.set_data(evse_status);
// Send EVSE.GetStatus request with valid ID
send_req_and_validate_res(client, evse_get_status_req_valid_index, expected_response);
// Send EVSE.GetStatus request with invalid ID
send_req_and_validate_res(client, evse_get_status_req_invalid_index, res_obj_invalid_index,
is_key_value_in_json_rpc_result);
}
// Test: Connect to WebSocket server and send EVSE.GetHardwareCapabilities request with valid and invalid index
TEST_F(RpcHandlerTest, EvseGetHardwareCapabilitiesReq) {
WebSocketTestClient client("localhost", test_port);
ASSERT_TRUE(client.connect());
ASSERT_TRUE(client.wait_until_connected(std::chrono::milliseconds(100)));
// Set up requests
nlohmann::json evse_get_hardware_capabilities_req_valid_index =
create_json_rpc_request("EVSE.GetHardwareCapabilities", {{"evse_index", 1}}, 1);
nlohmann::json evse_get_hardware_capabilities_req_invalid_index =
create_json_rpc_request("EVSE.GetHardwareCapabilities", {{"evse_index", 99}}, 1);
// Set up the data store with test data
RPCDataTypes::EVSEInfoObj evse_info;
evse_info.index = 1;
data_store.evses[0]->evseinfo.set_data(evse_info);
RPCDataTypes::EVSEGetHardwareCapabilitiesResObj result;
result.error = RPCDataTypes::ResponseErrorEnum::NoError;
result.hardware_capabilities.max_current_A_export = 32.0;
result.hardware_capabilities.max_current_A_import = 16.0;
result.hardware_capabilities.max_phase_count_export = 3;
result.hardware_capabilities.max_phase_count_import = 3;
result.hardware_capabilities.min_current_A_export = 6.0;
result.hardware_capabilities.min_current_A_import = 6.0;
result.hardware_capabilities.min_phase_count_export = 1;
result.hardware_capabilities.min_phase_count_import = 1;
result.hardware_capabilities.phase_switch_during_charging = true;
// Set up the expected responses
nlohmann::json expected_response = create_json_rpc_response(result, 1);
nlohmann::json expected_error_no_data = {
{"error", response_error_enum_to_string(RPCDataTypes::ResponseErrorEnum::ErrorNoDataAvailable)}};
nlohmann::json expected_error_invalid_index = {
{"error", response_error_enum_to_string(RPCDataTypes::ResponseErrorEnum::ErrorInvalidEVSEIndex)}};
// Send Api.Hello request
client.send_api_hello_req();
client.wait_for_data(std::chrono::seconds(1));
// Send EVSE.GetHardwareCapabilities request with valid ID, but no hardware capabilities available
send_req_and_validate_res(client, evse_get_hardware_capabilities_req_valid_index, expected_error_no_data,
is_key_value_in_json_rpc_result);
// Set the hardware capabilities in the data store
data_store.evses[0]->hardwarecapabilities.set_data(result.hardware_capabilities);
// Send EVSE.GetHardwareCapabilities request with valid ID
send_req_and_validate_res(client, evse_get_hardware_capabilities_req_valid_index, expected_response);
// Send EVSE.GetHardwareCapabilities request with invalid ID
send_req_and_validate_res(client, evse_get_hardware_capabilities_req_invalid_index, expected_error_invalid_index,
is_key_value_in_json_rpc_result);
}
// Test: Connect to WebSocket server and send EVSE.SetChargingAllowed request with valid and invalid index
TEST_F(RpcHandlerTest, EvseSetChargingAllowedReq) {
WebSocketTestClient client("localhost", test_port);
ASSERT_TRUE(client.connect());
ASSERT_TRUE(client.wait_until_connected(std::chrono::milliseconds(100)));
// Set up requests
nlohmann::json evse_set_charging_allowed_req_valid_index =
create_json_rpc_request("EVSE.SetChargingAllowed", {{"evse_index", 1}, {"charging_allowed", true}}, 1);
nlohmann::json evse_set_charging_allowed_req_valid_index_false =
create_json_rpc_request("EVSE.SetChargingAllowed", {{"evse_index", 1}, {"charging_allowed", false}}, 1);
nlohmann::json evse_set_charging_allowed_req_invalid_index =
create_json_rpc_request("EVSE.SetChargingAllowed", {{"evse_index", 99}, {"charging_allowed", true}}, 1);
// Set up the data store with test data
RPCDataTypes::EVSEInfoObj evse_info;
evse_info.index = 1;
data_store.evses[0]->evseinfo.set_data(evse_info);
RPCDataTypes::EVSEStatusObj evse_status;
evse_status.available = false;
data_store.evses[0]->evsestatus.set_data(evse_status);
// Set up the expected responses
types::json_rpc_api::ErrorResObj result{RPCDataTypes::ResponseErrorEnum::NoError};
nlohmann::json expected_response = create_json_rpc_response(result, 1);
nlohmann::json expected_error = {
{"error", response_error_enum_to_string(RPCDataTypes::ResponseErrorEnum::ErrorInvalidEVSEIndex)}};
// Send Api.Hello request
client.send_api_hello_req();
client.wait_for_data(std::chrono::seconds(1));
// Send EVSE.SetChargingAllowed request with valid ID
send_req_and_validate_res(client, evse_set_charging_allowed_req_valid_index, expected_response);
// Check if the EVSE status is updated
ASSERT_TRUE(data_store.evses[0]->evsestatus.get_data().has_value());
ASSERT_TRUE(data_store.evses[0]->evsestatus.get_data().value().charging_allowed);
// Send EVSE.SetChargingAllowed request with valid ID and false
send_req_and_validate_res(client, evse_set_charging_allowed_req_valid_index_false, expected_response);
// Check if the EVSE status is updated
ASSERT_TRUE(data_store.evses[0]->evsestatus.get_data().has_value());
ASSERT_FALSE(data_store.evses[0]->evsestatus.get_data().value().charging_allowed);
// Send EVSE.SetChargingAllowed request with invalid ID
send_req_and_validate_res(client, evse_set_charging_allowed_req_invalid_index, expected_error,
is_key_value_in_json_rpc_result);
}
// Test: Connect to WebSocket server and send EVSE.MeterData request with valid and invalid index
TEST_F(RpcHandlerTest, EvseMeterDataReq) {
WebSocketTestClient client("localhost", test_port);
ASSERT_TRUE(client.connect());
ASSERT_TRUE(client.wait_until_connected(std::chrono::milliseconds(100)));
// Set up requests
nlohmann::json evse_meter_data_req_valid_index =
create_json_rpc_request("EVSE.GetMeterData", {{"evse_index", 1}}, 1);
nlohmann::json evse_meter_data_req_invalid_index =
create_json_rpc_request("EVSE.GetMeterData", {{"evse_index", 99}}, 1);
// Set up the data store with test data
RPCDataTypes::EVSEInfoObj evse_info;
evse_info.index = 1;
data_store.evses[0]->evseinfo.set_data(evse_info);
// Configure meter data, but do not set it in the data store
RPCDataTypes::MeterDataObj meter_data{};
meter_data.energy_Wh_import.total = 123.45;
meter_data.timestamp = "2025-06-10T09:51:56Z";
// Set up the expected responses
types::json_rpc_api::EVSEGetMeterDataResObj result{{meter_data}, RPCDataTypes::ResponseErrorEnum::NoError};
nlohmann::json expected_response_no_error = create_json_rpc_response(result, 1);
nlohmann::json expected_error_no_data = {
{"error", response_error_enum_to_string(RPCDataTypes::ResponseErrorEnum::ErrorNoDataAvailable)}};
nlohmann::json expected_error_invalid_index = {
{"error", response_error_enum_to_string(RPCDataTypes::ResponseErrorEnum::ErrorInvalidEVSEIndex)}};
// Send Api.Hello request
client.send_api_hello_req();
client.wait_for_data(std::chrono::seconds(1));
// Send EVSE.MeterData request with valid ID, but no meter data available
send_req_and_validate_res(client, evse_meter_data_req_valid_index, expected_error_no_data,
is_key_value_in_json_rpc_result);
// Set the meter data in the data store
data_store.evses[0]->meterdata.set_data(meter_data);
// Send EVSE.MeterData request with valid ID and meter data available
send_req_and_validate_res(client, evse_meter_data_req_valid_index, expected_response_no_error);
// Send EVSE.MeterData request with invalid ID
send_req_and_validate_res(client, evse_meter_data_req_invalid_index, expected_error_invalid_index,
is_key_value_in_json_rpc_result);
}
// Test: Connect to WebSocket server and send EVSE.SetACCharging request with valid and invalid index
TEST_F(RpcHandlerTest, EvseSetACChargingReq) {
WebSocketTestClient client("localhost", test_port);
ASSERT_TRUE(client.connect());
ASSERT_TRUE(client.wait_until_connected(std::chrono::milliseconds(100)));
// Set up requests
nlohmann::json evse_set_ac_charging_req_valid_index = create_json_rpc_request(
"EVSE.SetACCharging",
{{"evse_index", 1}, {"charging_allowed", true}, {"max_current", 12.3}, {"phase_count", 3}}, 1);
// As long as the method is not implemented, we expect an error response that the method is not implemented
nlohmann::json expected_res = create_json_rpc_error_response(-32601, "method not found: EVSE.SetACCharging", 1);
// Send Api.Hello request
client.send_api_hello_req();
client.wait_for_data(std::chrono::seconds(1));
// Send EVSE.SetACCharging request with valid ID
client.send(evse_set_ac_charging_req_valid_index.dump());
// Wait for the response
std::string received_data = client.wait_for_data(std::chrono::seconds(1), false);
// Check if the response is valid
nlohmann::json response = nlohmann::json::parse(received_data);
ASSERT_EQ(response, expected_res);
}
// Test: Connect to WebSocket server and send EVSE.SetACChargingCurrent request with valid and invalid index
TEST_F(RpcHandlerTest, EvseSetACChargingCurrentReq) {
WebSocketTestClient client("localhost", test_port);
ASSERT_TRUE(client.connect());
ASSERT_TRUE(client.wait_until_connected(std::chrono::milliseconds(100)));
// Set up requests
nlohmann::json evse_set_ac_charging_current_req_valid_index =
create_json_rpc_request("EVSE.SetACChargingCurrent", {{"evse_index", 1}, {"max_current", 12.3}}, 1);
nlohmann::json evse_set_ac_charging_current_req_invalid_index =
create_json_rpc_request("EVSE.SetACChargingCurrent", {{"evse_index", 99}, {"max_current", 12.3}}, 1);
nlohmann::json evse_set_ac_charging_current_req_invalid_max_current =
create_json_rpc_request("EVSE.SetACChargingCurrent", {{"evse_index", 1}, {"max_current", 15.0}}, 1);
// Set up the data store with test data
RPCDataTypes::EVSEInfoObj evse_info{};
evse_info.index = 1;
data_store.evses[0]->evseinfo.set_data(evse_info);
RPCDataTypes::EVSEStatusObj evse_status{};
evse_status.charging_allowed = true;
data_store.evses[0]->evsestatus.set_data(evse_status);
data_store.evses[0]->evsestatus.set_ac_charge_param_evse_max_current(12.3);
// Set up the expected responses
types::json_rpc_api::ErrorResObj result{RPCDataTypes::ResponseErrorEnum::NoError};
nlohmann::json expected_response = create_json_rpc_response(result, 1);
nlohmann::json expected_error_invalid_index = {
{"error", response_error_enum_to_string(RPCDataTypes::ResponseErrorEnum::ErrorInvalidEVSEIndex)}};
nlohmann::json expected_error_invalid_current = {
{"error", response_error_enum_to_string(RPCDataTypes::ResponseErrorEnum::ErrorValuesNotApplied)}};
// Send Api.Hello request
client.send_api_hello_req();
client.wait_for_data(std::chrono::seconds(1));
// Send EVSE.SetACChargingCurrent request with valid ID
send_req_and_validate_res(client, evse_set_ac_charging_current_req_valid_index, expected_response);
// Send EVSE.SetACChargingCurrent request with invalid ID
send_req_and_validate_res(client, evse_set_ac_charging_current_req_invalid_index, expected_error_invalid_index,
is_key_value_in_json_rpc_result);
// Send EVSE.SetACChargingCurrent request with invalid AC charging current
send_req_and_validate_res(client, evse_set_ac_charging_current_req_invalid_max_current,
expected_error_invalid_current, is_key_value_in_json_rpc_result);
}
// Test: Connect to WebSocket server and send EVSE.SetACChargingPhaseCount request with valid and invalid index
TEST_F(RpcHandlerTest, EvseSetACChargingPhaseCountReq) {
WebSocketTestClient client("localhost", test_port);
ASSERT_TRUE(client.connect());
ASSERT_TRUE(client.wait_until_connected(std::chrono::milliseconds(100)));
// Set up requests
nlohmann::json evse_set_ac_charging_phase_count_req_valid_index =
create_json_rpc_request("EVSE.SetACChargingPhaseCount", {{"evse_index", 1}, {"phase_count", 3}}, 1);
nlohmann::json evse_set_ac_charging_phase_count_req_invalid_index =
create_json_rpc_request("EVSE.SetACChargingPhaseCount", {{"evse_index", 99}, {"phase_count", 3}}, 1);
// Set up the data store with test data
RPCDataTypes::EVSEInfoObj evse_info;
evse_info.index = 1;
data_store.evses[0]->evseinfo.set_data(evse_info);
RPCDataTypes::EVSEStatusObj evse_status;
evse_status.available = false;
evse_status.ac_charge_param.emplace();
evse_status.ac_charge_param->evse_max_current = 12.3;
data_store.evses[0]->evsestatus.set_data(evse_status);
RPCDataTypes::EVSEGetHardwareCapabilitiesResObj hw_cap;
hw_cap.error = RPCDataTypes::ResponseErrorEnum::NoError;
hw_cap.hardware_capabilities.max_current_A_export = 32.0;
hw_cap.hardware_capabilities.max_current_A_import = 16.0;
hw_cap.hardware_capabilities.max_phase_count_export = 3;
hw_cap.hardware_capabilities.max_phase_count_import = 3;
hw_cap.hardware_capabilities.min_current_A_export = 6.0;
hw_cap.hardware_capabilities.min_current_A_import = 6.0;
hw_cap.hardware_capabilities.min_phase_count_export = 1;
hw_cap.hardware_capabilities.min_phase_count_import = 1;
hw_cap.hardware_capabilities.phase_switch_during_charging = true;
data_store.evses[0]->hardwarecapabilities.set_data(hw_cap.hardware_capabilities);
// Set up the expected responses
types::json_rpc_api::ErrorResObj result{RPCDataTypes::ResponseErrorEnum::NoError};
nlohmann::json expected_response = create_json_rpc_response(result, 1);
nlohmann::json expected_error = {
{"error", response_error_enum_to_string(RPCDataTypes::ResponseErrorEnum::ErrorInvalidEVSEIndex)}};
// Send Api.Hello request
client.send_api_hello_req();
client.wait_for_data(std::chrono::seconds(1));
// Send EVSE.SetACChargingPhaseCount request with valid ID
send_req_and_validate_res(client, evse_set_ac_charging_phase_count_req_valid_index, expected_response);
// Send EVSE.SetACChargingPhaseCount request with invalid ID
send_req_and_validate_res(client, evse_set_ac_charging_phase_count_req_invalid_index, expected_error,
is_key_value_in_json_rpc_result);
}
// Test: Connect to WebSocket server and send EVSE.SetACChargingPhaseCount request with invalid phases and disabled
// phase switching
TEST_F(RpcHandlerTest, EvseSetACChargingPhaseCountReqBadCases) {
WebSocketTestClient client("localhost", test_port);
ASSERT_TRUE(client.connect());
ASSERT_TRUE(client.wait_until_connected(std::chrono::milliseconds(100)));
// Set up requests
nlohmann::json evse_set_ac_charging_phase_count_req_valid_phase_count =
create_json_rpc_request("EVSE.SetACChargingPhaseCount", {{"evse_index", 1}, {"phase_count", 1}}, 1);
nlohmann::json evse_set_ac_charging_phase_count_req_invalid_phase_count =
create_json_rpc_request("EVSE.SetACChargingPhaseCount", {{"evse_index", 1}, {"phase_count", 2}}, 1);
nlohmann::json evse_set_ac_charging_phase_count_req_out_of_range =
create_json_rpc_request("EVSE.SetACChargingPhaseCount", {{"evse_index", 1}, {"phase_count", 3}}, 1);
// Set up the data store with test data
RPCDataTypes::EVSEInfoObj evse_info;
evse_info.index = 1;
data_store.evses[0]->evseinfo.set_data(evse_info);
RPCDataTypes::EVSEStatusObj evse_status;
evse_status.available = false;
evse_status.ac_charge_param.emplace();
evse_status.ac_charge_param->evse_max_current = 12.3;
evse_status.ac_charge_status.emplace();
evse_status.ac_charge_status->evse_active_phase_count = 1;
data_store.evses[0]->evsestatus.set_data(evse_status);
RPCDataTypes::EVSEGetHardwareCapabilitiesResObj hw_cap;
hw_cap.error = RPCDataTypes::ResponseErrorEnum::NoError;
hw_cap.hardware_capabilities.max_current_A_export = 32.0;
hw_cap.hardware_capabilities.max_current_A_import = 16.0;
hw_cap.hardware_capabilities.max_phase_count_export = 1;
hw_cap.hardware_capabilities.max_phase_count_import = 1;
hw_cap.hardware_capabilities.min_current_A_export = 6.0;
hw_cap.hardware_capabilities.min_current_A_import = 6.0;
hw_cap.hardware_capabilities.min_phase_count_export = 1;
hw_cap.hardware_capabilities.min_phase_count_import = 1;
hw_cap.hardware_capabilities.phase_switch_during_charging = false;
data_store.evses[0]->hardwarecapabilities.set_data(hw_cap.hardware_capabilities);
// Set up the expected responses
types::json_rpc_api::ErrorResObj result{RPCDataTypes::ResponseErrorEnum::NoError};
nlohmann::json expected_no_error = create_json_rpc_response(result, 1);
nlohmann::json expected_invalid_param = {
{"error", response_error_enum_to_string(RPCDataTypes::ResponseErrorEnum::ErrorInvalidParameter)}};
nlohmann::json expected_error_out_of_range = {
{"error", response_error_enum_to_string(RPCDataTypes::ResponseErrorEnum::ErrorOutOfRange)}};
nlohmann::json expected_error_operation_not_supported = {
{"error", response_error_enum_to_string(RPCDataTypes::ResponseErrorEnum::ErrorOperationNotSupported)}};
// Send Api.Hello request
client.send_api_hello_req();
client.wait_for_data(std::chrono::seconds(1));
// Send EVSE.SetACChargingPhaseCount request with phase count. This should not lead to an error, because
// an initialization of the phase count should be still possible.
send_req_and_validate_res(client, evse_set_ac_charging_phase_count_req_valid_phase_count, expected_no_error);
// Try to switch phase count although phase switching is not allowed (phase_switch_during_charging == false)
send_req_and_validate_res(client, evse_set_ac_charging_phase_count_req_invalid_phase_count,
expected_error_operation_not_supported, is_key_value_in_json_rpc_result);
// Send EVSE.SetACChargingPhaseCount request with phase count out of range
// Enable phase switching, because otherwise it returns an ErrorOperationNotSupported error
hw_cap.hardware_capabilities.phase_switch_during_charging = true;
data_store.evses[0]->hardwarecapabilities.set_data(hw_cap.hardware_capabilities);
send_req_and_validate_res(client, evse_set_ac_charging_phase_count_req_out_of_range, expected_error_out_of_range,
is_key_value_in_json_rpc_result);
// Invalid phase count error occurs when phase_count is configured to 2
hw_cap.hardware_capabilities.max_phase_count_export = 3;
data_store.evses[0]->hardwarecapabilities.set_data(hw_cap.hardware_capabilities);
send_req_and_validate_res(client, evse_set_ac_charging_phase_count_req_invalid_phase_count, expected_invalid_param,
is_key_value_in_json_rpc_result);
}
// Test: Connect to WebSocket server and send EVSE.SetDCCharging request with valid and invalid index
TEST_F(RpcHandlerTest, EvseSetDCChargingReq) {
WebSocketTestClient client("localhost", test_port);
ASSERT_TRUE(client.connect());
ASSERT_TRUE(client.wait_until_connected(std::chrono::milliseconds(100)));
// Set up requests
nlohmann::json evse_set_dc_charging_req_valid_index = create_json_rpc_request(
"EVSE.SetDCCharging", {{"evse_index", 1}, {"charging_allowed", true}, {"max_power", 12.3}}, 1);
// As long as the method is not implemented, we expect an error response that the method is not implemented
nlohmann::json expected_res = create_json_rpc_error_response(-32601, "method not found: EVSE.SetDCCharging", 1);
// Send Api.Hello request
client.send_api_hello_req();
client.wait_for_data(std::chrono::seconds(1));
// Send EVSE.SetDCCharging request with valid ID
client.send(evse_set_dc_charging_req_valid_index.dump());
// Wait for the response
std::string received_data = client.wait_for_data(std::chrono::seconds(1), false);
// Check if the response is valid
nlohmann::json response = nlohmann::json::parse(received_data);
ASSERT_EQ(response, expected_res);
}
// Test: Connect to WebSocket server and send EVSE.SetDCChargingPower request with valid and invalid index
TEST_F(RpcHandlerTest, EvseSetDCChargingPowerReq) {
WebSocketTestClient client("localhost", test_port);
ASSERT_TRUE(client.connect());
ASSERT_TRUE(client.wait_until_connected(std::chrono::milliseconds(100)));
// Set up requests
nlohmann::json evse_set_dc_charging_power_req_valid_index =
create_json_rpc_request("EVSE.SetDCChargingPower", {{"evse_index", 1}, {"max_power", 12.3}}, 1);
nlohmann::json evse_set_dc_charging_power_req_invalid_index =
create_json_rpc_request("EVSE.SetDCChargingPower", {{"evse_index", 99}, {"max_power", 12.3}}, 1);
// Set up the data store with test data
RPCDataTypes::EVSEInfoObj evse_info;
evse_info.index = 1;
data_store.evses[0]->evseinfo.set_data(evse_info);
RPCDataTypes::EVSEStatusObj evse_status;
evse_status.available = false;
evse_status.dc_charge_param.emplace();
data_store.evses[0]->evsestatus.set_data(evse_status);
// Set up the expected responses
types::json_rpc_api::ErrorResObj result{RPCDataTypes::ResponseErrorEnum::NoError};
nlohmann::json expected_response = create_json_rpc_response(result, 1);
nlohmann::json expected_error = {
{"error", response_error_enum_to_string(RPCDataTypes::ResponseErrorEnum::ErrorInvalidEVSEIndex)}};
// Send Api.Hello request
client.send_api_hello_req();
client.wait_for_data(std::chrono::seconds(1));
// Send EVSE.SetDCChargingPower request with valid ID
send_req_and_validate_res(client, evse_set_dc_charging_power_req_valid_index, expected_response);
// Send EVSE.SetDCChargingPower request with invalid ID
send_req_and_validate_res(client, evse_set_dc_charging_power_req_invalid_index, expected_error,
is_key_value_in_json_rpc_result);
}
// Test: Connect to WebSocket server and send EVSE.EnableConnector request with valid and invalid index
TEST_F(RpcHandlerTest, EvseEnableConnectorReq) {
WebSocketTestClient client("localhost", test_port);
ASSERT_TRUE(client.connect());
ASSERT_TRUE(client.wait_until_connected(std::chrono::milliseconds(100)));
// Set up requests
nlohmann::json evse_enable_connector_req_valid_index = create_json_rpc_request(
"EVSE.EnableConnector", {{"evse_index", 1}, {"enable", true}, {"priority", 1}, {"connector_index", 1}}, 1);
nlohmann::json evse_enable_connector_req_invalid_index = create_json_rpc_request(
"EVSE.EnableConnector", {{"evse_index", 99}, {"enable", true}, {"priority", 1}, {"connector_index", 1}}, 1);
nlohmann::json evse_enable_connector_req_invalid_connector_index = create_json_rpc_request(
"EVSE.EnableConnector", {{"evse_index", 1}, {"enable", true}, {"priority", 1}, {"connector_index", 99}}, 1);
// Set up the expected responses
types::json_rpc_api::ErrorResObj result{RPCDataTypes::ResponseErrorEnum::NoError};
nlohmann::json expected_response = create_json_rpc_response(result, 1);
nlohmann::json expected_error = {
{"error", response_error_enum_to_string(RPCDataTypes::ResponseErrorEnum::ErrorInvalidEVSEIndex)}};
nlohmann::json expected_error_invalid_connector_index = {
{"error", response_error_enum_to_string(RPCDataTypes::ResponseErrorEnum::ErrorInvalidConnectorIndex)}};
// Set up the data store with test data
RPCDataTypes::EVSEInfoObj evse_info;
evse_info.index = 1;
evse_info.available_connectors.emplace_back();
evse_info.available_connectors[0].index = 1;
evse_info.available_connectors[0].type = types::json_rpc_api::ConnectorTypeEnum::cCCS2;
data_store.evses[0]->evseinfo.set_data(evse_info);
RPCDataTypes::EVSEStatusObj evse_status;
evse_status.available = false;
data_store.evses[0]->evsestatus.set_data(evse_status);
// Send Api.Hello request
client.send_api_hello_req();
client.wait_for_data(std::chrono::seconds(1));
// Send EVSE.EnableConnector request with valid ID
send_req_and_validate_res(client, evse_enable_connector_req_valid_index, expected_response);
// Send EVSE.EnableConnector request with invalid ID
send_req_and_validate_res(client, evse_enable_connector_req_invalid_index, expected_error,
is_key_value_in_json_rpc_result);
// Send EVSE.EnableConnector request with invalid connector ID
send_req_and_validate_res(client, evse_enable_connector_req_invalid_connector_index,
expected_error_invalid_connector_index, is_key_value_in_json_rpc_result);
}
// Test: Connect to WebSocket server and send invalid request
TEST_F(RpcHandlerTest, InvalidRequest) {
WebSocketTestClient client("localhost", test_port);
ASSERT_TRUE(client.connect());
ASSERT_TRUE(client.wait_until_connected(std::chrono::milliseconds(100)));
// Send Api.Hello request
client.send_api_hello_req();
// Wait for the response
client.wait_for_data(std::chrono::seconds(1));
// Send invalid request
nlohmann::json invalid_request = create_json_rpc_request("API.InvalidMethod", {}, 1);
// Expected response
nlohmann::json expected_response = create_json_rpc_error_response(-32601, "method not found: API.InvalidMethod", 1);
// Send invalid request
client.send(invalid_request.dump());
// Wait for the response
std::string received_data = client.wait_for_data(std::chrono::seconds(1), false);
// Check if the response is not empty
ASSERT_FALSE(received_data.empty());
// Check if the response is valid
nlohmann::json response = nlohmann::json::parse(received_data);
ASSERT_EQ(response, expected_response);
}