// SPDX-License-Identifier: Apache-2.0 // Copyright Pionix GmbH and Contributors to EVerest #include #include #include #include #include #include #include #include #include #include #include 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> connectors = std::nullopt, std::optional 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 auth_handler; std::unique_ptr auth_receiver; testing::MockFunction send_callback_mock; StrictMock> mock_publish_token_validation_status_callback; testing::MockFunction mock_stop_transaction_callback; testing::MockFunction mock_withdraw_authorization_callback_mock; void SetUp() override { std::vector evse_indices{0, 1}; this->auth_receiver = std::make_unique(evse_indices); const std::string id = "auth_handler_test_id"; this->auth_handler = std::make_unique(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 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 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& /*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 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 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 connectors1{1, 2}; ProvidedIdToken provided_token1 = get_provided_token(VALID_TOKEN_1, connectors1); std::vector 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 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 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 connectors1{1, 2}; std::vector connectors2{3, 4}; std::vector 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 validation_results; const auto id_token = provided_token.id_token.value; std::vector evse_ids{2}; std::vector 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 connector_1{1}; std::vector 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>(); auto timeout_entered_promise = std::make_shared>(); std::shared_future token_entered_future = token_entered_promise->get_future(); std::shared_future timeout_entered_future = timeout_entered_promise->get_future(); std::atomic token_validation_started{false}; std::atomic timeout_triggered{false}; std::vector 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{}, 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 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 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 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 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 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