From 887e99c909a64cc97eede11b6b39824ff770997a Mon Sep 17 00:00:00 2001 From: Vincent W Date: Fri, 3 Oct 2025 15:20:37 +0200 Subject: [PATCH 1/8] feat(GradeCalculator): Add tests --- .../CMakeLists.txt | 14 +++ .../test/GradeCalculator.test.cpp | 103 ++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 src/g2_2025_grade_calculator_pkg/test/GradeCalculator.test.cpp diff --git a/src/g2_2025_grade_calculator_pkg/CMakeLists.txt b/src/g2_2025_grade_calculator_pkg/CMakeLists.txt index 7eef918..814ea15 100644 --- a/src/g2_2025_grade_calculator_pkg/CMakeLists.txt +++ b/src/g2_2025_grade_calculator_pkg/CMakeLists.txt @@ -127,6 +127,20 @@ if(BUILD_TESTING) target_link_libraries(${PROJECT_NAME}_test_exam_result_generator pqxx pq tomlplusplus::tomlplusplus ) + + # Add gtest for GradeCalculator + ament_add_gtest(${PROJECT_NAME}_test_grade_calculator + test/GradeCalculator.test.cpp + src/grade_calculator/nodes/GradeCalculator.cpp + ) + target_include_directories(${PROJECT_NAME}_test_grade_calculator PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/src/grade_calculator + ) + ament_target_dependencies(${PROJECT_NAME}_test_grade_calculator + rclcpp + g2_2025_interfaces + ) endif() ament_package() diff --git a/src/g2_2025_grade_calculator_pkg/test/GradeCalculator.test.cpp b/src/g2_2025_grade_calculator_pkg/test/GradeCalculator.test.cpp new file mode 100644 index 0000000..c213ec4 --- /dev/null +++ b/src/g2_2025_grade_calculator_pkg/test/GradeCalculator.test.cpp @@ -0,0 +1,103 @@ +#include +#include +#include +#include +#include "grade_calculator/nodes/GradeCalculator.hpp" + +using namespace std::chrono_literals; +using assignments::one::grade_calculator::GradeCalculator; + +class GradeCalculatorTest : public ::testing::Test +{ +protected: + static void SetUpTestSuite() { + int argc = 0; + char **argv = nullptr; + rclcpp::init(argc, argv); + } + + static void TearDownTestSuite() { + rclcpp::shutdown(); + } + + void SetUp() override + { + // Start GradeCalculator node + grade_calculator_node_ = std::make_shared(); + executor_ = std::make_shared(); + executor_->add_node(grade_calculator_node_); + + // Spin in background thread + spin_thread_ = std::thread([this]() { + executor_->spin(); + }); + + // Create client node + client_node_ = rclcpp::Node::make_shared("grade_calculator_test_client"); + client_ = client_node_->create_client("grade_calculator_service"); + } + + void TearDown() override + { + executor_->cancel(); + if (spin_thread_.joinable()) { + spin_thread_.join(); + } + + grade_calculator_node_.reset(); + client_.reset(); + client_node_.reset(); + } + + int call_service(const std::string &name, const std::vector &grades) + { + if (!client_->wait_for_service(5s)) { + throw std::runtime_error("Service not available"); + } + + auto request = std::make_shared(); + request->student_name = name; + request->exam_grades = grades; + + auto future = client_->async_send_request(request); + + if (rclcpp::spin_until_future_complete(client_node_, future) == rclcpp::FutureReturnCode::SUCCESS) { + return future.get()->result; + } else { + throw std::runtime_error("Service call failed"); + } + } + + std::shared_ptr executor_; + std::shared_ptr grade_calculator_node_; + std::thread spin_thread_; + rclcpp::Node::SharedPtr client_node_; + rclcpp::Client::SharedPtr client_; +}; + +// ---- TEST CASES ---- + +TEST_F(GradeCalculatorTest, NormalAverage) +{ + EXPECT_EQ(call_service("Alice", {80, 90, 70}), 80); +} + +TEST_F(GradeCalculatorTest, WesselBonus) +{ + EXPECT_EQ(call_service("Wessel", {80, 90, 70}), 90); +} + +TEST_F(GradeCalculatorTest, wesselBonus) +{ + EXPECT_EQ(call_service("wessel", {80, 90, 70}), 90); +} + +TEST_F(GradeCalculatorTest, GradeTooHigh) +{ + EXPECT_EQ(call_service("Wessel", {100, 100, 100}), 100); +} + +TEST_F(GradeCalculatorTest, GradeTooLow) +{ + EXPECT_EQ(call_service("Alice", {0, 0, 0}), 10); +} -- 2.39.5 From 25e21a15fc26c0c1b257d42c2e136a27eaf49ce8 Mon Sep 17 00:00:00 2001 From: Vincent W Date: Fri, 3 Oct 2025 16:42:57 +0200 Subject: [PATCH 2/8] feat(FinalGradeDeterminator): Add tests --- .../CMakeLists.txt | 19 ++ .../test/FinalGradeDeterminator.test.cpp | 183 ++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 src/g2_2025_grade_calculator_pkg/test/FinalGradeDeterminator.test.cpp diff --git a/src/g2_2025_grade_calculator_pkg/CMakeLists.txt b/src/g2_2025_grade_calculator_pkg/CMakeLists.txt index 814ea15..cb4a3e2 100644 --- a/src/g2_2025_grade_calculator_pkg/CMakeLists.txt +++ b/src/g2_2025_grade_calculator_pkg/CMakeLists.txt @@ -141,6 +141,25 @@ if(BUILD_TESTING) rclcpp g2_2025_interfaces ) + + # Add gtest for FinalGradeDeterminator + ament_add_gtest(${PROJECT_NAME}_test_final_grade_determinator + test/FinalGradeDeterminator.test.cpp + src/final_grade_determinator/nodes/FinalGradeDeterminator.cpp + src/database/DatabaseManager.cpp + src/config/ConfigManager.cpp + ) + target_include_directories(${PROJECT_NAME}_test_final_grade_determinator PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/src/final_grade_determinator + ) + ament_target_dependencies(${PROJECT_NAME}_test_final_grade_determinator + rclcpp + g2_2025_interfaces + ) + target_link_libraries(${PROJECT_NAME}_test_final_grade_determinator + pqxx pq tomlplusplus::tomlplusplus + ) endif() ament_package() diff --git a/src/g2_2025_grade_calculator_pkg/test/FinalGradeDeterminator.test.cpp b/src/g2_2025_grade_calculator_pkg/test/FinalGradeDeterminator.test.cpp new file mode 100644 index 0000000..bce83d9 --- /dev/null +++ b/src/g2_2025_grade_calculator_pkg/test/FinalGradeDeterminator.test.cpp @@ -0,0 +1,183 @@ +#include +#include +#include +#include + +#include "final_grade_determinator/nodes/FinalGradeDeterminator.hpp" +#include "database/DatabaseManager.hpp" +#include "g2_2025_interfaces/msg/exam.hpp" +#include "g2_2025_interfaces/msg/student.hpp" +#include "g2_2025_interfaces/srv/exams.hpp" + +using namespace std::chrono_literals; +using namespace assignments::one::final_grade_determinator; + +class FinalGradeDeterminatorTest : public ::testing::Test { +protected: + void SetUp() override { + rclcpp::init(0, nullptr); + + test_node_ = std::make_shared("test_node"); + + // Subscriber to capture student messages + student_subscriber_ = test_node_->create_subscription( + "student_course_management", 10, + [this](const g2_2025_interfaces::msg::Student::SharedPtr msg) { + received_student_messages_.push_back(*msg); + } + ); + + // Publisher to send exam messages + exam_publisher_ = test_node_->create_publisher( + "exam_results", 10 + ); + + // Mock service server for grade calculator + grade_calculator_service_ = test_node_->create_service( + "grade_calculator_service", + [this](const g2_2025_interfaces::srv::Exams::Request::SharedPtr request, + g2_2025_interfaces::srv::Exams::Response::SharedPtr response) { + service_requests_.push_back(*request); + // Mock calculation - average of grades + int sum = 0; + for (const auto& grade : request->exam_grades) { + sum += grade; + } + response->result = sum / request->exam_grades.size(); + } + ); + + received_student_messages_.clear(); + service_requests_.clear(); + } + + void TearDown() override { + final_grade_determinator_.reset(); + test_node_.reset(); + rclcpp::shutdown(); + } + + void create_final_grade_determinator() { + final_grade_determinator_ = std::make_shared(); + } + + void spin_some_time(std::chrono::milliseconds duration = 100ms) { + auto start_time = std::chrono::steady_clock::now(); + while (std::chrono::steady_clock::now() - start_time < duration) { + rclcpp::spin_some(test_node_); + if (final_grade_determinator_) { + rclcpp::spin_some(final_grade_determinator_); + } + std::this_thread::sleep_for(10ms); + } + } + + std::shared_ptr test_node_; + std::shared_ptr final_grade_determinator_; + rclcpp::Subscription::SharedPtr student_subscriber_; + rclcpp::Publisher::SharedPtr exam_publisher_; + rclcpp::Service::SharedPtr grade_calculator_service_; + + std::vector received_student_messages_; + std::vector service_requests_; +}; + +TEST_F(FinalGradeDeterminatorTest, ConstructorTest) { + ASSERT_NO_THROW({ + create_final_grade_determinator(); + }); + + ASSERT_NE(final_grade_determinator_, nullptr); +} + +TEST_F(FinalGradeDeterminatorTest, ExamCollectionTest) { + create_final_grade_determinator(); + spin_some_time(500ms); + + const std::string student_name = "Test Student"; + const std::string course_name = "Test Course"; + std::vector grades = {80, 85, 90, 75, 95}; + + // Send 5 exam results (default collection amount) + for (const auto& grade : grades) { + auto msg = std::make_shared(); + msg->student_name = student_name; + msg->course_name = course_name; + msg->result = grade; + msg->timestamp = test_node_->now(); + + exam_publisher_->publish(*msg); + spin_some_time(50ms); + } + + spin_some_time(1s); + + // Verify service request was made + ASSERT_FALSE(service_requests_.empty()); + EXPECT_EQ(service_requests_.back().student_name, student_name); + EXPECT_EQ(service_requests_.back().course_name, course_name); + EXPECT_EQ(service_requests_.back().exam_grades, grades); + + // Verify student message was published + ASSERT_FALSE(received_student_messages_.empty()); + EXPECT_EQ(received_student_messages_.back().student_name, student_name); + EXPECT_EQ(received_student_messages_.back().course_name, course_name); +} + +TEST_F(FinalGradeDeterminatorTest, PartialExamCollectionTest) { + create_final_grade_determinator(); + spin_some_time(500ms); + + // Send only 3 exam results (less than required 5) + const std::string student_name = "Test Student"; + const std::string course_name = "Test Course"; + std::vector grades = {80, 85, 90}; + + for (const auto& grade : grades) { + auto msg = std::make_shared(); + msg->student_name = student_name; + msg->course_name = course_name; + msg->result = grade; + msg->timestamp = test_node_->now(); + + exam_publisher_->publish(*msg); + spin_some_time(50ms); + } + + spin_some_time(1s); + + // Verify no service request was made yet + EXPECT_TRUE(service_requests_.empty()); + // Verify no student message was published + EXPECT_TRUE(received_student_messages_.empty()); +} + +TEST_F(FinalGradeDeterminatorTest, MultipleStudentsTest) { + create_final_grade_determinator(); + spin_some_time(500ms); + + std::vector students = {"Alice", "Bob"}; + const std::string course_name = "Test Course"; + std::vector grades = {80, 85, 90, 75, 95}; + + // Send complete set of grades for each student + for (const auto& student : students) { + for (const auto& grade : grades) { + auto msg = std::make_shared(); + msg->student_name = student; + msg->course_name = course_name; + msg->result = grade; + msg->timestamp = test_node_->now(); + + exam_publisher_->publish(*msg); + spin_some_time(50ms); + } + } + + spin_some_time(1s); + + // Verify service requests were made for both students + ASSERT_EQ(service_requests_.size(), 2); + // Verify student messages were published for both students + ASSERT_EQ(received_student_messages_.size(), 2); +} -- 2.39.5 From 1e7c7cefe5bb25752aade931023041d476fcf1d8 Mon Sep 17 00:00:00 2001 From: Vincent W Date: Fri, 3 Oct 2025 16:43:28 +0200 Subject: [PATCH 3/8] fix(gitignore): add .vscode folder --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index c32553f..216cdd7 100644 --- a/.gitignore +++ b/.gitignore @@ -33,5 +33,7 @@ qtcreator-* COLCON_IGNORE AMENT_IGNORE +.vscode + # End of https://www.toptal.com/developers/gitignore/api/ros2 -- 2.39.5 From a325e19a41fd15195740cfa3cc10f6982364300e Mon Sep 17 00:00:00 2001 From: Vincent W Date: Fri, 3 Oct 2025 17:18:10 +0200 Subject: [PATCH 4/8] feat(GradeCalc,GradeDeterm): Add documention --- doc/nodes/FinalGradeDeterminator.md | 35 +++++++++++++++++++++++++++++ doc/nodes/GradeCalculator.md | 25 +++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 doc/nodes/FinalGradeDeterminator.md create mode 100644 doc/nodes/GradeCalculator.md diff --git a/doc/nodes/FinalGradeDeterminator.md b/doc/nodes/FinalGradeDeterminator.md new file mode 100644 index 0000000..0c2f3d3 --- /dev/null +++ b/doc/nodes/FinalGradeDeterminator.md @@ -0,0 +1,35 @@ +# FinalGradeDeterminator (`assignments::one::final_grade_determinator`) + +## Overview +The `FinalGradeDeterminator` node collects exam results for student-course combinations, triggers grade calculation when enough results are gathered, and stores final grades in the database. It interacts with ROS2 publishers, subscribers, and service clients to manage the grading workflow. + +#### Implementation Details + +**Constructor** +```cpp +FinalGradeDeterminator() +``` +- Initializes ROS2 node with name `final_grade_determinator` +- Declares and retrieves `grade_collection_amount` parameter +- Sets up `DatabaseManager` +- Creates publisher for student course management +- Subscribes to exam results topic +- Initializes service client for grade calculation + +**Core Functions** + +**`void exam_results_callback(const g2_2025_interfaces::msg::Exam::SharedPtr msg)`** +- Updates internal map with received exam result for student-course combo +- Checks if enough results have been collected +- Triggers grade calculation request when threshold is met + +**`void grade_calculator_request(StudentCourse combo)`** +- Waits for grade calculator service to be available +- Sends async request with collected exam grades for the student-course combination +- Uses callback to handle service response + +**`void grade_calculator_response(rclcpp::Client::SharedFuture future, StudentCourse studentCourseCombo)`** +- Verifies database connection +- Publishes final student message to ROS2 topic +- Logs final grade information +- Stores final course result in database diff --git a/doc/nodes/GradeCalculator.md b/doc/nodes/GradeCalculator.md new file mode 100644 index 0000000..0e90fd5 --- /dev/null +++ b/doc/nodes/GradeCalculator.md @@ -0,0 +1,25 @@ +# GradeCalculator (`assignments::one::grade_calculator::GradeCalculator`) + +## Overview +The `GradeCalculator` node provides a ROS2 service for calculating student exam grades. It processes exam scores, applies custom logic for specific student names, and ensures grade results are within valid bounds. + +#### Implementation Details + +**Constructor** +```cpp +GradeCalculator() +``` +- Initializes ROS2 node with name `grade_calculator` +- Creates a ROS2 service server for `grade_calculator_service` +- Binds the service callback to handle grade calculation requests +- Logs service startup + +**Core Functions** + +**`void grade_calculator_callback(const Exams::Request::SharedPtr request, const Exams::Response::SharedPtr response)`** +- Checks if exam grades are provided +- Calculates the total and average of exam grades +- Converts student name to lowercase for comparison +- Adds a bonus of 10 points if the student name is "wessel" +- Ensures the final grade is clamped between 10 and 100 +- Sends the calculated grade back through the service response -- 2.39.5 From d89f47833ee1a2ac0239a5a79188f74d23186af1 Mon Sep 17 00:00:00 2001 From: Vincent W Date: Sat, 4 Oct 2025 10:16:59 +0200 Subject: [PATCH 5/8] fix(FinalGradeDeterminator): Make db_manager optional for testing --- .../nodes/FinalGradeDeterminator.cpp | 10 +++++++--- .../nodes/FinalGradeDeterminator.hpp | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/g2_2025_grade_calculator_pkg/src/final_grade_determinator/nodes/FinalGradeDeterminator.cpp b/src/g2_2025_grade_calculator_pkg/src/final_grade_determinator/nodes/FinalGradeDeterminator.cpp index bcf753d..7bebb39 100644 --- a/src/g2_2025_grade_calculator_pkg/src/final_grade_determinator/nodes/FinalGradeDeterminator.cpp +++ b/src/g2_2025_grade_calculator_pkg/src/final_grade_determinator/nodes/FinalGradeDeterminator.cpp @@ -2,12 +2,16 @@ namespace assignments::one::final_grade_determinator { -FinalGradeDeterminator::FinalGradeDeterminator() : Node("final_grade_determinator") { +FinalGradeDeterminator::FinalGradeDeterminator(std::unique_ptr db_manager) : Node("final_grade_determinator") { this->declare_parameter("grade_collection_amount", 5); grade_collection_amount_ = this->get_parameter("grade_collection_amount").as_int(); - db_manager_ = std::make_unique(this->get_logger()); - + // Make db_manager optional for testing purposes + if (db_manager) { + db_manager_ = std::move(db_manager); + } else { + db_manager_ = std::make_unique(this->get_logger()); + } // Create publisher for exam results student_publisher_ = this->create_publisher( "student_course_management", 10 diff --git a/src/g2_2025_grade_calculator_pkg/src/final_grade_determinator/nodes/FinalGradeDeterminator.hpp b/src/g2_2025_grade_calculator_pkg/src/final_grade_determinator/nodes/FinalGradeDeterminator.hpp index 8703bc7..b9db482 100644 --- a/src/g2_2025_grade_calculator_pkg/src/final_grade_determinator/nodes/FinalGradeDeterminator.hpp +++ b/src/g2_2025_grade_calculator_pkg/src/final_grade_determinator/nodes/FinalGradeDeterminator.hpp @@ -18,7 +18,7 @@ namespace assignments::one::final_grade_determinator { class FinalGradeDeterminator : public rclcpp::Node { public: - FinalGradeDeterminator(); + FinalGradeDeterminator(std::unique_ptr db_manager = nullptr); private: rclcpp::Subscription::SharedPtr exam_subscriber_; rclcpp::Publisher::SharedPtr student_publisher_; -- 2.39.5 From 447834dda7dbcd0b465d5ddc0eb5a40680f0d8a4 Mon Sep 17 00:00:00 2001 From: Vincent W Date: Sat, 4 Oct 2025 10:17:32 +0200 Subject: [PATCH 6/8] fix(DatabaseManager): Make functions virtual for testing --- .../src/database/DatabaseManager.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/g2_2025_grade_calculator_pkg/src/database/DatabaseManager.hpp b/src/g2_2025_grade_calculator_pkg/src/database/DatabaseManager.hpp index 1d3b2d2..ac73413 100644 --- a/src/g2_2025_grade_calculator_pkg/src/database/DatabaseManager.hpp +++ b/src/g2_2025_grade_calculator_pkg/src/database/DatabaseManager.hpp @@ -24,10 +24,10 @@ public: ~DatabaseManager() = default; bool connect(const std::string& connection_string); - bool is_connected() const; + virtual bool is_connected() const; // Table operations - void init_database(); + virtual void init_database(); void create_tables(); void insert_sample_data(); @@ -35,7 +35,7 @@ public: std::vector queue_pending_combinations(); bool store_exam_result(const std::string& student_name, const std::string& course_name, int grade); bool enroll_student_into_course(const StudentCourse& sc); - bool store_final_course_result(const StudentCourse& sc, int exam_count, int final_grade); + virtual bool store_final_course_result(const StudentCourse& sc, int exam_count, int final_grade); int get_final_course_grade(const StudentCourse& sc); -- 2.39.5 From b69dbda1a5b311539cbf4c3161f5591a8b26f7d7 Mon Sep 17 00:00:00 2001 From: Vincent W Date: Sat, 4 Oct 2025 10:21:55 +0200 Subject: [PATCH 7/8] fix(FinalGradeDeterminator): Add mock class for DatabaseManger The mock class is used to bypass the blocking error of the FinalGradeDeterminator when no Database is present for testing purposes --- .../test/FinalGradeDeterminator.test.cpp | 37 +++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/src/g2_2025_grade_calculator_pkg/test/FinalGradeDeterminator.test.cpp b/src/g2_2025_grade_calculator_pkg/test/FinalGradeDeterminator.test.cpp index bce83d9..311a332 100644 --- a/src/g2_2025_grade_calculator_pkg/test/FinalGradeDeterminator.test.cpp +++ b/src/g2_2025_grade_calculator_pkg/test/FinalGradeDeterminator.test.cpp @@ -12,6 +12,36 @@ using namespace std::chrono_literals; using namespace assignments::one::final_grade_determinator; +namespace assignments::one { + +class FakeDatabaseManager : public DatabaseManager { +public: + explicit FakeDatabaseManager(rclcpp::Logger logger = rclcpp::get_logger("fake_db")) + : DatabaseManager(logger) {} + + bool is_connected() const override { + return true; // Always pretend we are connected + } + + bool store_final_course_result(const StudentCourse& sc, int exam_count, int final_grade) override { + stored_results_.push_back({sc, exam_count, final_grade}); + return true; // Always succeed + } + + void init_database() override { + // No-op for fake + } + + struct StoredResult { + StudentCourse sc; + int exam_count; + int final_grade; + }; + std::vector stored_results_; +}; + +} // namespace assignments::one + class FinalGradeDeterminatorTest : public ::testing::Test { protected: void SetUp() override { @@ -53,12 +83,14 @@ protected: void TearDown() override { final_grade_determinator_.reset(); + fake_db_.reset(); test_node_.reset(); rclcpp::shutdown(); } void create_final_grade_determinator() { - final_grade_determinator_ = std::make_shared(); + fake_db_ = std::make_unique(); + final_grade_determinator_ = std::make_shared(std::move(fake_db_)); } void spin_some_time(std::chrono::milliseconds duration = 100ms) { @@ -71,13 +103,12 @@ protected: std::this_thread::sleep_for(10ms); } } - + std::unique_ptr fake_db_; std::shared_ptr test_node_; std::shared_ptr final_grade_determinator_; rclcpp::Subscription::SharedPtr student_subscriber_; rclcpp::Publisher::SharedPtr exam_publisher_; rclcpp::Service::SharedPtr grade_calculator_service_; - std::vector received_student_messages_; std::vector service_requests_; }; -- 2.39.5 From 2ab1c1c31fe5605d3dab4a67acbfe47a6f001d9d Mon Sep 17 00:00:00 2001 From: Vincent W Date: Sun, 5 Oct 2025 18:19:07 +0200 Subject: [PATCH 8/8] fix(tests): fix formatting, syntax and struct positioning --- .../test/FinalGradeDeterminator.test.cpp | 59 +++++++++++-------- .../test/GradeCalculator.test.cpp | 29 ++++----- 2 files changed, 45 insertions(+), 43 deletions(-) diff --git a/src/g2_2025_grade_calculator_pkg/test/FinalGradeDeterminator.test.cpp b/src/g2_2025_grade_calculator_pkg/test/FinalGradeDeterminator.test.cpp index 311a332..4be2461 100644 --- a/src/g2_2025_grade_calculator_pkg/test/FinalGradeDeterminator.test.cpp +++ b/src/g2_2025_grade_calculator_pkg/test/FinalGradeDeterminator.test.cpp @@ -14,30 +14,31 @@ using namespace assignments::one::final_grade_determinator; namespace assignments::one { -class FakeDatabaseManager : public DatabaseManager { +struct MockStoredResult { + StudentCourse sc; + int exam_count; + int final_grade; +}; + +class MockDatabaseManager : public DatabaseManager { public: - explicit FakeDatabaseManager(rclcpp::Logger logger = rclcpp::get_logger("fake_db")) - : DatabaseManager(logger) {} + explicit MockDatabaseManager(rclcpp::Logger logger = rclcpp::get_logger("fake_db")) + : DatabaseManager(logger) { + } bool is_connected() const override { return true; // Always pretend we are connected } bool store_final_course_result(const StudentCourse& sc, int exam_count, int final_grade) override { - stored_results_.push_back({sc, exam_count, final_grade}); + stored_results_.push_back({ sc, exam_count, final_grade }); return true; // Always succeed } void init_database() override { - // No-op for fake - } + } // no-op - struct StoredResult { - StudentCourse sc; - int exam_count; - int final_grade; - }; - std::vector stored_results_; + std::vector stored_results_; }; } // namespace assignments::one @@ -66,14 +67,14 @@ protected: grade_calculator_service_ = test_node_->create_service( "grade_calculator_service", [this](const g2_2025_interfaces::srv::Exams::Request::SharedPtr request, - g2_2025_interfaces::srv::Exams::Response::SharedPtr response) { - service_requests_.push_back(*request); - // Mock calculation - average of grades - int sum = 0; - for (const auto& grade : request->exam_grades) { - sum += grade; - } - response->result = sum / request->exam_grades.size(); + g2_2025_interfaces::srv::Exams::Response::SharedPtr response) { + service_requests_.push_back(*request); + // Mock calculation - average of grades + int sum = 0; + for (const auto& grade : request->exam_grades) { + sum += grade; + } + response->result = sum / request->exam_grades.size(); } ); @@ -89,21 +90,25 @@ protected: } void create_final_grade_determinator() { - fake_db_ = std::make_unique(); + fake_db_ = std::make_unique(); final_grade_determinator_ = std::make_shared(std::move(fake_db_)); } void spin_some_time(std::chrono::milliseconds duration = 100ms) { auto start_time = std::chrono::steady_clock::now(); + while (std::chrono::steady_clock::now() - start_time < duration) { rclcpp::spin_some(test_node_); + if (final_grade_determinator_) { rclcpp::spin_some(final_grade_determinator_); } + std::this_thread::sleep_for(10ms); } } - std::unique_ptr fake_db_; + + std::unique_ptr fake_db_; std::shared_ptr test_node_; std::shared_ptr final_grade_determinator_; rclcpp::Subscription::SharedPtr student_subscriber_; @@ -113,6 +118,8 @@ protected: std::vector service_requests_; }; +// ---- TEST CASES ---- + TEST_F(FinalGradeDeterminatorTest, ConstructorTest) { ASSERT_NO_THROW({ create_final_grade_determinator(); @@ -127,7 +134,7 @@ TEST_F(FinalGradeDeterminatorTest, ExamCollectionTest) { const std::string student_name = "Test Student"; const std::string course_name = "Test Course"; - std::vector grades = {80, 85, 90, 75, 95}; + std::vector grades = { 80, 85, 90, 75, 95 }; // Send 5 exam results (default collection amount) for (const auto& grade : grades) { @@ -162,7 +169,7 @@ TEST_F(FinalGradeDeterminatorTest, PartialExamCollectionTest) { // Send only 3 exam results (less than required 5) const std::string student_name = "Test Student"; const std::string course_name = "Test Course"; - std::vector grades = {80, 85, 90}; + std::vector grades = { 80, 85, 90 }; for (const auto& grade : grades) { auto msg = std::make_shared(); @@ -187,9 +194,9 @@ TEST_F(FinalGradeDeterminatorTest, MultipleStudentsTest) { create_final_grade_determinator(); spin_some_time(500ms); - std::vector students = {"Alice", "Bob"}; + std::vector students = { "Alice", "Bob" }; const std::string course_name = "Test Course"; - std::vector grades = {80, 85, 90, 75, 95}; + std::vector grades = { 80, 85, 90, 75, 95 }; // Send complete set of grades for each student for (const auto& student : students) { diff --git a/src/g2_2025_grade_calculator_pkg/test/GradeCalculator.test.cpp b/src/g2_2025_grade_calculator_pkg/test/GradeCalculator.test.cpp index c213ec4..c3d9a65 100644 --- a/src/g2_2025_grade_calculator_pkg/test/GradeCalculator.test.cpp +++ b/src/g2_2025_grade_calculator_pkg/test/GradeCalculator.test.cpp @@ -12,7 +12,7 @@ class GradeCalculatorTest : public ::testing::Test protected: static void SetUpTestSuite() { int argc = 0; - char **argv = nullptr; + char** argv = nullptr; rclcpp::init(argc, argv); } @@ -49,7 +49,7 @@ protected: client_node_.reset(); } - int call_service(const std::string &name, const std::vector &grades) + int call_service(const std::string& name, const std::vector& grades) { if (!client_->wait_for_service(5s)) { throw std::runtime_error("Service not available"); @@ -77,27 +77,22 @@ protected: // ---- TEST CASES ---- -TEST_F(GradeCalculatorTest, NormalAverage) -{ - EXPECT_EQ(call_service("Alice", {80, 90, 70}), 80); +TEST_F(GradeCalculatorTest, NormalAverage) { + EXPECT_EQ(call_service("Alice", { 80, 90, 70 }), 80); } -TEST_F(GradeCalculatorTest, WesselBonus) -{ - EXPECT_EQ(call_service("Wessel", {80, 90, 70}), 90); +TEST_F(GradeCalculatorTest, WesselBonus) { + EXPECT_EQ(call_service("Wessel", { 80, 90, 70 }), 90); } -TEST_F(GradeCalculatorTest, wesselBonus) -{ - EXPECT_EQ(call_service("wessel", {80, 90, 70}), 90); +TEST_F(GradeCalculatorTest, wesselBonus) { + EXPECT_EQ(call_service("wessel", { 80, 90, 70 }), 90); } -TEST_F(GradeCalculatorTest, GradeTooHigh) -{ - EXPECT_EQ(call_service("Wessel", {100, 100, 100}), 100); +TEST_F(GradeCalculatorTest, GradeTooHigh) { + EXPECT_EQ(call_service("Wessel", { 100, 100, 100 }), 100); } -TEST_F(GradeCalculatorTest, GradeTooLow) -{ - EXPECT_EQ(call_service("Alice", {0, 0, 0}), 10); +TEST_F(GradeCalculatorTest, GradeTooLow) { + EXPECT_EQ(call_service("Alice", { 0, 0, 0 }), 10); } -- 2.39.5