Files
cariflex/tools/EVerest-main/modules/EVSE/Auth/tests/auth_tests.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

2252 lines
114 KiB
C++

// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include <chrono>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <iostream>
#include <memory>
#include <thread>
#include <everest/helpers/helpers.hpp>
#include <everest/logging.hpp>
#include <utils/date.hpp>
#include <AuthHandler.hpp>
#include <FakeAuthReceiver.hpp>
using ::testing::_;
using ::testing::Field;
using ::testing::Invoke;
using ::testing::MockFunction;
using ::testing::StrictMock;
class kvsIntf;
namespace module {
const static std::string VALID_TOKEN_1 = "VALID_RFID_1"; // SAME PARENT_ID
const static std::string VALID_TOKEN_2 = "VALID_RFID_2";
const static std::string VALID_TOKEN_3 = "VALID_RFID_3"; // SAME PARENT_ID
const static std::string INVALID_TOKEN = "INVALID_RFID";
const static std::string PARENT_ID_TOKEN = "PARENT_RFID";
const static int32_t CONNECTION_TIMEOUT = 1;
static SessionEvent get_session_started_event(const types::evse_manager::StartSessionReason& reason) {
SessionEvent session_event;
session_event.event = SessionEventEnum::SessionStarted;
SessionStarted session_started;
session_started.reason = reason;
session_event.session_started = session_started;
return session_event;
}
static SessionEvent get_transaction_started_event(const ProvidedIdToken provided_token) {
SessionEvent session_event;
session_event.event = SessionEventEnum::TransactionStarted;
TransactionStarted transaction_event;
transaction_event.meter_value.energy_Wh_import.total = 0;
transaction_event.id_tag = provided_token;
session_event.timestamp = Everest::Date::to_rfc3339(date::utc_clock::now());
session_event.transaction_started = transaction_event;
return session_event;
}
static ProvidedIdToken get_provided_token(const std::string& id_token,
std::optional<std::vector<int32_t>> connectors = std::nullopt,
std::optional<bool> prevalidated = std::nullopt) {
ProvidedIdToken provided_token;
provided_token.id_token = {id_token, types::authorization::IdTokenType::ISO14443};
provided_token.authorization_type = types::authorization::AuthorizationType::RFID;
if (connectors) {
provided_token.connectors.emplace(connectors.value());
}
if (prevalidated) {
provided_token.prevalidated.emplace(prevalidated.value());
}
return provided_token;
}
class AuthTest : public ::testing::Test {
protected:
std::unique_ptr<AuthHandler> auth_handler;
std::unique_ptr<FakeAuthReceiver> auth_receiver;
testing::MockFunction<bool(json message)> send_callback_mock;
StrictMock<MockFunction<void(const ProvidedIdToken& token, TokenValidationStatus status)>>
mock_publish_token_validation_status_callback;
testing::MockFunction<void(const int evse_index, const StopTransactionRequest& request)>
mock_stop_transaction_callback;
testing::MockFunction<void(const int evse_index)> mock_withdraw_authorization_callback_mock;
void SetUp() override {
std::vector<int32_t> evse_indices{0, 1};
this->auth_receiver = std::make_unique<FakeAuthReceiver>(evse_indices);
const std::string id = "auth_handler_test_id";
this->auth_handler = std::make_unique<AuthHandler>(SelectionAlgorithm::PlugEvents, CONNECTION_TIMEOUT, true,
false, false, id, nullptr);
this->auth_handler->register_notify_evse_callback([this](const int evse_index,
const ProvidedIdToken& provided_token,
const ValidationResult& validation_result) {
EVLOG_debug << "Authorize called with evse_index#" << evse_index << " and id_token "
<< provided_token.id_token.value;
if (validation_result.authorization_status == AuthorizationStatus::Accepted) {
this->auth_receiver->authorize(evse_index);
} else {
this->auth_receiver->deauthorize(evse_index);
}
});
this->auth_handler->register_withdraw_authorization_callback([this](int32_t evse_index) {
EVLOG_debug << "DeAuthorize called with evse_index#" << evse_index;
this->auth_receiver->deauthorize(evse_index);
this->mock_withdraw_authorization_callback_mock.Call(evse_index);
});
this->auth_handler->register_stop_transaction_callback(
[this](int32_t evse_index, const StopTransactionRequest& request) {
EVLOG_debug << "Stop transaction called with evse_index#" << evse_index << " and reason "
<< stop_transaction_reason_to_string(request.reason);
this->auth_receiver->deauthorize(evse_index);
this->mock_stop_transaction_callback.Call(evse_index, request);
});
this->auth_handler->register_validate_token_callback([](const ProvidedIdToken& provided_token) {
std::vector<ValidationResult> validation_results;
const auto id_token = provided_token.id_token.value;
ValidationResult result_1;
result_1.authorization_status = AuthorizationStatus::Invalid;
ValidationResult result_2;
if (everest::helpers::is_equal_case_insensitive(id_token, VALID_TOKEN_1) ||
everest::helpers::is_equal_case_insensitive(id_token, VALID_TOKEN_3)) {
result_2.authorization_status = AuthorizationStatus::Accepted;
result_2.parent_id_token = {PARENT_ID_TOKEN, types::authorization::IdTokenType::ISO14443};
} else if (everest::helpers::is_equal_case_insensitive(id_token, VALID_TOKEN_2)) {
result_2.authorization_status = AuthorizationStatus::Accepted;
} else {
result_2.authorization_status = AuthorizationStatus::Invalid;
}
validation_results.push_back(result_1);
validation_results.push_back(result_2);
return validation_results;
});
this->auth_handler->register_reservation_cancelled_callback(
[](const std::optional<int32_t> evse_index, const int32_t reservation_id, const ReservationEndReason reason,
const bool send_reservation_update) {
EVLOG_info << "Signaling reservating cancelled to evse#"
<< (evse_index.has_value() ? evse_index.value() : 0);
});
this->auth_handler->register_publish_token_validation_status_callback(
[this](const ProvidedIdToken& token, TokenValidationStatus status,
const std::vector<MessageContent>& /*tariff_messages*/) {
mock_publish_token_validation_status_callback.Call(token, status);
});
this->auth_handler->init_evse(1, 0, {Connector(1, types::evse_manager::ConnectorTypeEnum::cCCS2)});
this->auth_handler->init_evse(2, 1,
{Connector(1, types::evse_manager::ConnectorTypeEnum::sType2),
Connector(2, types::evse_manager::ConnectorTypeEnum::cCCS2)});
}
void TearDown() override {
SessionEvent event;
event.event = SessionEventEnum::SessionFinished;
this->auth_handler->handle_session_event(1, event);
this->auth_handler->handle_session_event(2, event);
}
};
/// \brief Test if a connector receives authorization
TEST_F(AuthTest, test_simple_authorization) {
const SessionEvent session_event = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
this->auth_handler->handle_session_event(1, session_event);
std::vector<int32_t> connectors{1};
ProvidedIdToken provided_token = get_provided_token(VALID_TOKEN_1, connectors);
ProvidedIdToken expected = provided_token;
expected.parent_id_token = {PARENT_ID_TOKEN, types::authorization::IdTokenType::ISO14443};
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback, Call(expected, TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback, Call(expected, TokenValidationStatus::UsedToStart));
const auto result = this->auth_handler->on_token(provided_token);
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
}
/// \brief Test if connector that triggered a SessionStarted event receives authorization when two connectors are
/// referenced in the provided token
TEST_F(AuthTest, test_two_referenced_connectors) {
const SessionEvent session_event = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
this->auth_handler->handle_session_event(2, session_event);
std::vector<int32_t> connectors{1, 2};
ProvidedIdToken provided_token = get_provided_token(VALID_TOKEN_1, connectors);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::UsedToStart));
const auto result = this->auth_handler->on_token(provided_token);
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(1));
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
}
/// \brief Test if connector that triggered a SessionStarted event receives authorization when two connectors are
/// referenced in the provided token
TEST_F(AuthTest, test_multiple_referenced_connectors) {
this->auth_handler->init_evse(3, 2, {Connector(1, types::evse_manager::ConnectorTypeEnum::sType2)});
this->auth_receiver->add_evse_index(2);
std::vector<int32_t> connectors1{1, 2};
ProvidedIdToken provided_token1 = get_provided_token(VALID_TOKEN_1, connectors1);
std::vector<int32_t> connectors2 = {3};
ProvidedIdToken provided_token2 = get_provided_token(VALID_TOKEN_2, connectors2);
std::mutex mtx;
std::condition_variable cv;
bool processing_called = false;
// Set up expectations for mock_publish_token_validation_status_callback
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token1.id_token), TokenValidationStatus::Processing))
.WillOnce(Invoke([&]() {
std::unique_lock<std::mutex> lock(mtx);
processing_called = true;
cv.notify_all();
}));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token1.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token1.id_token), TokenValidationStatus::TimedOut));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token2.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token2.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token2.id_token), TokenValidationStatus::UsedToStart));
TokenHandlingResult result1;
std::thread t1([this, &result1, provided_token1] { result1 = this->auth_handler->on_token(provided_token1); });
// Wait for TokenValidationStatus::Processing to be triggered for t1 before starting t2
{
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [&]() { return processing_called; });
}
TokenHandlingResult result2;
std::thread t2([this, &result2, provided_token2] { result2 = this->auth_handler->on_token(provided_token2); });
const SessionEvent session_event = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
this->auth_handler->handle_session_event(3, session_event);
t2.join();
ASSERT_TRUE(result2 == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
ASSERT_TRUE(this->auth_receiver->get_authorization(2));
SessionEvent session_event2 = get_transaction_started_event(provided_token2);
this->auth_handler->handle_session_event(3, session_event2);
t1.join();
ASSERT_TRUE(result1 == TokenHandlingResult::TIMEOUT);
}
/// \brief Test three authorization requests for different referenced EVSEs with only one EV plugin. Two requests should
/// timeout, one should receive authorization
TEST_F(AuthTest, test_multiple_authorization_requests) {
std::vector<int32_t> connectors1{1, 2};
std::vector<int32_t> connectors2{3, 4};
std::vector<int32_t> connectors3{5, 6};
this->auth_handler->init_evse(3, 2, {Connector(1, types::evse_manager::ConnectorTypeEnum::sType2)});
this->auth_handler->init_evse(4, 3, {Connector(1, types::evse_manager::ConnectorTypeEnum::sType2)});
this->auth_handler->init_evse(5, 4, {Connector(1, types::evse_manager::ConnectorTypeEnum::sType2)});
this->auth_handler->init_evse(6, 5, {Connector(1, types::evse_manager::ConnectorTypeEnum::sType2)});
this->auth_receiver->add_evse_index(2);
this->auth_receiver->add_evse_index(3);
this->auth_receiver->add_evse_index(4);
this->auth_receiver->add_evse_index(5);
ProvidedIdToken provided_token1 = get_provided_token(VALID_TOKEN_1, connectors1);
ProvidedIdToken provided_token2 = get_provided_token(VALID_TOKEN_2, connectors2);
ProvidedIdToken provided_token3 = get_provided_token(VALID_TOKEN_3, connectors3);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token1.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token1.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token1.id_token), TokenValidationStatus::TimedOut));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token2.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token2.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token2.id_token), TokenValidationStatus::TimedOut));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token3.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token3.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token3.id_token), TokenValidationStatus::UsedToStart));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token3.id_token), TokenValidationStatus::TimedOut))
.Times(0);
TokenHandlingResult result1;
TokenHandlingResult result2;
TokenHandlingResult result3;
std::thread t1([this, provided_token1, &result1]() { result1 = this->auth_handler->on_token(provided_token1); });
std::thread t2([this, provided_token2, &result2]() { result2 = this->auth_handler->on_token(provided_token2); });
std::thread t3([this, provided_token3, &result3]() { result3 = this->auth_handler->on_token(provided_token3); });
SessionEvent session_event = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
this->auth_handler->handle_session_event(6, session_event);
t3.join();
SessionEvent transaction_started_event = get_transaction_started_event(provided_token3);
this->auth_handler->handle_session_event(6, transaction_started_event);
ASSERT_TRUE(this->auth_receiver->get_authorization(5));
ASSERT_TRUE(result3 == TokenHandlingResult::USED_TO_START_TRANSACTION);
t1.join();
t2.join();
ASSERT_TRUE(result1 == TokenHandlingResult::TIMEOUT);
ASSERT_TRUE(result2 == TokenHandlingResult::TIMEOUT);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
ASSERT_FALSE(this->auth_receiver->get_authorization(2));
ASSERT_FALSE(this->auth_receiver->get_authorization(3));
ASSERT_FALSE(this->auth_receiver->get_authorization(4));
}
/// \brief Test if a transaction is stopped when an id_token is swiped twice
TEST_F(AuthTest, test_stop_transaction) {
std::vector<int32_t> connectors{1};
ProvidedIdToken provided_token = get_provided_token(VALID_TOKEN_1, connectors);
SessionEvent session_event1 = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
SessionEvent session_event2 = get_transaction_started_event(provided_token);
this->auth_handler->handle_session_event(1, session_event1);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing))
.Times(2);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Accepted))
.Times(1);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::UsedToStart))
.Times(1);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::UsedToStop))
.Times(1);
auto result = this->auth_handler->on_token(provided_token);
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
this->auth_handler->handle_session_event(1, session_event2);
// second swipe to finish transaction
result = this->auth_handler->on_token(provided_token);
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_STOP_TRANSACTION);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
}
/// \brief Simple test to test if authorize first and plugin provides authorization
TEST_F(AuthTest, test_authorize_first) {
std::vector<int32_t> connectors{1, 2};
ProvidedIdToken provided_token = get_provided_token(VALID_TOKEN_1, connectors);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::UsedToStart));
TokenHandlingResult result;
std::thread t1([this, provided_token, &result]() { result = this->auth_handler->on_token(provided_token); });
SessionEvent session_event = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
std::thread t2([this, session_event]() { this->auth_handler->handle_session_event(1, session_event); });
t1.join();
t2.join();
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
}
/// \brief Test if swiping the same card several times and timeout is handled correctly
TEST_F(AuthTest, test_swipe_multiple_times_with_timeout) {
std::vector<int32_t> connectors{1, 2};
ProvidedIdToken provided_token = get_provided_token(VALID_TOKEN_1, connectors);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing))
.Times(2);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Accepted))
.Times(2);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::TimedOut))
.Times(1);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::UsedToStart))
.Times(1);
TokenHandlingResult result1;
TokenHandlingResult result2;
TokenHandlingResult result3;
TokenHandlingResult result4;
TokenHandlingResult result5;
std::thread t1([this, provided_token, &result1]() { result1 = this->auth_handler->on_token(provided_token); });
std::thread t2([this, provided_token, &result2]() { result2 = this->auth_handler->on_token(provided_token); });
std::thread t3([this, provided_token, &result3]() { result3 = this->auth_handler->on_token(provided_token); });
std::thread t4([this, provided_token, &result4]() { result4 = this->auth_handler->on_token(provided_token); });
// wait for timeout
std::this_thread::sleep_for(std::chrono::seconds(CONNECTION_TIMEOUT + 1));
t1.join();
t2.join();
t3.join();
t4.join();
std::vector<TokenHandlingResult> results = {result1, result2, result3, result4};
// Count occurrences of TIMEOUT and ALREADY_IN_PROCESS
int timeout_count = std::count(results.begin(), results.end(), TokenHandlingResult::TIMEOUT);
int in_process_count = std::count(results.begin(), results.end(), TokenHandlingResult::ALREADY_IN_PROCESS);
// Assert that exactly one result is TIMEOUT and the others are ALREADY_IN_PROCESS
ASSERT_EQ(timeout_count, 1);
ASSERT_EQ(in_process_count, 3);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
std::thread t5([this, provided_token, &result5]() { result5 = this->auth_handler->on_token(provided_token); });
SessionEvent session_event = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
std::thread t6([this, session_event]() { this->auth_handler->handle_session_event(2, session_event); });
t5.join();
t6.join();
ASSERT_TRUE(result5 == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
ASSERT_TRUE(this->auth_receiver->get_authorization(1));
}
/// \brief Test if swiping two different cars will be processed and used for two seperate transactions
TEST_F(AuthTest, test_two_id_tokens) {
std::vector<int32_t> connectors{1, 2};
ProvidedIdToken provided_token_1 = get_provided_token(VALID_TOKEN_1, connectors);
ProvidedIdToken provided_token_2 = get_provided_token(VALID_TOKEN_2, connectors);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::UsedToStart));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::UsedToStart));
TokenHandlingResult result1;
TokenHandlingResult result2;
std::thread t1([this, provided_token_1, &result1]() { result1 = this->auth_handler->on_token(provided_token_1); });
std::thread t2([this, provided_token_2, &result2]() { result2 = this->auth_handler->on_token(provided_token_2); });
SessionEvent session_event = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
std::thread t3([this, session_event]() { this->auth_handler->handle_session_event(1, session_event); });
std::thread t4([this, session_event]() { this->auth_handler->handle_session_event(2, session_event); });
t1.join();
t2.join();
t3.join();
t4.join();
ASSERT_TRUE(result1 == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(result2 == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
ASSERT_TRUE(this->auth_receiver->get_authorization(1));
}
/// \brief Test if two transactions can be started for two succeeding plugins before two card swipes
TEST_F(AuthTest, test_two_plugins) {
TokenHandlingResult result1;
TokenHandlingResult result2;
SessionEvent session_event = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
std::thread t1([this, session_event]() { this->auth_handler->handle_session_event(1, session_event); });
std::thread t2([this, session_event]() { this->auth_handler->handle_session_event(2, session_event); });
std::vector<int32_t> connectors{1, 2};
ProvidedIdToken provided_token_1 = get_provided_token(VALID_TOKEN_1, connectors);
ProvidedIdToken provided_token_2 = get_provided_token(VALID_TOKEN_2, connectors);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::UsedToStart));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::UsedToStart));
std::thread t3([this, provided_token_1, &result1]() { result1 = this->auth_handler->on_token(provided_token_1); });
std::thread t4([this, provided_token_2, &result2]() { result2 = this->auth_handler->on_token(provided_token_2); });
t1.join();
t2.join();
t3.join();
t4.join();
ASSERT_TRUE(result1 == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(result2 == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
ASSERT_TRUE(this->auth_receiver->get_authorization(1));
}
/// \brief Test if a connector receives authorization after subsequent plug-in and plug-out events
TEST_F(AuthTest, test_authorization_after_plug_in_and_plug_out) {
TokenHandlingResult result1;
TokenHandlingResult result2;
std::vector<int32_t> connectors{1, 2};
ProvidedIdToken provided_token_1 = get_provided_token(VALID_TOKEN_1, connectors);
// Plug-in and plug-out event on connector 1
SessionEvent session_event_connected =
get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
SessionEvent session_event_disconnected;
session_event_disconnected.event = SessionEventEnum::SessionFinished;
this->auth_handler->handle_session_event(1, session_event_connected);
this->auth_handler->handle_session_event(1, session_event_disconnected);
// Swipe RFID
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::UsedToStart));
std::thread t3([this, provided_token_1, &result1]() { result1 = this->auth_handler->on_token(provided_token_1); });
// Plug-in on connector 2, conntector 2 should be authorized
std::thread t4(
[this, session_event_connected]() { this->auth_handler->handle_session_event(2, session_event_connected); });
t3.join();
t4.join();
ASSERT_TRUE(result1 == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
ASSERT_TRUE(this->auth_receiver->get_authorization(1));
}
/// \brief Test if transactions can be started for two succeeding plugins before one valid and one invalid card swipe
TEST_F(AuthTest, test_two_plugins_with_invalid_rfid) {
TokenHandlingResult result1;
TokenHandlingResult result2;
SessionEvent session_event = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
std::thread t1([this, session_event]() { this->auth_handler->handle_session_event(1, session_event); });
std::thread t2([this, session_event]() { this->auth_handler->handle_session_event(2, session_event); });
std::vector<int32_t> connectors{1, 2};
ProvidedIdToken provided_token_1 = get_provided_token(VALID_TOKEN_1, connectors);
ProvidedIdToken provided_token_2 = get_provided_token(INVALID_TOKEN, connectors);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::UsedToStart));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Rejected));
t1.join();
t2.join();
std::thread t3([this, provided_token_1, &result1]() { result1 = this->auth_handler->on_token(provided_token_1); });
t3.join();
ASSERT_TRUE(result1 == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
std::thread t4([this, provided_token_2, &result2]() { result2 = this->auth_handler->on_token(provided_token_2); });
t4.join();
ASSERT_TRUE(result2 == TokenHandlingResult::REJECTED);
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
}
/// \brief Test if state permanent fault leads to not provide authorization
TEST_F(AuthTest, test_faulted_state) {
TokenHandlingResult result1;
TokenHandlingResult result2;
std::thread t1([this]() { this->auth_handler->handle_permanent_fault_raised(1, 1); });
std::thread t2([this]() { this->auth_handler->handle_permanent_fault_raised(2, 1); });
std::thread t3([this]() { this->auth_handler->handle_permanent_fault_raised(2, 2); });
t1.join();
t2.join();
t3.join();
std::vector<int32_t> connectors{1, 2};
ProvidedIdToken provided_token_1 = get_provided_token(VALID_TOKEN_1, connectors);
ProvidedIdToken provided_token_2 = get_provided_token(VALID_TOKEN_2, connectors);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Rejected));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Rejected));
std::thread t4([this, provided_token_1, &result1]() { result1 = this->auth_handler->on_token(provided_token_1); });
std::thread t5([this, provided_token_2, &result2]() { result2 = this->auth_handler->on_token(provided_token_2); });
t4.join();
t5.join();
ASSERT_TRUE(result1 == TokenHandlingResult::NO_CONNECTOR_AVAILABLE);
ASSERT_TRUE(result2 == TokenHandlingResult::NO_CONNECTOR_AVAILABLE);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
}
/// \brief Test if transaction can be stopped by swiping the card twice
TEST_F(AuthTest, test_transaction_finish) {
TokenHandlingResult result1;
TokenHandlingResult result2;
std::vector<int32_t> connectors{1, 2};
ProvidedIdToken provided_token_1 = get_provided_token(VALID_TOKEN_1, connectors);
ProvidedIdToken provided_token_2 = get_provided_token(VALID_TOKEN_2, connectors);
SessionEvent session_event1 = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
SessionEvent session_event2 = get_transaction_started_event(provided_token_1);
SessionEvent session_event3 = get_transaction_started_event(provided_token_2);
std::thread t1([this, session_event1]() { this->auth_handler->handle_session_event(1, session_event1); });
std::thread t2([this, session_event1]() { this->auth_handler->handle_session_event(2, session_event1); });
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Processing))
.Times(2);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Processing))
.Times(2);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Accepted))
.Times(1);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Accepted))
.Times(1);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::UsedToStart))
.Times(1);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::UsedToStop))
.Times(1);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::UsedToStart))
.Times(1);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::UsedToStop))
.Times(1);
std::thread t3([this, provided_token_1, &result1]() { result1 = this->auth_handler->on_token(provided_token_1); });
std::thread t4([this, provided_token_2, &result2]() { result2 = this->auth_handler->on_token(provided_token_2); });
t1.join();
t2.join();
t3.join();
t4.join();
std::thread t5([this, session_event2]() { this->auth_handler->handle_session_event(1, session_event2); });
std::thread t6([this, session_event3]() { this->auth_handler->handle_session_event(2, session_event3); });
t5.join();
t6.join();
ASSERT_TRUE(result1 == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(result2 == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
ASSERT_TRUE(this->auth_receiver->get_authorization(1));
std::thread t7([this, provided_token_1, &result1]() { result1 = this->auth_handler->on_token(provided_token_1); });
std::thread t8([this, provided_token_2, &result2]() { result2 = this->auth_handler->on_token(provided_token_2); });
t7.join();
t8.join();
ASSERT_TRUE(result1 == TokenHandlingResult::USED_TO_STOP_TRANSACTION);
ASSERT_TRUE(result2 == TokenHandlingResult::USED_TO_STOP_TRANSACTION);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
}
/// \brief Test if transaction can be finished with parent_id when prioritize_authorization_over_stopping_transaction is
/// false
TEST_F(AuthTest, test_parent_id_finish) {
TokenHandlingResult result;
std::vector<int32_t> connectors{1, 2};
ProvidedIdToken provided_token_1 = get_provided_token(VALID_TOKEN_1, connectors);
ProvidedIdToken provided_token_2 = get_provided_token(VALID_TOKEN_3, connectors);
SessionEvent session_event1 = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
this->auth_handler->handle_session_event(1, session_event1);
SessionEvent session_event2 = get_transaction_started_event(provided_token_1);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::UsedToStart));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::UsedToStop));
// swipe VALID_TOKEN_1
std::thread t2([this, provided_token_1, &result]() { result = this->auth_handler->on_token(provided_token_1); });
std::thread t3([this, session_event2]() { this->auth_handler->handle_session_event(1, session_event2); });
t2.join();
t3.join();
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
// swipe VALID_TOKEN_3 to finish transaction
std::thread t4([this, provided_token_2, &result]() { result = this->auth_handler->on_token(provided_token_2); });
t4.join();
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_STOP_TRANSACTION);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
}
/// \brief Test if transaction doesnt finish with parent_id when prioritize_authorization_over_stopping_transaction is
/// true. Instead: Authorization should be given to connector#2
TEST_F(AuthTest, test_parent_id_no_finish) {
TokenHandlingResult result;
// this changes the behavior compared to previous test
this->auth_handler->set_prioritize_authorization_over_stopping_transaction(true);
SessionEvent session_event = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
std::thread t1([this, session_event]() { this->auth_handler->handle_session_event(1, session_event); });
std::vector<int32_t> connectors{1, 2};
ProvidedIdToken provided_token_1 = get_provided_token(VALID_TOKEN_1, connectors);
ProvidedIdToken provided_token_2 = get_provided_token(VALID_TOKEN_3, connectors);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::UsedToStart));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::UsedToStart));
// swipe VALID_TOKEN_1
std::thread t2([this, provided_token_1, &result]() { result = this->auth_handler->on_token(provided_token_1); });
t1.join();
t2.join();
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
// swipe VALID_TOKEN_3. This does not finish transaction but will provide authorization to connector#2 after plugin
std::thread t3([this, provided_token_2, &result]() { result = this->auth_handler->on_token(provided_token_2); });
std::thread t4([this, session_event]() { this->auth_handler->handle_session_event(2, session_event); });
t3.join();
t4.join();
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
ASSERT_TRUE(this->auth_receiver->get_authorization(1));
}
/// \brief Test if transaction doesnt finish with parent_id when prioritize_authorization_over_stopping_transaction is
/// true. Instead: Authorization should be given to connector#2
TEST_F(AuthTest, test_parent_id_finish_because_no_available_connector) {
TokenHandlingResult result;
this->auth_handler->set_prioritize_authorization_over_stopping_transaction(true);
SessionEvent session_event_1 = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
std::thread t1([this, session_event_1]() { this->auth_handler->handle_session_event(1, session_event_1); });
std::thread t2([this]() {
this->auth_handler->handle_permanent_fault_raised(2, 1);
this->auth_handler->handle_permanent_fault_raised(2, 2);
});
std::vector<int32_t> connectors{1, 2};
ProvidedIdToken provided_token_1 = get_provided_token(VALID_TOKEN_1, connectors);
ProvidedIdToken provided_token_2 = get_provided_token(VALID_TOKEN_3, connectors);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::UsedToStart));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::UsedToStop));
// swipe VALID_TOKEN_1
std::thread t3([this, provided_token_1, &result]() { result = this->auth_handler->on_token(provided_token_1); });
t1.join();
t2.join();
t3.join();
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_START_TRANSACTION);
SessionEvent session_event_3 = get_transaction_started_event(provided_token_1);
std::thread t4([this, session_event_3]() { this->auth_handler->handle_session_event(1, session_event_3); });
t4.join();
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
// swipe VALID_TOKEN_3. This does finish transaction because no connector is available
std::thread t5([this, provided_token_2, &result]() { result = this->auth_handler->on_token(provided_token_2); });
t5.join();
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_STOP_TRANSACTION);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
}
/// \brief Test if transaction doesnt finish with parent_id when prioritize_authorization_over_stopping_transaction is
/// true. Instead: Authorization should be given to connector#2
TEST_F(AuthTest, test_parent_id_finish_because_no_available_connector_2) {
// Same test as above, but now the other evse is set to faulted.
TokenHandlingResult result;
this->auth_handler->set_prioritize_authorization_over_stopping_transaction(true);
SessionEvent session_event_1 = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
std::thread t1([this, session_event_1]() { this->auth_handler->handle_session_event(2, session_event_1); });
std::thread t2([this]() { this->auth_handler->handle_permanent_fault_raised(1, 1); });
std::vector<int32_t> connectors{1, 2};
ProvidedIdToken provided_token_1 = get_provided_token(VALID_TOKEN_1, connectors);
ProvidedIdToken provided_token_2 = get_provided_token(VALID_TOKEN_3, connectors);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::UsedToStart));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::UsedToStop));
// swipe VALID_TOKEN_1
std::thread t3([this, provided_token_1, &result]() { result = this->auth_handler->on_token(provided_token_1); });
t1.join();
t2.join();
t3.join();
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_START_TRANSACTION);
SessionEvent session_event_3 = get_transaction_started_event(provided_token_1);
std::thread t4([this, session_event_3]() { this->auth_handler->handle_session_event(2, session_event_3); });
t4.join();
ASSERT_TRUE(this->auth_receiver->get_authorization(1));
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
// swipe VALID_TOKEN_3. This does finish transaction because no connector is available
std::thread t5([this, provided_token_2, &result]() { result = this->auth_handler->on_token(provided_token_2); });
t5.join();
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_STOP_TRANSACTION);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
}
/// \brief Test if a reservation can be placed
TEST_F(AuthTest, test_reservation) {
Reservation reservation;
reservation.evse_id = 1;
reservation.id_token = VALID_TOKEN_1;
reservation.reservation_id = 1;
reservation.connector_type = types::evse_manager::ConnectorTypeEnum::cCCS2;
reservation.expiry_time = Everest::Date::to_rfc3339((date::utc_clock::now() + std::chrono::hours(1)));
const auto reservation_result = this->auth_handler->handle_reservation(reservation);
ASSERT_EQ(reservation_result, ReservationResult::Accepted);
}
/// \brief Test if a reservation cannot be placed if expiry_time is in the past
TEST_F(AuthTest, test_reservation_in_past) {
Reservation reservation;
reservation.evse_id = 1;
reservation.id_token = VALID_TOKEN_1;
reservation.reservation_id = 1;
reservation.connector_type = types::evse_manager::ConnectorTypeEnum::cCCS2;
reservation.expiry_time = Everest::Date::to_rfc3339((date::utc_clock::now() - std::chrono::hours(1)));
const auto reservation_result = this->auth_handler->handle_reservation(reservation);
ASSERT_EQ(reservation_result, ReservationResult::Rejected);
}
/// \brief Test if a token that is not reserved gets rejected and a token that is reserved gets accepted
TEST_F(AuthTest, test_reservation_with_authorization) {
TokenHandlingResult result;
Reservation reservation;
reservation.evse_id = 1;
reservation.id_token = VALID_TOKEN_2;
reservation.reservation_id = 1;
reservation.connector_type = types::evse_manager::ConnectorTypeEnum::cCCS2;
reservation.expiry_time = Everest::Date::to_rfc3339(date::utc_clock::now() + std::chrono::hours(1));
const auto reservation_result = this->auth_handler->handle_reservation(reservation);
ASSERT_EQ(reservation_result, ReservationResult::Accepted);
SessionEvent session_event_1;
session_event_1.event = SessionEventEnum::ReservationStart;
std::thread t1([this, session_event_1]() { this->auth_handler->handle_session_event(1, session_event_1); });
t1.join();
SessionEvent session_event_2 = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
std::thread t2([this, session_event_2]() { this->auth_handler->handle_session_event(1, session_event_2); });
t2.join();
std::vector<int32_t> connectors{1, 2};
ProvidedIdToken provided_token_1 = get_provided_token(VALID_TOKEN_1, connectors);
ProvidedIdToken provided_token_2 = get_provided_token(VALID_TOKEN_2, connectors);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Processing));
// In general the token gets accepted but the connector that was picked up by the user is reserved, therefore it's
// rejected afterwards
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Rejected));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::UsedToStart));
// this token is not valid for the reservation
std::thread t3([this, provided_token_1, &result]() { result = this->auth_handler->on_token(provided_token_1); });
t3.join();
ASSERT_EQ(result, TokenHandlingResult::REJECTED);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
// this token is valid for the reservation
std::thread t4([this, provided_token_2, &result]() { result = this->auth_handler->on_token(provided_token_2); });
t4.join();
ASSERT_EQ(result, TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
}
/// \brief Test if a token that is not reserved gets rejected when it is not possible to charge because of global
/// reservations.
TEST_F(AuthTest, test_reservation_with_authorization_global_reservations) {
TokenHandlingResult result;
Reservation reservation;
reservation.id_token = VALID_TOKEN_2;
reservation.reservation_id = 1;
reservation.connector_type = types::evse_manager::ConnectorTypeEnum::sType2;
reservation.expiry_time = Everest::Date::to_rfc3339(date::utc_clock::now() + std::chrono::hours(1));
const auto reservation_result = this->auth_handler->handle_reservation(reservation);
ASSERT_EQ(reservation_result, ReservationResult::Accepted);
SessionEvent session_event_1;
session_event_1.event = SessionEventEnum::ReservationStart;
std::thread t1([this, session_event_1]() { this->auth_handler->handle_session_event(2, session_event_1); });
t1.join();
std::vector<int32_t> connectors{1, 2};
ProvidedIdToken provided_token_1 = get_provided_token(VALID_TOKEN_1, connectors);
// In general the token gets accepted but the connector that was picked up by the user is the only one that has
// the correct connector for the reservation so it can not be used as it has to be available for the one who
// reserved it.
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Rejected));
// this token is not valid for the reservation
std::thread t2([this, provided_token_1, &result]() { result = this->auth_handler->on_token(provided_token_1); });
SessionEvent session_event = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
std::thread t3([this, session_event]() { this->auth_handler->handle_session_event(2, session_event); });
t2.join();
t3.join();
ASSERT_EQ(result, TokenHandlingResult::REJECTED);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
}
/// \brief Test if a token that is not reserved gets rejected when it is not possible to charge because of global
/// reservations.
TEST_F(AuthTest, test_reservation_with_authorization_global_reservations_2) {
TokenHandlingResult result;
// Make two global reservations.
Reservation reservation;
reservation.id_token = VALID_TOKEN_2;
reservation.reservation_id = 1;
reservation.connector_type = types::evse_manager::ConnectorTypeEnum::cCCS2;
reservation.expiry_time = Everest::Date::to_rfc3339(date::utc_clock::now() + std::chrono::hours(1));
const auto reservation_result = this->auth_handler->handle_reservation(reservation);
ASSERT_EQ(reservation_result, ReservationResult::Accepted);
reservation.reservation_id = 2;
reservation.id_token = VALID_TOKEN_1;
const auto reservation_result2 = this->auth_handler->handle_reservation(reservation);
ASSERT_EQ(reservation_result2, ReservationResult::Accepted);
SessionEvent session_event_1;
session_event_1.event = SessionEventEnum::ReservationStart;
std::thread t1([this, session_event_1]() { this->auth_handler->handle_session_event(2, session_event_1); });
t1.join();
std::vector<int32_t> connectors{1, 2};
ProvidedIdToken provided_token_3 = get_provided_token(VALID_TOKEN_3, connectors);
// There are two global reservations and two evse's, so no evse is available.
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_3.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_3.id_token), TokenValidationStatus::Rejected));
// this token is not valid for the reservation
std::thread t2([this, provided_token_3, &result]() { result = this->auth_handler->on_token(provided_token_3); });
SessionEvent session_event = get_session_started_event(types::evse_manager::StartSessionReason::Authorized);
std::thread t3([this, session_event]() { this->auth_handler->handle_session_event(2, session_event); });
t2.join();
t3.join();
ASSERT_EQ(result, TokenHandlingResult::NO_CONNECTOR_AVAILABLE);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
}
/// \brief Test complete happy event flow of a session
TEST_F(AuthTest, test_complete_event_flow) {
TokenHandlingResult result;
std::vector<int32_t> connectors{1};
ProvidedIdToken provided_token = get_provided_token(VALID_TOKEN_1, connectors);
// events
SessionEvent session_event_1 = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
SessionEvent session_event_2 = get_transaction_started_event(provided_token);
SessionEvent session_event_3;
session_event_3.event = SessionEventEnum::ChargingPausedEV;
SessionEvent session_event_4;
session_event_4.event = SessionEventEnum::ChargingStarted;
SessionEvent session_event_5;
session_event_5.event = SessionEventEnum::TransactionFinished;
TransactionStarted transaction_finished_event;
transaction_finished_event.meter_value.energy_Wh_import.total = 1000;
transaction_finished_event.id_tag = provided_token;
session_event_5.timestamp = Everest::Date::to_rfc3339(date::utc_clock::now());
SessionEvent session_event_6;
session_event_6.event = SessionEventEnum::SessionFinished;
this->auth_handler->handle_session_event(1, session_event_1);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing))
.Times(2);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Accepted))
.Times(2);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::UsedToStart))
.Times(2);
result = this->auth_handler->on_token(provided_token);
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
// wait for state machine to process session finished event
this->auth_handler->handle_session_event(1, session_event_2);
this->auth_handler->handle_session_event(1, session_event_3);
this->auth_handler->handle_session_event(1, session_event_4);
this->auth_handler->handle_session_event(1, session_event_5);
this->auth_handler->handle_session_event(1, session_event_6);
this->auth_receiver->reset();
this->auth_handler->handle_session_event(1, session_event_1);
result = this->auth_handler->on_token(provided_token);
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
}
/// \brief Test if a token that is not reserved gets rejected and a parent_id_token that is reserved gets accepted
TEST_F(AuthTest, test_reservation_with_parent_id_tag) {
TokenHandlingResult result;
Reservation reservation;
reservation.evse_id = 1;
reservation.id_token = VALID_TOKEN_1;
reservation.reservation_id = 1;
reservation.parent_id_token.emplace(PARENT_ID_TOKEN);
reservation.expiry_time = Everest::Date::to_rfc3339(date::utc_clock::now() + std::chrono::hours(1));
const auto reservation_result = this->auth_handler->handle_reservation(reservation);
ASSERT_EQ(reservation_result, ReservationResult::Accepted);
SessionEvent session_event_1;
session_event_1.event = SessionEventEnum::ReservationStart;
std::thread t1([this, session_event_1]() { this->auth_handler->handle_session_event(1, session_event_1); });
t1.join();
SessionEvent session_event_2 = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
std::thread t2([this, session_event_2]() { this->auth_handler->handle_session_event(1, session_event_2); });
t2.join();
std::vector<int32_t> connectors{1, 2};
ProvidedIdToken provided_token_1 = get_provided_token(VALID_TOKEN_2, connectors);
ProvidedIdToken provided_token_2 = get_provided_token(VALID_TOKEN_3, connectors);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Processing));
// In general the token gets accepted but the connector that was picked up by the user is reserved, therefore it's
// rejected afterwards
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Rejected));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::UsedToStart));
// this token is not valid for the reservation
std::thread t3([this, provided_token_1, &result]() { result = this->auth_handler->on_token(provided_token_1); });
t3.join();
ASSERT_TRUE(result == TokenHandlingResult::REJECTED);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
// this token is valid for the reservation because the parent id tags match
std::thread t4([this, provided_token_2, &result]() { result = this->auth_handler->on_token(provided_token_2); });
t4.join();
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
}
/// \brief Test if authorization is withdrawn after a timeout and new authorization is provided after the next swipe
TEST_F(AuthTest, test_authorization_timeout_and_reswipe) {
std::vector<int32_t> connectors{1, 2};
ProvidedIdToken provided_token = get_provided_token(VALID_TOKEN_1, connectors);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing))
.Times(2);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Accepted))
.Times(2);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::TimedOut))
.Times(1);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::UsedToStart))
.Times(1);
TokenHandlingResult result;
std::thread t1([this, provided_token, &result]() { result = this->auth_handler->on_token(provided_token); });
t1.join();
ASSERT_TRUE(result == TokenHandlingResult::TIMEOUT);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
std::thread t2([this, provided_token, &result]() { result = this->auth_handler->on_token(provided_token); });
SessionEvent session_event = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
std::thread t3([this, session_event]() { this->auth_handler->handle_session_event(1, session_event); });
t2.join();
t3.join();
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
}
/// \brief Test if response is ALREADY_IN_PROCESS with authorization was provided but transaction has not yet been
/// started
TEST_F(AuthTest, test_authorization_without_transaction) {
std::vector<int32_t> connectors{1};
ProvidedIdToken provided_token = get_provided_token(VALID_TOKEN_1, connectors);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing))
.Times(2);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Accepted))
.Times(2);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::UsedToStart))
.Times(2);
TokenHandlingResult result;
std::thread t1([this, provided_token, &result]() { result = this->auth_handler->on_token(provided_token); });
t1.join();
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
std::thread t2([this, provided_token, &result]() { result = this->auth_handler->on_token(provided_token); });
t2.join();
ASSERT_TRUE(result == TokenHandlingResult::ALREADY_IN_PROCESS);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::TimedOut));
// wait for timeout
std::this_thread::sleep_for(std::chrono::seconds(CONNECTION_TIMEOUT + 1));
std::thread t3([this, provided_token, &result]() { result = this->auth_handler->on_token(provided_token); });
t3.join();
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
}
/// \brief Test if two transactions can be started for two succeeding plugins before two card swipes
TEST_F(AuthTest, test_two_transactions_start_stop) {
TokenHandlingResult result1;
TokenHandlingResult result2;
TokenHandlingResult result3;
TokenHandlingResult result4;
SessionEvent session_event1 = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
std::thread t1([this, session_event1]() { this->auth_handler->handle_session_event(1, session_event1); });
std::thread t2([this, session_event1]() { this->auth_handler->handle_session_event(2, session_event1); });
std::vector<int32_t> connectors{1, 2};
ProvidedIdToken provided_token_1 = get_provided_token(VALID_TOKEN_1, connectors);
ProvidedIdToken provided_token_2 = get_provided_token(VALID_TOKEN_2, connectors);
SessionEvent session_event2 = get_transaction_started_event(provided_token_1);
SessionEvent session_event3 = get_transaction_started_event(provided_token_2);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Processing))
.Times(2);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Processing))
.Times(2);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Accepted))
.Times(1);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Accepted))
.Times(1);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::UsedToStart))
.Times(1);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::UsedToStop))
.Times(1);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::UsedToStart))
.Times(1);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::UsedToStop))
.Times(1);
std::thread t3([this, provided_token_1, &result1]() { result1 = this->auth_handler->on_token(provided_token_1); });
std::thread t4([this, provided_token_2, &result2]() { result2 = this->auth_handler->on_token(provided_token_2); });
t1.join();
t2.join();
t3.join();
t4.join();
ASSERT_TRUE(result1 == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(result2 == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
ASSERT_TRUE(this->auth_receiver->get_authorization(1));
std::thread t5([this, session_event2]() { this->auth_handler->handle_session_event(1, session_event2); });
std::thread t6([this, session_event3]() { this->auth_handler->handle_session_event(2, session_event3); });
t5.join();
t6.join();
std::thread t7([this, provided_token_2, &result3]() { result3 = this->auth_handler->on_token(provided_token_2); });
std::thread t8([this, provided_token_1, &result4]() { result4 = this->auth_handler->on_token(provided_token_1); });
t7.join();
t8.join();
ASSERT_TRUE(result3 == TokenHandlingResult::USED_TO_STOP_TRANSACTION);
ASSERT_TRUE(result4 == TokenHandlingResult::USED_TO_STOP_TRANSACTION);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
}
/// \brief Test PlugAndCharge
TEST_F(AuthTest, test_plug_and_charge) {
const SessionEvent session_event = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
this->auth_handler->handle_session_event(1, session_event);
ProvidedIdToken provided_token;
provided_token.id_token = {VALID_TOKEN_1, types::authorization::IdTokenType::eMAID};
provided_token.authorization_type = types::authorization::AuthorizationType::PlugAndCharge;
provided_token.certificate.emplace("TestCertificate");
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::UsedToStart));
const auto result = this->auth_handler->on_token(provided_token);
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
}
/// \brief Test PlugAndCharge
TEST_F(AuthTest, test_plug_and_charge_rejected) {
// put authorization on evse so that we can check later if it was removed
this->auth_receiver->authorize(0);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
const SessionEvent session_event = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
this->auth_handler->handle_session_event(1, session_event);
ProvidedIdToken provided_token;
provided_token.id_token = {INVALID_TOKEN, types::authorization::IdTokenType::eMAID};
provided_token.authorization_type = types::authorization::AuthorizationType::PlugAndCharge;
provided_token.certificate.emplace("TestCertificate");
provided_token.connectors = {1, 2};
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Rejected));
const auto result = this->auth_handler->on_token(provided_token);
ASSERT_TRUE(result == TokenHandlingResult::REJECTED);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
}
/// \brief Test empty intersection of referenced connectors in provided token and in validation result
TEST_F(AuthTest, test_empty_intersection) {
this->auth_handler->register_validate_token_callback([](const ProvidedIdToken& provided_token) {
std::vector<ValidationResult> validation_results;
const auto id_token = provided_token.id_token.value;
std::vector<int> evse_ids{2};
std::vector<int> evse_ids2{1, 2};
ValidationResult result_1;
result_1.authorization_status = AuthorizationStatus::Accepted;
result_1.evse_ids.emplace(evse_ids);
ValidationResult result_2;
result_2.authorization_status = AuthorizationStatus::Accepted;
result_2.evse_ids.emplace(evse_ids2);
validation_results.push_back(result_1);
validation_results.push_back(result_2);
return validation_results;
});
const SessionEvent session_event = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
this->auth_handler->handle_session_event(1, session_event);
this->auth_handler->handle_session_event(2, session_event);
std::vector<int32_t> connectors{1};
ProvidedIdToken provided_token;
provided_token.id_token = {VALID_TOKEN_1, types::authorization::IdTokenType::eMAID};
provided_token.authorization_type = types::authorization::AuthorizationType::PlugAndCharge;
provided_token.certificate.emplace("TestCertificate");
provided_token.connectors.emplace(connectors);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Accepted))
.Times(2);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::UsedToStart));
const auto result = this->auth_handler->on_token(provided_token);
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
}
TEST_F(AuthTest, test_master_pass_group_id) {
// set master group id to parent id token
this->auth_handler->set_master_pass_group_id(PARENT_ID_TOKEN);
// set_prioritize_authorization_over_stopping_transaction=false; otherwise token could be used for authorization of
// another connector
this->auth_handler->set_prioritize_authorization_over_stopping_transaction(false);
const SessionEvent session_event = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
this->auth_handler->handle_session_event(1, session_event);
auto provided_token = get_provided_token(PARENT_ID_TOKEN);
// Test if group id token is not allowed to start transactions
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Rejected));
auto result = this->auth_handler->on_token(provided_token);
ASSERT_TRUE(result == TokenHandlingResult::REJECTED);
provided_token = get_provided_token(VALID_TOKEN_2);
provided_token.parent_id_token = std::nullopt;
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::UsedToStart));
result = this->auth_handler->on_token(
provided_token); // swipe token that gets accepted without parent id in validation result
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_START_TRANSACTION);
// start transaction
SessionEvent session_event2 = get_transaction_started_event(provided_token);
provided_token.id_token = {VALID_TOKEN_1, types::authorization::IdTokenType::ISO14443};
// check if group id token can stop transactions
this->auth_handler->handle_session_event(1, session_event2);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::UsedToStop));
// second swipe to finish transaction
result = this->auth_handler->on_token(provided_token);
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_STOP_TRANSACTION);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
}
/// \brief Test TimedOut is published when authorization was provided but transaction has never been started.
TEST_F(AuthTest, test_token_timed_out) {
// In order to time-out waiting for a plug-in event, we need to get select_connector to wait for a plug-in event
// in the first place.
// To get select_connector to wait for a plug-in event, we must provide more then one connector here, since if we
// provide only 1, select_connector would just return the single connector.
std::vector<int32_t> connectors{1, 2};
ProvidedIdToken provided_token = get_provided_token(VALID_TOKEN_1, connectors);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::TimedOut));
TokenHandlingResult result;
std::thread t1([this, provided_token, &result]() { result = this->auth_handler->on_token(provided_token); });
t1.join();
ASSERT_TRUE(result == TokenHandlingResult::TIMEOUT);
// wait for timeout, after which TimedOut should be published.
std::this_thread::sleep_for(std::chrono::seconds(CONNECTION_TIMEOUT + 1));
}
/// \brief Test that in case of a plug in timeout, no authorization is given to the EVSE afterwards
TEST_F(AuthTest, test_plug_in_time_out) {
const SessionEvent session_event = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
this->auth_handler->handle_session_event(1, session_event);
std::vector<int32_t> connectors{1};
ProvidedIdToken provided_token = get_provided_token(VALID_TOKEN_1, connectors);
// ensure that the timeout has elapsed
const auto delay = std::chrono::milliseconds(250) + std::chrono::seconds(CONNECTION_TIMEOUT);
std::this_thread::sleep_for(delay);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Rejected));
// no connector should be available since the plug-in event has timed out
TokenHandlingResult result;
std::thread t1([this, provided_token, &result]() { result = this->auth_handler->on_token(provided_token); });
t1.join();
ASSERT_TRUE(result == TokenHandlingResult::NO_CONNECTOR_AVAILABLE);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
}
/// \brief Test that authorization is given to the EVSE afterwards if plug-in timeout is disabled
TEST_F(AuthTest, test_plug_in_time_out_disabled) {
this->auth_handler->set_plug_in_timeout_enabled(false);
const SessionEvent session_event = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
this->auth_handler->handle_session_event(1, session_event);
std::vector<int32_t> connectors{1};
ProvidedIdToken provided_token = get_provided_token(VALID_TOKEN_1, connectors);
std::this_thread::sleep_for(std::chrono::seconds(CONNECTION_TIMEOUT));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::UsedToStart));
TokenHandlingResult result;
std::thread t1([this, provided_token, &result]() { result = this->auth_handler->on_token(provided_token); });
t1.join();
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
}
/// \brief Test if a valid id_token is rejected in case a previous token is already assigned to the EVSE
TEST_F(AuthTest, test_subsequent_valid_tokens) {
std::vector<int32_t> connectors{1};
ProvidedIdToken provided_token_1 = get_provided_token(VALID_TOKEN_1, connectors);
ProvidedIdToken provided_token_2 = get_provided_token(VALID_TOKEN_2, connectors);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::UsedToStart));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Rejected));
TokenHandlingResult result1;
TokenHandlingResult result2;
std::thread t1([this, provided_token_1, &result1]() { result1 = this->auth_handler->on_token(provided_token_1); });
t1.join();
std::thread t2([this, provided_token_2, &result2]() { result2 = this->auth_handler->on_token(provided_token_2); });
t2.join();
ASSERT_TRUE(result1 == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(result2 == TokenHandlingResult::NO_CONNECTOR_AVAILABLE);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
}
/// \brief Test if a authorization can be withdrawn if an EV was connected and authorization was granted before
TEST_F(AuthTest, test_withdraw_authorization) {
const SessionEvent session_event = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
this->auth_handler->handle_session_event(1, session_event);
std::vector<int32_t> connectors{1};
ProvidedIdToken provided_token = get_provided_token(VALID_TOKEN_1, connectors);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::UsedToStart));
EXPECT_CALL(mock_withdraw_authorization_callback_mock, Call(0)).Times(1);
EXPECT_CALL(mock_stop_transaction_callback, Call(_, _)).Times(0);
const auto result = this->auth_handler->on_token(provided_token);
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
types::authorization::WithdrawAuthorizationRequest withdraw_request;
withdraw_request.evse_id = 1;
auto withdraw_result = this->auth_handler->handle_withdraw_authorization(withdraw_request);
ASSERT_EQ(withdraw_result, WithdrawAuthorizationResult::Accepted);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
SessionEvent deauthorized_event;
deauthorized_event.event = SessionEventEnum::Deauthorized;
this->auth_handler->handle_session_event(1, deauthorized_event);
withdraw_result = this->auth_handler->handle_withdraw_authorization(withdraw_request);
ASSERT_EQ(withdraw_result, WithdrawAuthorizationResult::AuthorizationNotFound);
}
/// \brief Test if a authorization can be withdrawn while authorization process is still selecting an EVSE
TEST_F(AuthTest, test_withdraw_authorization_while_waiting_for_ev_plugin) {
std::vector<int32_t> connectors{1, 2};
ProvidedIdToken provided_token1 = get_provided_token(VALID_TOKEN_1, connectors);
ProvidedIdToken provided_token2 = get_provided_token(VALID_TOKEN_2, connectors);
std::mutex mtx;
std::condition_variable cv;
bool processing_called1 = false;
bool processing_called2 = false;
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token1.id_token), TokenValidationStatus::Processing))
.WillOnce(Invoke([&]() {
std::unique_lock<std::mutex> lock(mtx);
processing_called1 = true;
cv.notify_all(); // Notify that the processing call happened
}));
;
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token1.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token1.id_token), TokenValidationStatus::Withdrawn));
EXPECT_CALL(mock_withdraw_authorization_callback_mock, Call(0)).Times(0);
EXPECT_CALL(mock_stop_transaction_callback, Call(_, _)).Times(0);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token2.id_token), TokenValidationStatus::Processing))
.WillOnce(Invoke([&]() {
std::unique_lock<std::mutex> lock(mtx);
processing_called2 = true;
cv.notify_all(); // Notify that the processing call happened
}));
;
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token2.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token2.id_token), TokenValidationStatus::Withdrawn));
EXPECT_CALL(mock_withdraw_authorization_callback_mock, Call(0)).Times(0);
EXPECT_CALL(mock_stop_transaction_callback, Call(_, _)).Times(0);
TokenHandlingResult result1;
TokenHandlingResult result2;
std::thread t1([this, provided_token1, &result1]() { result1 = this->auth_handler->on_token(provided_token1); });
std::thread t2([this, provided_token2, &result2]() { result2 = this->auth_handler->on_token(provided_token2); });
WithdrawAuthorizationResult withdraw_authorization_result1;
types::authorization::WithdrawAuthorizationRequest withdraw_request1;
withdraw_request1.id_token = {VALID_TOKEN_1, types::authorization::IdTokenType::ISO14443};
WithdrawAuthorizationResult withdraw_authorization_result2;
types::authorization::WithdrawAuthorizationRequest withdraw_request2;
withdraw_request2.id_token = {VALID_TOKEN_2, types::authorization::IdTokenType::ISO14443};
// Wait for TokenValidationStatus::Processing to be triggered
{
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [&]() { return processing_called1 and processing_called2; });
}
std::thread t3([this, withdraw_request1, &withdraw_authorization_result1]() {
withdraw_authorization_result1 = this->auth_handler->handle_withdraw_authorization(withdraw_request1);
});
std::thread t4([this, withdraw_request2, &withdraw_authorization_result2]() {
withdraw_authorization_result2 = this->auth_handler->handle_withdraw_authorization(withdraw_request2);
});
t1.join();
t2.join();
t3.join();
t4.join();
ASSERT_EQ(result1, TokenHandlingResult::WITHDRAWN);
ASSERT_EQ(withdraw_authorization_result1, WithdrawAuthorizationResult::Accepted);
ASSERT_EQ(result2, TokenHandlingResult::WITHDRAWN);
ASSERT_EQ(withdraw_authorization_result2, WithdrawAuthorizationResult::Accepted);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
}
/// \brief Test if a authorization is not withdrawn if the token does not match and the authorization requests ends with
/// a timeout
TEST_F(AuthTest, test_withdraw_authorization_while_waiting_for_ev_plugin_timeout) {
std::vector<int32_t> connectors{1, 2};
ProvidedIdToken provided_token = get_provided_token(VALID_TOKEN_1, connectors);
std::mutex mtx;
std::condition_variable cv;
bool processing_called = false;
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing))
.WillOnce(Invoke([&]() {
std::unique_lock<std::mutex> lock(mtx);
processing_called = true;
cv.notify_all(); // Notify that the processing call happened
}));
;
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::TimedOut));
EXPECT_CALL(mock_withdraw_authorization_callback_mock, Call(_)).Times(0);
EXPECT_CALL(mock_stop_transaction_callback, Call(_, _)).Times(0);
TokenHandlingResult result;
std::thread t1([this, provided_token, &result]() { result = this->auth_handler->on_token(provided_token); });
WithdrawAuthorizationResult withdraw_authorization_result;
types::authorization::WithdrawAuthorizationRequest withdraw_request;
withdraw_request.id_token = {VALID_TOKEN_2, types::authorization::IdTokenType::ISO14443};
// Wait for TokenValidationStatus::Processing to be triggered
{
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [&]() { return processing_called; });
}
std::thread t2([this, withdraw_request, &withdraw_authorization_result]() {
withdraw_authorization_result = this->auth_handler->handle_withdraw_authorization(withdraw_request);
});
t1.join();
t2.join();
ASSERT_EQ(result, TokenHandlingResult::TIMEOUT);
ASSERT_EQ(withdraw_authorization_result, WithdrawAuthorizationResult::AuthorizationNotFound);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
}
/// \brief Test if a authorization can be withdrawn during an active transaction
TEST_F(AuthTest, test_withdraw_authorization_during_transaction) {
const SessionEvent session_started_event =
get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
this->auth_handler->handle_session_event(1, session_started_event);
std::vector<int32_t> connectors{1};
ProvidedIdToken provided_token = get_provided_token(VALID_TOKEN_1, connectors);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::UsedToStart));
EXPECT_CALL(mock_withdraw_authorization_callback_mock, Call(_)).Times(0);
EXPECT_CALL(mock_stop_transaction_callback, Call(0, _)).Times(1);
const auto result = this->auth_handler->on_token(provided_token);
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
const SessionEvent transaction_started_event = get_transaction_started_event(provided_token);
this->auth_handler->handle_session_event(1, transaction_started_event);
types::authorization::WithdrawAuthorizationRequest withdraw_request;
withdraw_request.id_token = {VALID_TOKEN_1, types::authorization::IdTokenType::ISO14443};
this->auth_handler->handle_withdraw_authorization(withdraw_request);
}
/// \brief Test two successive authorization requests and connector selection. The first authorization request targets
/// only connector1, the second authorization request targets connector1 and connector2. Connector2 should be selected,
/// since the transaction at connector1 is already running.
TEST_F(AuthTest, test_two_authorization_plug_events) {
std::vector<int32_t> connector_1{1};
std::vector<int32_t> all_connectors{1, 2};
ProvidedIdToken provided_token_1 = get_provided_token(VALID_TOKEN_1, connector_1);
ProvidedIdToken provided_token_2 = get_provided_token(VALID_TOKEN_2, all_connectors);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::UsedToStart));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::UsedToStart));
TokenHandlingResult result1;
std::thread t1([this, provided_token_1, &result1]() { result1 = this->auth_handler->on_token(provided_token_1); });
SessionEvent session_event = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
std::thread t2([this, session_event]() { this->auth_handler->handle_session_event(1, session_event); });
t1.join();
t2.join();
ASSERT_TRUE(result1 == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
SessionEvent session_event2 = get_transaction_started_event(provided_token_1);
this->auth_handler->handle_session_event(1, session_event2);
TokenHandlingResult result2;
std::thread t3([this, provided_token_2, &result2]() { result2 = this->auth_handler->on_token(provided_token_2); });
t3.join();
ASSERT_TRUE(result2 == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
ASSERT_TRUE(this->auth_receiver->get_authorization(1));
}
TEST_F(AuthTest, test_token_swipe_race_with_timeout) {
auto token_entered_promise = std::make_shared<std::promise<void>>();
auto timeout_entered_promise = std::make_shared<std::promise<void>>();
std::shared_future<void> token_entered_future = token_entered_promise->get_future();
std::shared_future<void> timeout_entered_future = timeout_entered_promise->get_future();
std::atomic<bool> token_validation_started{false};
std::atomic<bool> timeout_triggered{false};
std::vector<int32_t> connectors{1};
ProvidedIdToken provided_token = get_provided_token(VALID_TOKEN_1, connectors);
// Plug-in first
SessionEvent session_event = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
this->auth_handler->handle_session_event(1, session_event);
this->auth_handler->register_validate_token_callback(
[token_entered_promise, timeout_entered_future](const ProvidedIdToken& provided_token) {
token_entered_promise->set_value();
timeout_entered_future.wait();
ValidationResult result;
result.authorization_status = AuthorizationStatus::Accepted;
result.parent_id_token = {PARENT_ID_TOKEN, types::authorization::IdTokenType::ISO14443};
return std::vector<ValidationResult>{ValidationResult{}, result};
});
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Rejected));
TokenHandlingResult result;
std::thread token_thread([&]() {
std::this_thread::sleep_for(std::chrono::milliseconds((CONNECTION_TIMEOUT * 1000) - 200));
result = this->auth_handler->on_token(provided_token);
});
std::thread timeout_simulation_thread([timeout_entered_promise, token_entered_future, &timeout_triggered]() {
token_entered_future.wait();
std::this_thread::sleep_for(std::chrono::milliseconds(200));
timeout_triggered = true;
timeout_entered_promise->set_value();
});
token_thread.join();
timeout_simulation_thread.join();
ASSERT_TRUE(timeout_triggered);
ASSERT_EQ(result, TokenHandlingResult::NO_CONNECTOR_AVAILABLE);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
}
/// \brief Test case-insensitive token comparison for authorization
TEST_F(AuthTest, test_case_insensitive_authorization) {
const SessionEvent session_event = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
this->auth_handler->handle_session_event(1, session_event);
std::vector<int32_t> connectors{1};
// Lowercase token for provided token
ProvidedIdToken provided_token = get_provided_token("valid_rfid_1", connectors);
// Validation callback returns uppercase VALID_TOKEN_1
ProvidedIdToken expected = provided_token;
expected.parent_id_token = {PARENT_ID_TOKEN, types::authorization::IdTokenType::ISO14443};
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback, Call(expected, TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback, Call(expected, TokenValidationStatus::UsedToStart));
const auto result = this->auth_handler->on_token(provided_token);
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
}
/// \brief Test case-insensitive token comparison for stopping transactions
TEST_F(AuthTest, test_case_insensitive_stop_transaction) {
std::vector<int32_t> connectors{1};
ProvidedIdToken provided_token = get_provided_token(VALID_TOKEN_1, connectors);
// Create a token with different case for stopping the transaction
ProvidedIdToken stop_token = get_provided_token("VALID_rfid_1", connectors);
SessionEvent session_event1 = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
SessionEvent session_event2 = get_transaction_started_event(provided_token);
this->auth_handler->handle_session_event(1, session_event1);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing))
.Times(1);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Accepted))
.Times(1);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::UsedToStart))
.Times(1);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, stop_token.id_token), TokenValidationStatus::Processing))
.Times(1);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, stop_token.id_token), TokenValidationStatus::UsedToStop))
.Times(1);
auto result = this->auth_handler->on_token(provided_token);
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
this->auth_handler->handle_session_event(1, session_event2);
result = this->auth_handler->on_token(stop_token);
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_STOP_TRANSACTION);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
}
/// \brief Test case-insensitive parent ID token comparison for stopping transactions
TEST_F(AuthTest, test_case_insensitive_parent_id_stop_transaction) {
TokenHandlingResult result;
std::vector<int32_t> connectors{1, 2};
ProvidedIdToken provided_token_1 = get_provided_token(VALID_TOKEN_1, connectors);
// Use different case for parent ID token
ProvidedIdToken provided_token_2 = get_provided_token(VALID_TOKEN_3, connectors);
SessionEvent session_event1 = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
this->auth_handler->handle_session_event(1, session_event1);
SessionEvent session_event2 = get_transaction_started_event(provided_token_1);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_1.id_token), TokenValidationStatus::UsedToStart));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token_2.id_token), TokenValidationStatus::UsedToStop));
// Start transaction with VALID_TOKEN_1
result = this->auth_handler->on_token(provided_token_1);
this->auth_handler->handle_session_event(1, session_event2);
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
// Stop transaction with VALID_TOKEN_3 (has same parent ID as VALID_TOKEN_1)
result = this->auth_handler->on_token(provided_token_2);
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_STOP_TRANSACTION);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
}
/// \brief Test case-insensitive comparison with master pass group ID
TEST_F(AuthTest, test_case_insensitive_master_pass_group_id) {
// Set master group id with different case than what will be returned in validation
this->auth_handler->set_master_pass_group_id("parent_rfid"); // lowercase
this->auth_handler->set_prioritize_authorization_over_stopping_transaction(false);
const SessionEvent session_event = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
this->auth_handler->handle_session_event(1, session_event);
// Test with a token that would have master group ID as parent - should be rejected initially
auto provided_token = get_provided_token("PARENT_RFID"); // uppercase
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Rejected));
auto result = this->auth_handler->on_token(provided_token);
ASSERT_TRUE(result == TokenHandlingResult::REJECTED);
// Start a transaction with a valid token
provided_token = get_provided_token(VALID_TOKEN_2);
provided_token.parent_id_token = std::nullopt;
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::UsedToStart));
result = this->auth_handler->on_token(provided_token);
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_START_TRANSACTION);
SessionEvent session_event2 = get_transaction_started_event(provided_token);
this->auth_handler->handle_session_event(1, session_event2);
// Test if master group id token can stop transactions (case insensitive)
// Use a token that gets validated and returns a parent_id_token matching master group ID (different case)
provided_token.id_token = {VALID_TOKEN_1, types::authorization::IdTokenType::ISO14443};
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::UsedToStop));
result = this->auth_handler->on_token(provided_token);
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_STOP_TRANSACTION);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
}
/// \brief Test case-insensitive withdraw authorization
TEST_F(AuthTest, test_case_insensitive_withdraw_authorization) {
const SessionEvent session_event = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
this->auth_handler->handle_session_event(1, session_event);
std::vector<int32_t> connectors{1};
ProvidedIdToken provided_token = get_provided_token(VALID_TOKEN_1, connectors);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::UsedToStart));
EXPECT_CALL(mock_withdraw_authorization_callback_mock, Call(0)).Times(1);
const auto result = this->auth_handler->on_token(provided_token);
ASSERT_TRUE(result == TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
// Test withdraw with different case
types::authorization::WithdrawAuthorizationRequest withdraw_request;
withdraw_request.evse_id = 1;
withdraw_request.id_token = {"valid_RFID_1", types::authorization::IdTokenType::ISO14443};
auto withdraw_result = this->auth_handler->handle_withdraw_authorization(withdraw_request);
ASSERT_EQ(withdraw_result, WithdrawAuthorizationResult::Accepted);
ASSERT_FALSE(this->auth_receiver->get_authorization(0));
}
/// \brief Test case-insensitive reservation matching
TEST_F(AuthTest, test_case_insensitive_reservation_matching) {
TokenHandlingResult result;
Reservation reservation;
reservation.evse_id = 1;
reservation.id_token = "VALID_rfid_2"; // Different case
reservation.reservation_id = 1;
reservation.connector_type = types::evse_manager::ConnectorTypeEnum::cCCS2;
reservation.expiry_time = Everest::Date::to_rfc3339(date::utc_clock::now() + std::chrono::hours(1));
const auto reservation_result = this->auth_handler->handle_reservation(reservation);
ASSERT_EQ(reservation_result, ReservationResult::Accepted);
SessionEvent session_event_1;
session_event_1.event = SessionEventEnum::ReservationStart;
this->auth_handler->handle_session_event(1, session_event_1);
SessionEvent session_event_2 = get_session_started_event(types::evse_manager::StartSessionReason::EVConnected);
this->auth_handler->handle_session_event(1, session_event_2);
std::vector<int32_t> connectors{1, 2};
// Use different case for token comparison
ProvidedIdToken provided_token = get_provided_token("valid_RFID_2", connectors);
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Processing));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::Accepted));
EXPECT_CALL(mock_publish_token_validation_status_callback,
Call(Field(&ProvidedIdToken::id_token, provided_token.id_token), TokenValidationStatus::UsedToStart));
result = this->auth_handler->on_token(provided_token);
ASSERT_EQ(result, TokenHandlingResult::USED_TO_START_TRANSACTION);
ASSERT_TRUE(this->auth_receiver->get_authorization(0));
ASSERT_FALSE(this->auth_receiver->get_authorization(1));
}
} // namespace module