From fd07992eeeef93c82679825b43e4829237809bce Mon Sep 17 00:00:00 2001 From: Vincent W Date: Tue, 7 Oct 2025 14:42:44 +0200 Subject: [PATCH 01/14] fix(documentation): Add test documentation to GradeCalculator and FinalGradeDeterminator --- doc/nodes/FinalGradeDeterminator.md | 68 ++++++++++++++++++++++++++++- doc/nodes/GradeCalculator.md | 59 +++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 1 deletion(-) diff --git a/doc/nodes/FinalGradeDeterminator.md b/doc/nodes/FinalGradeDeterminator.md index 0c2f3d3..ab501d0 100644 --- a/doc/nodes/FinalGradeDeterminator.md +++ b/doc/nodes/FinalGradeDeterminator.md @@ -11,7 +11,7 @@ FinalGradeDeterminator() ``` - Initializes ROS2 node with name `final_grade_determinator` - Declares and retrieves `grade_collection_amount` parameter -- Sets up `DatabaseManager` +- Sets up `DatabaseManager` - Creates publisher for student course management - Subscribes to exam results topic - Initializes service client for grade calculation @@ -28,8 +28,74 @@ FinalGradeDeterminator() - 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 + +--- + + +## Unit Tests + +Unit tests for `FinalGradeDeterminator` are implemented in `src/g2_2025_grade_calculator_pkg/test/FinalGradeDeterminator.test.cpp` using Google Test and ROS2 test utilities. The tests use a mock database manager and a mock grade calculator service to simulate the node's interactions. + + +### Test Cases + + + +#### 1. ConstructorTest + +**Description:** Verifies that the node can be constructed without errors. + +- **Input:** Construct a `FinalGradeDeterminator` node. +- **Expected Output:** No exceptions are thrown. The node is created successfully. + + + +#### 2. ExamCollectionTest + +**Description:** Sends the required number of exam results and checks that a grade calculation request is triggered, a student message is published, and the results are stored. + +- **Input:** + - Student: `"Test Student"` + - Course: `"Test Course"` + - Grades published: `[80, 85, 90, 75, 95]` (5 exam results, which is the default required amount) +- **Expected Output:** + - A grade calculation service request is made with the correct student, course, and grades. + - A student message is published with the correct student and course. + - The final grade is calculated as the average: (80 + 85 + 90 + 75 + 95) / 5 = 85. + + + +#### 3. PartialExamCollectionTest + +**Description:** Sends fewer than the required number of exam results and checks that no grade calculation or student message occurs. + +- **Input:** + - Student: `"Test Student"` + - Course: `"Test Course"` + - Grades published: `[80, 85, 90]` (only 3 exam results, less than required) +- **Expected Output:** + - No grade calculation service request is made. + - No student message is published. + + + +#### 4. MultipleStudentsTest + +**Description:** Simulates multiple students submitting exam results and verifies that each student triggers independent grade calculation and messaging. + +- **Input:** + - Students: `"Alice"`, `"Bob"` + - Course: `"Test Course"` + - Each student receives grades: `[80, 85, 90, 75, 95]` (5 exam results per student) +- **Expected Output:** + - Two grade calculation service requests are made, one for each student, with the correct grades. + - Two student messages are published, one for each student. + - The final grade for each student is calculated as the average: (80 + 85 + 90 + 75 + 95) / 5 = 85. + +These tests ensure that the node correctly collects exam results, triggers grade calculation at the right time, publishes the appropriate messages, and interacts with the database as expected. diff --git a/doc/nodes/GradeCalculator.md b/doc/nodes/GradeCalculator.md index 0e90fd5..2cb96ec 100644 --- a/doc/nodes/GradeCalculator.md +++ b/doc/nodes/GradeCalculator.md @@ -23,3 +23,62 @@ GradeCalculator() - 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 + +--- + +## Unit Tests + +Unit tests for `GradeCalculator` are implemented in `src/g2_2025_grade_calculator_pkg/test/GradeCalculator.test.cpp` using Google Test and ROS2 test utilities. The tests use a client to call the grade calculation service and verify the results. + +### Test Cases + +#### 1. NormalAverage + +**Description:** Verifies that the service returns the average of the provided grades for a normal student name. + +- **Input:** + - Student: `"Alice"` + - Grades: `[80, 90, 70]` +- **Expected Output:** + - Result: `80` (average of 80, 90, 70) + +#### 2. WesselBonus + +**Description:** Checks that the student name "Wessel" (case-insensitive) receives a 10-point bonus, and the result is clamped to 100 if necessary. + +- **Input:** + - Student: `"Wessel"` + - Grades: `[80, 90, 70]` +- **Expected Output:** + - Result: `90` (average 80 + 10 bonus) + +#### 3. wesselBonus + +**Description:** Checks that the bonus logic is case-insensitive for the student name "wessel". + +- **Input:** + - Student: `"wessel"` + - Grades: `[80, 90, 70]` +- **Expected Output:** + - Result: `90` (average 80 + 10 bonus) + +#### 4. GradeTooHigh + +**Description:** Ensures that the grade is clamped to a maximum of 100, even after applying the bonus. + +- **Input:** + - Student: `"Wessel"` + - Grades: `[100, 100, 100]` +- **Expected Output:** + - Result: `100` (average 100 + 10 bonus, clamped to 100) + +#### 5. GradeTooLow + +**Description:** Ensures that the grade is clamped to a minimum of 10. + +- **Input:** + - Student: `"Alice"` + - Grades: `[0, 0, 0]` +- **Expected Output:** + - Result: `10` (average 0, clamped to 10) + From 2e02ccddc5ba0b0687ccb3dd155bf8fd9572f33a Mon Sep 17 00:00:00 2001 From: Vincent W Date: Tue, 7 Oct 2025 15:16:42 +0200 Subject: [PATCH 02/14] fix(documentation): Add parameter to documentation --- doc/nodes/FinalGradeDeterminator.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/nodes/FinalGradeDeterminator.md b/doc/nodes/FinalGradeDeterminator.md index ab501d0..9a64817 100644 --- a/doc/nodes/FinalGradeDeterminator.md +++ b/doc/nodes/FinalGradeDeterminator.md @@ -5,6 +5,10 @@ The `FinalGradeDeterminator` node collects exam results for student-course combi #### Implementation Details +**Parameters** + +- **`grade_collection_amount`** (int, default: 5): Number of exam results required before triggering grade calculation for a student-course combination. + **Constructor** ```cpp FinalGradeDeterminator() From 6e1c0346b03bb4af336783bae39a99d3369c0eac Mon Sep 17 00:00:00 2001 From: Vincent W Date: Tue, 7 Oct 2025 15:17:06 +0200 Subject: [PATCH 03/14] feat(launchfile): Add launchfile to project --- src/g2_2025_grade_calculator_pkg/CMakeLists.txt | 5 +++++ .../launch/grade_calculator.launch.xml | 9 +++++++++ 2 files changed, 14 insertions(+) create mode 100644 src/g2_2025_grade_calculator_pkg/launch/grade_calculator.launch.xml diff --git a/src/g2_2025_grade_calculator_pkg/CMakeLists.txt b/src/g2_2025_grade_calculator_pkg/CMakeLists.txt index cb4a3e2..f0552e8 100644 --- a/src/g2_2025_grade_calculator_pkg/CMakeLists.txt +++ b/src/g2_2025_grade_calculator_pkg/CMakeLists.txt @@ -74,6 +74,11 @@ install( DESTINATION lib/${PROJECT_NAME} ) +install( + DIRECTORY launch + DESTINATION share/${PROJECT_NAME}/ +) + if(BUILD_TESTING) find_package(ament_cmake_gtest REQUIRED) diff --git a/src/g2_2025_grade_calculator_pkg/launch/grade_calculator.launch.xml b/src/g2_2025_grade_calculator_pkg/launch/grade_calculator.launch.xml new file mode 100644 index 0000000..d3c66b4 --- /dev/null +++ b/src/g2_2025_grade_calculator_pkg/launch/grade_calculator.launch.xml @@ -0,0 +1,9 @@ + + + + + + + + + From 437c5bc16e2cd157a017571b404d9a2671563291 Mon Sep 17 00:00:00 2001 From: Vincent W Date: Tue, 7 Oct 2025 15:47:15 +0200 Subject: [PATCH 04/14] feat(readme): Add aditional content to the main readme --- README.md | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/README.md b/README.md index d36a629..3adb8b6 100644 --- a/README.md +++ b/README.md @@ -10,3 +10,121 @@ Assignments made for the first semester of ROS2

## Table of contents + +- [System Architecture](#system-architecture) +- [Components](#components) +- [Installation](#installation) + +--- + +## System Architecture + +The TI Minor Grade Generator is a ROS2-based distributed system for managing student exam results and calculating final grades. The system follows a microservices architecture with clear separation of concerns. + +### Overview + +The system consists of multiple ROS2 nodes that communicate through topics and services to process exam data, calculate grades, and store the results in a database. + +### Components + +#### Core Nodes + +1. **ExamResultGenerator** (`assignments::one::exam_resutl_generator`) + - Publishes random exam results for students and courses + - Simulates exam result generation for testing and demonstration + - ([Full documentation](doc/nodes/ExamResultGenerator)) + + +2. **FinalGradeDeterminator** (`assignments::one::final_grade_determinator`) + - Collect grades from ExamResultGenerator + - Send results to the GradeCalculator + - Put resulting grade in the Database + - ([Full documentation](doc/nodes/FinalGradeDeterminator)) + +3. **GradeCalculator** (`assignments::one::grade_calculator`) + - Provides ROS2 service for calculating student exam grades + - Ensures grade results are within valid bounds (10-100) + - ([Full documentation](doc/nodes/GradeCalculator)) + + +#### Database Management + +- **DatabaseManager**: Handles all database operations including storing final course results +- ([Full documentation](doc/DatabaseManager.md)) + +#### Configuration Management + +- **ConfigManager**: Loads and validates system configuration from TOML files +- Provides runtime access to configuration parameters for all nodes +- Ensures consistent environment setup and error handling for misconfigurations +- ([Full documentation](doc/ConfigManager.md)) + +#### Message Interfaces + +- **Exam Messages** (`g2_2025_interfaces::msg::Exam`): Contains student name, course name, result, and timestamp +- **Student Messages** (`g2_2025_interfaces::msg::Student`): Contains student and course information with timestamp +- **Grade Calculation Service** (`g2_2025_interfaces::srv::Exams`): Service interface for grade calculation requests + +### Workflow + +1. **Exam Result Collection**: Exam results are published to the `exam_results` topic +2. **Result Aggregation**: FinalGradeDeterminator collects results until the configured threshold is met +3. **Grade Calculation**: When enough results are available, a service call is made to GradeCalculator +4. **Result Processing**: Calculated grades are published and stored in the database +5. **Student Management**: Final student information is published to `student_course_management` topic as this stops the random generation of grades +6. **Retake scheduler**: Schedule an exam retake for the grades below 55 by resending information to `student_course_management` as this will re-enable grade generation +7. **Retake grade determination**: repeat steps 1 through 6 using the `retake_grade_determinator` node instead of `final_grade_determinator` until every student hase a passing grade + +### Configuration + +The system uses TOML configuration files for environment-specific settings: +- Database connection parameters +- ROS2 node configurations +- Grade collection thresholds + +### Testing + +Unit tests are implemented using Google Test framework: +- Mock services and database managers for isolated testing +- Parameterized tests for various grade calculation scenarios +- Documentation on the tests are located in the full documentation of the nodes themselves + +## Installation + +### Prerequisites + +- ROS2 Jazzy or newer installed ([ROS2 Installation Guide](https://docs.ros.org/en/jazzy/Installation.html)) +- CMake (version 3.8+) +- Python 3.8+ +- libtomlplusplus-dev +- libpqxx-dev +- Colcon build tool +- Docker compose + +### Clone the Repository + +```bash +git clone https://git.wessel.gg/inholland/ros2-assignments.git +cd ros2-assignments +``` + +### Build the Workspace + +```bash +colcon build +``` +Any parameters can be changed before building by editing the `grade_calculator.launch.xml` in the launch folder + +### Source the Workspace + +```bash +source install/setup.bash +``` + +### Run the System + +```bash +sudo docker-compose up +ros2 launch g2_2025_grade_calculator_pkg grade_calculator.launch.xml +``` + From f1878270dcf5e149c7c3489efed4994720fa7932 Mon Sep 17 00:00:00 2001 From: Vincent W Date: Tue, 7 Oct 2025 15:47:15 +0200 Subject: [PATCH 05/14] feat(readme): Add aditional content to the main readme --- README.md | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/README.md b/README.md index d36a629..5972d79 100644 --- a/README.md +++ b/README.md @@ -10,3 +10,121 @@ Assignments made for the first semester of ROS2

## Table of contents + +- [System Architecture](#system-architecture) +- [Components](#components) +- [Installation](#installation) + +--- + +## System Architecture + +The TI Minor Grade Generator is a ROS2-based distributed system for managing student exam results and calculating final grades. The system follows a microservices architecture with clear separation of concerns. + +### Overview + +The system consists of multiple ROS2 nodes that communicate through topics and services to process exam data, calculate grades, and store the results in a database. + +### Components + +#### Core Nodes + +1. **ExamResultGenerator** (`assignments::one::exam_resutl_generator`) + - Publishes random exam results for students and courses + - Simulates exam result generation for testing and demonstration + - ([Full documentation](doc/nodes/ExamResultGenerator.md)) + + +2. **FinalGradeDeterminator** (`assignments::one::final_grade_determinator`) + - Collect grades from ExamResultGenerator + - Send results to the GradeCalculator + - Put resulting grade in the Database + - ([Full documentation](doc/nodes/FinalGradeDeterminator.md)) + +3. **GradeCalculator** (`assignments::one::grade_calculator`) + - Provides ROS2 service for calculating student exam grades + - Ensures grade results are within valid bounds (10-100) + - ([Full documentation](doc/nodes/GradeCalculator.md)) + + +#### Database Management + +- **DatabaseManager**: Handles all database operations including storing final course results +- ([Full documentation](doc/DatabaseManager.md)) + +#### Configuration Management + +- **ConfigManager**: Loads and validates system configuration from TOML files +- Provides runtime access to configuration parameters for all nodes +- Ensures consistent environment setup and error handling for misconfigurations +- ([Full documentation](doc/ConfigManager.md)) + +#### Message Interfaces + +- **Exam Messages** (`g2_2025_interfaces::msg::Exam`): Contains student name, course name, result, and timestamp +- **Student Messages** (`g2_2025_interfaces::msg::Student`): Contains student and course information with timestamp +- **Grade Calculation Service** (`g2_2025_interfaces::srv::Exams`): Service interface for grade calculation requests + +### Workflow + +1. **Exam Result Collection**: Exam results are published to the `exam_results` topic +2. **Result Aggregation**: FinalGradeDeterminator collects results until the configured threshold is met +3. **Grade Calculation**: When enough results are available, a service call is made to GradeCalculator +4. **Result Processing**: Calculated grades are published and stored in the database +5. **Student Management**: Final student information is published to `student_course_management` topic as this stops the random generation of grades +6. **Retake scheduler**: Schedule an exam retake for the grades below 55 by resending information to `student_course_management` as this will re-enable grade generation +7. **Retake grade determination**: repeat steps 1 through 6 using the `retake_grade_determinator` node instead of `final_grade_determinator` until every student hase a passing grade + +### Configuration + +The system uses TOML configuration files for environment-specific settings: +- Database connection parameters +- ROS2 node configurations +- Grade collection thresholds + +### Testing + +Unit tests are implemented using Google Test framework: +- Mock services and database managers for isolated testing +- Parameterized tests for various grade calculation scenarios +- Documentation on the tests are located in the full documentation of the nodes themselves + +## Installation + +### Prerequisites + +- ROS2 Jazzy or newer installed ([ROS2 Installation Guide](https://docs.ros.org/en/jazzy/Installation.html)) +- CMake (version 3.8+) +- Python 3.8+ +- libtomlplusplus-dev +- libpqxx-dev +- Colcon build tool +- Docker compose + +### Clone the Repository + +```bash +git clone https://git.wessel.gg/inholland/ros2-assignments.git +cd ros2-assignments +``` + +### Build the Workspace + +```bash +colcon build +``` +Any parameters can be changed before building by editing the `grade_calculator.launch.xml` in the launch folder + +### Source the Workspace + +```bash +source install/setup.bash +``` + +### Run the System + +```bash +sudo docker-compose up +ros2 launch g2_2025_grade_calculator_pkg grade_calculator.launch.xml +``` + From 62995c13c236424257f18bc0f3312873eb6e26a7 Mon Sep 17 00:00:00 2001 From: Wessel Tip Date: Tue, 7 Oct 2025 18:59:13 +0200 Subject: [PATCH 06/14] fix(exam_result_generator): Fix timestamp being 0 --- .../src/exam_result_generator/nodes/ExamResultGenerator.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/g2_2025_grade_calculator_pkg/src/exam_result_generator/nodes/ExamResultGenerator.cpp b/src/g2_2025_grade_calculator_pkg/src/exam_result_generator/nodes/ExamResultGenerator.cpp index a4079cd..daac8ca 100644 --- a/src/g2_2025_grade_calculator_pkg/src/exam_result_generator/nodes/ExamResultGenerator.cpp +++ b/src/g2_2025_grade_calculator_pkg/src/exam_result_generator/nodes/ExamResultGenerator.cpp @@ -72,6 +72,7 @@ void ExamResultGenerator::generate_random_result() { exam_msg.student_name = selected.student_name; exam_msg.course_name = selected.course_name; exam_msg.result = grade; + exam_msg.timestamp = this->get_clock()->now(); exam_publisher_->publish(exam_msg); From 130b4950301e58010177615aa2ef994f05625606 Mon Sep 17 00:00:00 2001 From: Wessel Tip Date: Tue, 7 Oct 2025 19:49:31 +0200 Subject: [PATCH 07/14] docs: Add unit tests - DatabaseManager: Added unit test documentation - ConfigManager: Added unit test documentation - ExamResultGenerator: Added unit test documentation - FinalGradeDeterminator: Fix spacing --- doc/ConfigManager.md | 83 ++++++++++++++++++++++++++++- doc/DatabaseManager.md | 66 +++++++++++++++++++++++ doc/nodes/ExamResultGenerator.md | 65 ++++++++++++++++++++++ doc/nodes/FinalGradeDeterminator.md | 9 ---- 4 files changed, 213 insertions(+), 10 deletions(-) diff --git a/doc/ConfigManager.md b/doc/ConfigManager.md index b47924a..3fcdc95 100644 --- a/doc/ConfigManager.md +++ b/doc/ConfigManager.md @@ -130,5 +130,86 @@ ssl = false [database.pool] min_connections = 1 max_connections = 10 - ``` + +--- + +## Unit Tests + +Unit tests for `ConfigManager` are implemented in `src/g2_2025_grade_calculator_pkg/test/ConfigManager.test.cpp` using Google Test and ROS2 test utilities. The tests use temporary TOML files to validate configuration loading and parsing functionality. + +### Test Cases + +#### 1. ConstructorTest + +**Description:** Verifies that ConfigManager can be created with a ROS2 logger without crashing. + +- **Test Action:** Create ConfigManager instance with ROS2 logger +- **Expected Result:** Instance created successfully without exceptions + +#### 2. LoadValidConfigTest + +**Description:** Tests loading of valid TOML configuration files with proper parsing. + +- **Test Action:** + - Create temporary TOML file with valid configuration + - Call `load_config()` with file path +- **Expected Result:** + - Returns `true` + - `is_loaded()` returns `true` + +#### 3. LoadInvalidFileTest + +**Description:** Tests error handling when attempting to load non-existent configuration files. + +- **Test Action:** Call `load_config()` with non-existent file path +- **Expected Result:** + - Returns `false` + - `is_loaded()` returns `false` + +#### 4. DatabaseConfigParsingTest + +**Description:** Tests complete database configuration parsing with all parameters. + +- **Test Configuration:** + ```toml + [database] + host = "test_host" + port = 1234 + dbname = "test_db" + user = "test_user" + password = "test_password" + timeout = 60 + ssl = true + + [database.pool] + min_connections = 2 + max_connections = 20 + ``` +- **Expected Result:** All configuration values parsed correctly with proper types + +#### 5. DatabaseConfigWithoutPoolTest + +**Description:** Tests default values when optional pool section is missing from configuration. + +- **Test Configuration:** + ```toml + [database] + host = "localhost" + port = 5432 + dbname = "grades" + user = "postgres" + password = "postgres" + ``` +- **Expected Result:** + - Main database config parsed correctly + - Default pool values: `min_connections = 1`, `max_connections = 10` + +#### 6. GetConfigWithoutLoadingTest + +**Description:** Tests behavior when attempting to access configuration before loading any file. + +- **Test Action:** Call `get_database_config()` without loading configuration +- **Expected Result:** + - Returns `std::nullopt` + - `is_loaded()` returns `false` diff --git a/doc/DatabaseManager.md b/doc/DatabaseManager.md index a4bc1d1..cd14e56 100644 --- a/doc/DatabaseManager.md +++ b/doc/DatabaseManager.md @@ -123,3 +123,69 @@ if (db_manager.is_connected()) { } } ``` + +--- + +## Unit Tests + +Unit tests for `DatabaseManager` are implemented in `src/g2_2025_grade_calculator_pkg/test/DatabaseManager.test.cpp` using Google Test and ROS2 test utilities. The tests are designed to work reliably whether a database connection is available or not, focusing on error handling and method behavior validation. + +### Test Cases + +#### 1. ConstructorTest + +**Description:** Verifies that DatabaseManager can be created without crashing and proper initialization occurs. + +- **Test Action:** Create DatabaseManager instance with ROS2 logger +- **Expected Result:** Instance created successfully without exceptions + +#### 2. ConnectionStatusTest + +**Description:** Tests that the `is_connected()` method returns a valid boolean value. + +- **Test Action:** Call `is_connected()` method +- **Expected Result:** Returns either `true` or `false` (no crashes or invalid states) + +#### 3. QueuePendingCombinationsTest + +**Description:** Verifies retrieval of pending student-course combinations that need exam results. + +- **Test Action:** Call `queue_pending_combinations()` +- **Expected Result:** Returns valid vector (empty if no database connection, populated if connected) + +#### 4. StoreExamResultTest + +**Description:** Tests exam result storage with graceful handling of connection states. + +- **Test Action:** + - Student: `"TestStudent"` + - Course: `"TestCourse"` + - Grade: `85` +- **Expected Result:** Returns `false` if no connection, `true` if connected and successful + +#### 5. EnrollStudentTest + +**Description:** Tests student enrollment into courses. + +- **Test Action:** + - StudentCourse object with test data +- **Expected Result:** Returns `false` if no connection, `true` if connected and successful + +#### 6. GetFinalGradeTest + +**Description:** Tests final grade retrieval for non-existent student-course combinations. + +- **Test Action:** + - Student: `"NonExistentStudent"` + - Course: `"NonExistentCourse"` +- **Expected Result:** Returns `-1` (no results found or no connection) + +#### 7. StoreFinalResultTest + +**Description:** Tests storing calculated final course results. + +- **Test Action:** + - StudentCourse object + - Exam count: `3` + - Final grade: `75` +- **Expected Result:** Returns `false` if no connection, `true` if connected and successful diff --git a/doc/nodes/ExamResultGenerator.md b/doc/nodes/ExamResultGenerator.md index 6b75456..a4e6def 100644 --- a/doc/nodes/ExamResultGenerator.md +++ b/doc/nodes/ExamResultGenerator.md @@ -36,3 +36,68 @@ ExamResultGenerator() **`void add_student_course_combination(const StudentCourse& sc)`** - Enrolls student into course via database - Adds combination to processing queue + +--- + +## Unit Tests + +Unit tests for `ExamResultGenerator` are implemented in `src/g2_2025_grade_calculator_pkg/test/ExamResultGenerator.test.cpp` using Google Test and ROS2 test utilities. The tests use a test subscriber node to capture published messages and validate node behavior. + +### Test Cases + +#### 1. ConstructorTest + +**Description:** Verifies that ExamResultGenerator can be created without crashing and proper initialization occurs. + +- **Test Action:** Create ExamResultGenerator node instance +- **Expected Result:** + - Node created successfully without exceptions + - Node name set to `"exam_result_generator"` + +#### 2. PublisherCreationTest + +**Description:** Verifies that the `exam_results` topic publisher is properly configured. + +- **Test Action:** Check topic names and types after node creation +- **Expected Result:** + - `/exam_results` topic is published + - Topic uses `g2_2025_interfaces/msg/Exam` message type + +#### 3. SubscriberCreationTest + +**Description:** Tests that the node subscribes to the `student_course_management` topic with correct message type. + +- **Test Action:** Check subscription topics and types +- **Expected Result:** + - `/student_course_management` topic is subscribed + - Subscription uses `g2_2025_interfaces/msg/Student` message type + +#### 4. StudentManagementMessageHandlingTest + +**Description:** Tests the node's ability to handle incoming student management messages without crashing. + +- **Test Action:** + - Publish student management message: + - Student: `"Test Student"` + - Course: `"Test Course"` +- **Expected Result:** Message processed without crashes + +#### 5. MultipleStudentMessagesTest + +**Description:** Validates handling of multiple rapid student management messages for robustness testing. + +- **Test Action:** + - Send multiple messages with different student-course combinations + - Students: `["Alice", "Bob", "Charlie"]` + - Courses: `["Math", "Physics", "Chemistry"]` +- **Expected Result:** All messages processed without crashes or memory issues + +#### 6. ExamMessageValidationTest + +**Description:** Captures and validates published exam result messages for correct content and format. + +- **Test Action:** Listen for published exam result messages over 6 seconds +- **Expected Result:** + - Published grades are within range `[10, 100]` + - Course names are not empty + - Message format is correct diff --git a/doc/nodes/FinalGradeDeterminator.md b/doc/nodes/FinalGradeDeterminator.md index 9a64817..a679dde 100644 --- a/doc/nodes/FinalGradeDeterminator.md +++ b/doc/nodes/FinalGradeDeterminator.md @@ -46,11 +46,8 @@ FinalGradeDeterminator() Unit tests for `FinalGradeDeterminator` are implemented in `src/g2_2025_grade_calculator_pkg/test/FinalGradeDeterminator.test.cpp` using Google Test and ROS2 test utilities. The tests use a mock database manager and a mock grade calculator service to simulate the node's interactions. - ### Test Cases - - #### 1. ConstructorTest **Description:** Verifies that the node can be constructed without errors. @@ -58,8 +55,6 @@ Unit tests for `FinalGradeDeterminator` are implemented in `src/g2_2025_grade_ca - **Input:** Construct a `FinalGradeDeterminator` node. - **Expected Output:** No exceptions are thrown. The node is created successfully. - - #### 2. ExamCollectionTest **Description:** Sends the required number of exam results and checks that a grade calculation request is triggered, a student message is published, and the results are stored. @@ -73,8 +68,6 @@ Unit tests for `FinalGradeDeterminator` are implemented in `src/g2_2025_grade_ca - A student message is published with the correct student and course. - The final grade is calculated as the average: (80 + 85 + 90 + 75 + 95) / 5 = 85. - - #### 3. PartialExamCollectionTest **Description:** Sends fewer than the required number of exam results and checks that no grade calculation or student message occurs. @@ -87,8 +80,6 @@ Unit tests for `FinalGradeDeterminator` are implemented in `src/g2_2025_grade_ca - No grade calculation service request is made. - No student message is published. - - #### 4. MultipleStudentsTest **Description:** Simulates multiple students submitting exam results and verifies that each student triggers independent grade calculation and messaging. From 5e1df5367cfa7b469bf8b32f2fb6b308ecdd126a Mon Sep 17 00:00:00 2001 From: Vincent W Date: Wed, 8 Oct 2025 16:30:05 +0200 Subject: [PATCH 08/14] Major feat(Documentation): Complete documentation rework Split documentation into three folders: Architecture, Testing and Installation. Rework and expand architecture.md alongside interfaces.md Split all parts to do with testing from Architecture md's into Test md's Add parameter to ExamResultGenerator node documentation --- README.md | 3 - doc/Tests/ConfigManager.md | 79 ++++++++ doc/Tests/DatabaseManager.md | 65 +++++++ doc/{nodes => Tests}/ExamResultGenerator.md | 40 ----- .../FinalGradeDeterminator.md | 44 ----- doc/{nodes => Tests}/GradeCalculator.md | 28 --- .../Managers}/ConfigManager.md | 0 .../Managers}/DatabaseManager.md | 0 doc/architecture/architecture.md | 147 +++++++++++++++ doc/architecture/interfaces/interfaces.md | 170 ++++++++++++++++++ doc/architecture/nodes/ExamResultGenerator.md | 43 +++++ .../nodes/FinalGradeDeterminator.md | 42 +++++ doc/architecture/nodes/GradeCalculator.md | 25 +++ doc/installation/installation.md | 38 ++++ doc/interfaces/interfaces.md | 16 -- 15 files changed, 609 insertions(+), 131 deletions(-) create mode 100644 doc/Tests/ConfigManager.md create mode 100644 doc/Tests/DatabaseManager.md rename doc/{nodes => Tests}/ExamResultGenerator.md (62%) rename doc/{nodes => Tests}/FinalGradeDeterminator.md (58%) rename doc/{nodes => Tests}/GradeCalculator.md (59%) rename doc/{ => architecture/Managers}/ConfigManager.md (100%) rename doc/{ => architecture/Managers}/DatabaseManager.md (100%) create mode 100644 doc/architecture/architecture.md create mode 100644 doc/architecture/interfaces/interfaces.md create mode 100644 doc/architecture/nodes/ExamResultGenerator.md create mode 100644 doc/architecture/nodes/FinalGradeDeterminator.md create mode 100644 doc/architecture/nodes/GradeCalculator.md create mode 100644 doc/installation/installation.md delete mode 100644 doc/interfaces/interfaces.md diff --git a/README.md b/README.md index 5972d79..f74c8b4 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,6 @@ The system consists of multiple ROS2 nodes that communicate through topics and s - Simulates exam result generation for testing and demonstration - ([Full documentation](doc/nodes/ExamResultGenerator.md)) - 2. **FinalGradeDeterminator** (`assignments::one::final_grade_determinator`) - Collect grades from ExamResultGenerator - Send results to the GradeCalculator @@ -46,7 +45,6 @@ The system consists of multiple ROS2 nodes that communicate through topics and s - Ensures grade results are within valid bounds (10-100) - ([Full documentation](doc/nodes/GradeCalculator.md)) - #### Database Management - **DatabaseManager**: Handles all database operations including storing final course results @@ -127,4 +125,3 @@ source install/setup.bash sudo docker-compose up ros2 launch g2_2025_grade_calculator_pkg grade_calculator.launch.xml ``` - diff --git a/doc/Tests/ConfigManager.md b/doc/Tests/ConfigManager.md new file mode 100644 index 0000000..192691d --- /dev/null +++ b/doc/Tests/ConfigManager.md @@ -0,0 +1,79 @@ +## Unit Tests + +Unit tests for `ConfigManager` are implemented in `src/g2_2025_grade_calculator_pkg/test/ConfigManager.test.cpp` using Google Test and ROS2 test utilities. The tests use temporary TOML files to validate configuration loading and parsing functionality. + +### Test Cases + +#### 1. ConstructorTest + +**Description:** Verifies that ConfigManager can be created with a ROS2 logger without crashing. + +- **Test Action:** Create ConfigManager instance with ROS2 logger +- **Expected Result:** Instance created successfully without exceptions + +#### 2. LoadValidConfigTest + +**Description:** Tests loading of valid TOML configuration files with proper parsing. + +- **Test Action:** + - Create temporary TOML file with valid configuration + - Call `load_config()` with file path +- **Expected Result:** + - Returns `true` + - `is_loaded()` returns `true` + +#### 3. LoadInvalidFileTest + +**Description:** Tests error handling when attempting to load non-existent configuration files. + +- **Test Action:** Call `load_config()` with non-existent file path +- **Expected Result:** + - Returns `false` + - `is_loaded()` returns `false` + +#### 4. DatabaseConfigParsingTest + +**Description:** Tests complete database configuration parsing with all parameters. + +- **Test Configuration:** + ```toml + [database] + host = "test_host" + port = 1234 + dbname = "test_db" + user = "test_user" + password = "test_password" + timeout = 60 + ssl = true + + [database.pool] + min_connections = 2 + max_connections = 20 + ``` +- **Expected Result:** All configuration values parsed correctly with proper types + +#### 5. DatabaseConfigWithoutPoolTest + +**Description:** Tests default values when optional pool section is missing from configuration. + +- **Test Configuration:** + ```toml + [database] + host = "localhost" + port = 5432 + dbname = "grades" + user = "postgres" + password = "postgres" + ``` +- **Expected Result:** + - Main database config parsed correctly + - Default pool values: `min_connections = 1`, `max_connections = 10` + +#### 6. GetConfigWithoutLoadingTest + +**Description:** Tests behavior when attempting to access configuration before loading any file. + +- **Test Action:** Call `get_database_config()` without loading configuration +- **Expected Result:** + - Returns `std::nullopt` + - `is_loaded()` returns `false` diff --git a/doc/Tests/DatabaseManager.md b/doc/Tests/DatabaseManager.md new file mode 100644 index 0000000..e09a8be --- /dev/null +++ b/doc/Tests/DatabaseManager.md @@ -0,0 +1,65 @@ + + +## Unit Tests + +Unit tests for `DatabaseManager` are implemented in `src/g2_2025_grade_calculator_pkg/test/DatabaseManager.test.cpp` using Google Test and ROS2 test utilities. The tests are designed to work reliably whether a database connection is available or not, focusing on error handling and method behavior validation. + +### Test Cases + +#### 1. ConstructorTest + +**Description:** Verifies that DatabaseManager can be created without crashing and proper initialization occurs. + +- **Test Action:** Create DatabaseManager instance with ROS2 logger +- **Expected Result:** Instance created successfully without exceptions + +#### 2. ConnectionStatusTest + +**Description:** Tests that the `is_connected()` method returns a valid boolean value. + +- **Test Action:** Call `is_connected()` method +- **Expected Result:** Returns either `true` or `false` (no crashes or invalid states) + +#### 3. QueuePendingCombinationsTest + +**Description:** Verifies retrieval of pending student-course combinations that need exam results. + +- **Test Action:** Call `queue_pending_combinations()` +- **Expected Result:** Returns valid vector (empty if no database connection, populated if connected) + +#### 4. StoreExamResultTest + +**Description:** Tests exam result storage with graceful handling of connection states. + +- **Test Action:** + - Student: `"TestStudent"` + - Course: `"TestCourse"` + - Grade: `85` +- **Expected Result:** Returns `false` if no connection, `true` if connected and successful + +#### 5. EnrollStudentTest + +**Description:** Tests student enrollment into courses. + +- **Test Action:** + - StudentCourse object with test data +- **Expected Result:** Returns `false` if no connection, `true` if connected and successful + +#### 6. GetFinalGradeTest + +**Description:** Tests final grade retrieval for non-existent student-course combinations. + +- **Test Action:** + - Student: `"NonExistentStudent"` + - Course: `"NonExistentCourse"` +- **Expected Result:** Returns `-1` (no results found or no connection) + +#### 7. StoreFinalResultTest + +**Description:** Tests storing calculated final course results. + +- **Test Action:** + - StudentCourse object + - Exam count: `3` + - Final grade: `75` +- **Expected Result:** Returns `false` if no connection, `true` if connected and successful diff --git a/doc/nodes/ExamResultGenerator.md b/doc/Tests/ExamResultGenerator.md similarity index 62% rename from doc/nodes/ExamResultGenerator.md rename to doc/Tests/ExamResultGenerator.md index a4e6def..b1b7882 100644 --- a/doc/nodes/ExamResultGenerator.md +++ b/doc/Tests/ExamResultGenerator.md @@ -1,43 +1,3 @@ -# ExamResultGenerator (`assignments::one::exam_result_generator`) - -## Overview -The `ExamResultGenerator` is the core node responsible for simulating exam result generation. -It maintains a queue of student-course combinations that need exam results, generates random -grades, and publishes them to the system. - -#### Implementation Details - -**Constructor** -```cpp -ExamResultGenerator() -``` -- Initializes ROS2 node with name `exam_result_generator` -- Sets up random number generation infrastructure -- Creates DatabaseManager instance with node logger -- Establishes ROS2 publishers, subscribers, and timers -- Loads initial pending combinations from database - -**Core Functions** - -**`void queue_pending_combinations()`** -- Gets all student-course combinations needing exam results -- Populates operations queue from database -- Called at initialization and when database state changes - -**`void generate_random_result()`** -- Main exam result generation executed by timer -- Selects random student-course combination from queue -- Stores result in database and publishes to ROS2 topic - -**`void student_management_callback(const g2_2025_interfaces::msg::Student::SharedPtr msg)`** -- Toggles combinations in/out of processing queue -- Updates database with new enrollments - -**`void add_student_course_combination(const StudentCourse& sc)`** -- Enrolls student into course via database -- Adds combination to processing queue - ---- ## Unit Tests diff --git a/doc/nodes/FinalGradeDeterminator.md b/doc/Tests/FinalGradeDeterminator.md similarity index 58% rename from doc/nodes/FinalGradeDeterminator.md rename to doc/Tests/FinalGradeDeterminator.md index a679dde..4b6cce0 100644 --- a/doc/nodes/FinalGradeDeterminator.md +++ b/doc/Tests/FinalGradeDeterminator.md @@ -1,47 +1,3 @@ -# 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 - -**Parameters** - -- **`grade_collection_amount`** (int, default: 5): Number of exam results required before triggering grade calculation for a student-course combination. - -**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 - ---- - - ## Unit Tests Unit tests for `FinalGradeDeterminator` are implemented in `src/g2_2025_grade_calculator_pkg/test/FinalGradeDeterminator.test.cpp` using Google Test and ROS2 test utilities. The tests use a mock database manager and a mock grade calculator service to simulate the node's interactions. diff --git a/doc/nodes/GradeCalculator.md b/doc/Tests/GradeCalculator.md similarity index 59% rename from doc/nodes/GradeCalculator.md rename to doc/Tests/GradeCalculator.md index 2cb96ec..767a95f 100644 --- a/doc/nodes/GradeCalculator.md +++ b/doc/Tests/GradeCalculator.md @@ -1,31 +1,3 @@ -# 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 - ---- - ## Unit Tests Unit tests for `GradeCalculator` are implemented in `src/g2_2025_grade_calculator_pkg/test/GradeCalculator.test.cpp` using Google Test and ROS2 test utilities. The tests use a client to call the grade calculation service and verify the results. diff --git a/doc/ConfigManager.md b/doc/architecture/Managers/ConfigManager.md similarity index 100% rename from doc/ConfigManager.md rename to doc/architecture/Managers/ConfigManager.md diff --git a/doc/DatabaseManager.md b/doc/architecture/Managers/DatabaseManager.md similarity index 100% rename from doc/DatabaseManager.md rename to doc/architecture/Managers/DatabaseManager.md diff --git a/doc/architecture/architecture.md b/doc/architecture/architecture.md new file mode 100644 index 0000000..06bae16 --- /dev/null +++ b/doc/architecture/architecture.md @@ -0,0 +1,147 @@ +# TI Minor Grade Generator Design Document + +## Project Overview + +The TI Minor Grade Generator is a distributed ROS2-based system designed to manage student exam results and calculate final course grades. The system follows microservices architecture principles with clear separation of concerns and robust data management. + +## System Architecture + +### High-Level Architecture + +The system consists of multiple ROS2 nodes that communicate through standardized topics and services to process exam data, calculate grades, and persist results. The architecture ensures scalability, maintainability, and fault tolerance. + +### Key Design Principles + +- **Microservices Architecture**: Each component has a single responsibility +- **Asynchronous Communication**: Uses ROS2 topics and services for loose coupling +- **Data Persistence**: Centralized database management for reliable storage +- **Configurable Parameters**: TOML-based configuration for environment flexibility +- **Comprehensive Testing**: Unit and integration tests ensure reliability + +## System Components + +### Core Nodes + +#### 1. FinalGradeDeterminator Node +**Namespace**: `assignments::one::final_grade_determinator` + +**Brief Description**: Collects exam results, triggers grade calculation when thresholds are met, and stores final grades. + +**Key Features**: Configurable collection thresholds, automatic grade calculation triggering, database persistence + +*For detailed documentation, see: [FinalGradeDeterminator.md](nodes/FinalGradeDeterminator.md)* + +#### 2. GradeCalculator Node +**Namespace**: `assignments::one::grade_calculator` + +**Brief Description**: Provides grade calculation service with business logic including bonus points and grade validation. + +**Key Features**: Average calculation, special student rules, grade bounds validation (10-100) + +*For detailed documentation, see: [GradeCalculator.md](nodes/GradeCalculator.md)* + +#### 3. ExamResultGenerator Node +**Namespace**: `assignments::one::exam_result_generator` + +**Brief Description**: Simulates exam result generation by maintaining a queue of student-course combinations and publishing random grades. + +**Key Features**: Database-driven queue management, random grade generation, student enrollment handling + +*For detailed documentation, see: [ExamResultGenerator.md](nodes/ExamResultGenerator.md)* + +#### 4. RetakeScheduler Node +**Namespace**: `assignments::one::retake_scheduler` + +**Brief Description**: Manages retake exam scheduling and coordination for students who need to retake exams. + +**Key Features**: Retake request processing, schedule management, action server implementation for retake workflows + +*For detailed documentation, see: [RetakeScheduler.md](nodes/RetakeScheduler.md)* + +#### 5. RetakeGradeDeterminator Node +**Namespace**: `assignments::one::retake_grade_determinator` + +**Brief Description**: Handles grade calculation and processing specifically for retake exams. + +**Key Features**: Retake-specific grade processing, integration with main grade system, retake result validation + +*For detailed documentation, see: [RetakeGradeDeterminator.md](nodes/RetakeGradeDeterminator.md)* + +### Data Management + +#### DatabaseManager +**Brief Description**: PostgreSQL database interface handling connections, table management, and data persistence. + +**Key Features**: Connection management, automatic table creation, student enrollment tracking, exam result storage + +*For detailed documentation, see: [DatabaseManager.md](../DatabaseManager.md)* + +#### ConfigManager +**Brief Description**: TOML-based configuration management system allowing runtime configuration without recompilation. + +**Key Features**: Automatic config file discovery, type-safe TOML parsing, database connection configuration + +*For detailed documentation, see: [ConfigManager.md](../ConfigManager.md)* + +### Communication Interfaces + +#### ROS2 Message and Service Interfaces +**Brief Description**: Custom message types and service definitions for inter-node communication. + +**Key Components**: Exam and Student message types, Grade calculation service, Retake action interface, standardized timestamps + +*For detailed documentation, see: [interfaces.md](interfaces/interfaces.md)* + +## System Workflow + +### 1. Exam Result Processing + +1. **Input**: Exam results are published to the `exam_results` topic +2. **Collection**: FinalGradeDeterminator receives and stores exam results by student-course combination +3. **Monitoring**: System tracks the number of results per student-course pair +4. **Threshold Check**: When `grade_collection_amount` results are collected, proceed to calculation + +### 2. Grade Calculation Process + +1. **Service Request**: FinalGradeDeterminator calls GradeCalculator service +2. **Calculation**: GradeCalculator computes final grade using business logic +3. **Validation**: Result is validated and clamped to acceptable range +4. **Response**: Calculated grade is returned to FinalGradeDeterminator + +### 3. Result Management + +1. **Database Storage**: Final grade is persisted in the database +2. **Publication**: Student information is published to management topic +3. **Logging**: Process completion is logged for audit purposes + +### 4. Retake Processing Workflow + +1. **Retake Request**: RetakeScheduler receives retake requests via action interface +2. **Schedule Management**: System schedules retake exams and manages timelines +3. **Retake Execution**: Student completes retake exam, results are processed +4. **Grade Processing**: RetakeGradeDeterminator calculates retake grades with specialized logic +5. **Integration**: Retake results are integrated with main grade system and database + +## Configuration Management + +### TOML Configuration Structure + +The system uses TOML files for environment-specific configuration: + +```toml +[database] +host = "localhost" +port = 5432 +name = "grade_db" +user = "grade_user" +password = "secure_password" + +[grade_calculation] +collection_amount = 5 +min_grade = 10 +max_grade = 100 + +[logging] +level = "INFO" +output_file = "grade_system.log" +``` diff --git a/doc/architecture/interfaces/interfaces.md b/doc/architecture/interfaces/interfaces.md new file mode 100644 index 0000000..72f58d8 --- /dev/null +++ b/doc/architecture/interfaces/interfaces.md @@ -0,0 +1,170 @@ +# ROS2 Interface Definitions - g2_2025_interfaces + +## Package Overview + +This document describes the custom ROS2 interface definitions in the `g2_2025_interfaces` package. These interfaces provide standardized communication protocols for the TI Minor Grade Generator system. + +**Package Name**: `g2_2025_interfaces` +**Interface Types**: Messages, Services, Actions +**Location**: `src/g2_2025_interfaces/` + +## Message Types (.msg) + +### Exam.msg + +Represents examination result data exchanged between system components. + +``` +string student_name +string course_name +int32 result +builtin_interfaces/Time timestamp +``` + +**Field Descriptions**: +- `student_name`: Name identifier for the student +- `course_name`: Course identifier for the examination +- `result`: Numerical exam result/grade (integer value) +- `timestamp`: Timestamp of the message + +**Usage**: Primary message type for exam result communication between ExamResultGenerator and grade processing nodes. + +### Student.msg + +Represents student information and course enrollment data. + +``` +string student_name +string course_name +builtin_interfaces/Time timestamp +``` + +**Field Descriptions**: +- `student_name`: Name identifier for the student +- `course_name`: Course identifier for enrollment/management +- `timestamp`: Timestamp of the message + +**Usage**: Used for student-course management operations and enrollment tracking. + +## Service Definitions (.srv) + +### Exams.srv + +Service interface for grade calculation operations using multiple exam results. + +#### Request +``` +string student_name +string course_name +int32[] exam_grades +``` + +#### Response +``` +int32 result # Final calculated result +``` + +**Request Fields**: +- `student_name`: Student identifier for grade calculation +- `course_name`: Course identifier for context +- `exam_grades`: Array of exam grades to be processed + +**Response Fields**: +- `result`: Final calculated grade result (integer value) + +**Usage**: Main service interface used by GradeCalculator node for processing multiple exam grades into final results. + +## Action Definitions (.action) + +### Retake.action + +Action interface for managing retake exam scheduling and processing workflows. + +#### Goal +``` +string student_name +string course_name +``` + +#### Result +``` +# Empty result section - completion indicates success +``` + +#### Feedback +``` +float32 result +``` + +**Goal Fields**: +- `student_name`: Student for which a retake is requested +- `course_name`: Course for which a retake is requested + +**Feedback Fields**: +- `result`: Progress indicator or intermediate result (float value) + +**Usage**: Used by RetakeScheduler node to handle long-running retake management operations with progress feedback. + +## Node Interface Usage + +### ExamResultGenerator Node + +**Publishers**: +- **Topic**: `exam_results` +- **Message Type**: `g2_2025_interfaces::msg::Exam` +- **Purpose**: Publishes generated exam results to downstream processing nodes +- **Rate**: Configurable interval (default: 2 seconds) + +**Subscribers**: +- **Topic**: `student_course_management` +- **Message Type**: `g2_2025_interfaces::msg::Student` +- **Purpose**: Receives student-course enrollment updates for exam generation queue + +### FinalGradeDeterminator Node + +**Subscribers**: +- **Topic**: `exam_results` +- **Message Type**: `g2_2025_interfaces::msg::Exam` +- **Purpose**: Collects exam results for grade calculation processing + +**Service Clients**: +- **Service**: `calculate_grade` +- **Service Type**: `g2_2025_interfaces::srv::Exams` +- **Purpose**: Requests grade calculation from GradeCalculator when threshold is met + +**Publishers**: +- **Topic**: `student_course_management` +- **Message Type**: `g2_2025_interfaces::msg::Student` +- **Purpose**: Communicate end of enrollment or re-enrolment + +### GradeCalculator Node + +**Service Servers**: +- **Service**: `calculate_grade` +- **Service Type**: `g2_2025_interfaces::srv::Exams` +- **Purpose**: Provides grade calculation services for multiple exam grades +- **Processing**: Applies business logic (averaging, bonus points, validation) + +### RetakeScheduler Node + +**Action Servers**: +- **Action**: `retake_request` +- **Action Type**: `g2_2025_interfaces::action::Retake` +- **Purpose**: Handles long-running retake scheduling operations +- **Feedback**: Provides progress updates during scheduling process + +### RetakeGradeDeterminator Node + +**Service Clients**: +- **Service**: `calculate_grade` +- **Service Type**: `g2_2025_interfaces::srv::Exams` +- **Purpose**: Requests specialized retake grade calculations + +**Subscribers**: +- **Topic**: `retake_results` +- **Message Type**: `g2_2025_interfaces::msg::Exam` +- **Purpose**: Processes completed retake exam results + +## Related Documentation + +- [System Architecture](../Orig.md): Overall system design and communication patterns diff --git a/doc/architecture/nodes/ExamResultGenerator.md b/doc/architecture/nodes/ExamResultGenerator.md new file mode 100644 index 0000000..2f7fadb --- /dev/null +++ b/doc/architecture/nodes/ExamResultGenerator.md @@ -0,0 +1,43 @@ +# ExamResultGenerator (`assignments::one::exam_result_generator`) + +## Overview +The `ExamResultGenerator` is the core node responsible for simulating exam result generation. +It maintains a queue of student-course combinations that need exam results, generates random +grades, and publishes them to the system. + +#### Implementation Details + +**Parameters** + +- **`delay_between_grades_ms`** (int, default: 2000): Delay (in milliseconds) between generated grades. + +**Constructor** +```cpp +ExamResultGenerator() +``` +- Initializes ROS2 node with name `exam_result_generator` +- Sets up random number generation infrastructure +- Creates DatabaseManager instance with node logger +- Establishes ROS2 publishers, subscribers, and timers +- Loads initial pending combinations from database + +**Core Functions** + +**`void queue_pending_combinations()`** +- Gets all student-course combinations needing exam results +- Populates operations queue from database +- Called at initialization and when database state changes + +**`void generate_random_result()`** +- Main exam result generation executed by timer +- Selects random student-course combination from queue +- Stores result in database and publishes to ROS2 topic + +**`void student_management_callback(const g2_2025_interfaces::msg::Student::SharedPtr msg)`** +- Toggles combinations in/out of processing queue +- Updates database with new enrollments + +**`void add_student_course_combination(const StudentCourse& sc)`** +- Enrolls student into course via database +- Adds combination to processing queue + diff --git a/doc/architecture/nodes/FinalGradeDeterminator.md b/doc/architecture/nodes/FinalGradeDeterminator.md new file mode 100644 index 0000000..57ee589 --- /dev/null +++ b/doc/architecture/nodes/FinalGradeDeterminator.md @@ -0,0 +1,42 @@ +# 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 + +**Parameters** + +- **`grade_collection_amount`** (int, default: 5): Number of exam results required before triggering grade calculation for a student-course combination. + +**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/architecture/nodes/GradeCalculator.md b/doc/architecture/nodes/GradeCalculator.md new file mode 100644 index 0000000..0e90fd5 --- /dev/null +++ b/doc/architecture/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 diff --git a/doc/installation/installation.md b/doc/installation/installation.md new file mode 100644 index 0000000..370c8c0 --- /dev/null +++ b/doc/installation/installation.md @@ -0,0 +1,38 @@ +## Installation + +### Prerequisites + +- ROS2 Jazzy or newer installed ([ROS2 Installation Guide](https://docs.ros.org/en/jazzy/Installation.html)) +- CMake (version 3.8+) +- Python 3.8+ +- libtomlplusplus-dev +- libpqxx-dev +- Colcon build tool +- Docker compose + +### Clone the Repository + +```bash +git clone https://git.wessel.gg/inholland/ros2-assignments.git +cd ros2-assignments +``` + +### Build the Workspace + +```bash +colcon build +``` +Any parameters can be changed before building by editing the `grade_calculator.launch.xml` in the launch folder + +### Source the Workspace + +```bash +source install/setup.bash +``` + +### Run the System + +```bash +sudo docker-compose up +ros2 launch g2_2025_grade_calculator_pkg grade_calculator.launch.xml +``` diff --git a/doc/interfaces/interfaces.md b/doc/interfaces/interfaces.md deleted file mode 100644 index 823e3bc..0000000 --- a/doc/interfaces/interfaces.md +++ /dev/null @@ -1,16 +0,0 @@ -# Interface Overview - -**Publishers** -- Topic: `exam_results` -- Message Type: `g2_2025_interfaces::msg::Exam` -- Publishes generated exam results to downstream nodes - -**Subscribers** -- Topic: `student_course_management` -- Message Type: `g2_2025_interfaces::msg::Student` -- Handles requests to add/remove student-course combinations - -**Timers** -- Interval: 2 seconds -- Function: `generate_random_result()` -- Generates and publishes exam results at regular intervals From e42856ae4ee99a34f5ebdedac6c4cd82fa03a8f4 Mon Sep 17 00:00:00 2001 From: Wessel Tip Date: Wed, 8 Oct 2025 17:52:21 +0200 Subject: [PATCH 09/14] feat(tests): Add integration tests --- .../CMakeLists.txt | 6 + .../test/test_integration_system.py | 194 ++++++++++++++++++ 2 files changed, 200 insertions(+) create mode 100644 src/g2_2025_grade_calculator_pkg/test/test_integration_system.py diff --git a/src/g2_2025_grade_calculator_pkg/CMakeLists.txt b/src/g2_2025_grade_calculator_pkg/CMakeLists.txt index f0552e8..a2e2777 100644 --- a/src/g2_2025_grade_calculator_pkg/CMakeLists.txt +++ b/src/g2_2025_grade_calculator_pkg/CMakeLists.txt @@ -165,6 +165,12 @@ if(BUILD_TESTING) target_link_libraries(${PROJECT_NAME}_test_final_grade_determinator pqxx pq tomlplusplus::tomlplusplus ) + + # Add Python integration tests + find_package(ament_cmake_pytest REQUIRED) + ament_add_pytest_test(${PROJECT_NAME}_integration_test test/test_integration_system.py + TIMEOUT 60 + ) endif() ament_package() diff --git a/src/g2_2025_grade_calculator_pkg/test/test_integration_system.py b/src/g2_2025_grade_calculator_pkg/test/test_integration_system.py new file mode 100644 index 0000000..cea157b --- /dev/null +++ b/src/g2_2025_grade_calculator_pkg/test/test_integration_system.py @@ -0,0 +1,194 @@ +import time +import rclpy +from rclpy.node import Node +from rclpy.executors import SingleThreadedExecutor +from g2_2025_interfaces.msg import Exam, Student +from g2_2025_interfaces.srv import Exams + + +class IntegrationTestNode(Node): + """Test node for integration testing of the grade calculator""" + + def __init__(self): + super().__init__('integration_test_node') + + self.received_exams = [] + self.received_students = [] + + self.student_publisher = self.create_publisher( + Student, + 'student_course_management', + 10 + ) + + self.exam_subscriber = self.create_subscription( + Exam, + 'exam_results', + self.exam_callback, + 10 + ) + + self.grade_calculator_client = self.create_client( + Exams, + 'grade_calculator_service' + ) + + def exam_callback(self, msg): + """Callback for received exam messages""" + self.get_logger().info(f'Received exam: {msg.student_name} - {msg.course_name} - {msg.result}') + self.received_exams.append(msg) + + def publish_student_enrollment(self, student_name, course_name): + """Publish a student enrollment message""" + msg = Student() + msg.student_name = student_name + msg.course_name = course_name + msg.timestamp = self.get_clock().now().to_msg() + + self.get_logger().info(f'Publishing student enrollment: {student_name} - {course_name}') + self.student_publisher.publish(msg) + + def call_grade_calculator_service(self, student_name, grades): + """Call the grade calculator service""" + if not self.grade_calculator_client.wait_for_service(timeout_sec=5.0): + self.get_logger().warn('Grade calculator service not available') + return None + + request = Exams.Request() + request.student_name = student_name + request.grades = grades + + future = self.grade_calculator_client.call_async(request) + return future + + +def test_message_publishing(): + """Test basic message publishing and receiving""" + rclpy.init() + + try: + node = IntegrationTestNode() + executor = SingleThreadedExecutor() + executor.add_node(node) + + # Allow some time for setup + time.sleep(1.0) + + # Test publishing student enrollment + node.publish_student_enrollment("TestStudent", "TestCourse") + + # Spin for a short time to process messages + start_time = time.time() + while time.time() - start_time < 3.0: + executor.spin_once(timeout_sec=0.1) + + # Test passes if no exceptions occurred + assert True, "Message publishing test completed" + + finally: + node.destroy_node() + rclpy.shutdown() + + +def test_grade_calculator_service(): + """Test the grade calculator service""" + rclpy.init() + + try: + node = IntegrationTestNode() + executor = SingleThreadedExecutor() + executor.add_node(node) + + # Test grade calculation + future = node.call_grade_calculator_service("TestStudent", [80, 90, 70]) + + if future is not None: + # Spin until service response received or timeout + start_time = time.time() + while not future.done() and time.time() - start_time < 10.0: + executor.spin_once(timeout_sec=0.1) + + if future.done(): + result = future.result() + # Verify the result is reasonable + assert 10 <= result.final_grade <= 100, f"Grade {result.final_grade} is out of valid range" + assert result.final_grade == 80, f"Expected grade 80, got {result.final_grade}" + else: + # Service not available, test passes with warning + print("Warning: Grade calculator service not available during test") + + assert True, "Grade calculator service test completed" + + finally: + node.destroy_node() + rclpy.shutdown() + + +def test_system_integration(): + """Test integration between multiple components""" + rclpy.init() + + try: + node = IntegrationTestNode() + executor = SingleThreadedExecutor() + executor.add_node(node) + + # Clear any existing messages + node.received_exams.clear() + + # Publish student enrollment + node.publish_student_enrollment("IntegrationStudent", "IntegrationCourse") + + # Spin and wait for potential exam results + start_time = time.time() + while time.time() - start_time < 5.0: + executor.spin_once(timeout_sec=0.1) + + assert True, "System integration test completed" + + for exam in node.received_exams: + assert 10 <= exam.result <= 100, f"Exam grade {exam.result} is out of valid range" + assert len(exam.student_name) > 0, "Student name should not be empty" + assert len(exam.course_name) > 0, "Course name should not be empty" + + finally: + node.destroy_node() + rclpy.shutdown() + + +def test_multiple_enrollments(): + """Test handling multiple student enrollments""" + rclpy.init() + + try: + node = IntegrationTestNode() + executor = SingleThreadedExecutor() + executor.add_node(node) + + test_students = [ + ("Tilmann", "Differentieren"), + ("Vincent", "Integreren"), + ("Wessel", "Kompjuteren") + ] + + for student, course in test_students: + node.publish_student_enrollment(student, course) + time.sleep(0.1) + + # Spin to process messages + start_time = time.time() + while time.time() - start_time < 3.0: + executor.spin_once(timeout_sec=0.1) + + assert True, "Multiple enrollments test completed" + + finally: + node.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + test_message_publishing() + test_grade_calculator_service() + test_system_integration() + test_multiple_enrollments() From 161b5084fcbd06fbee0ebad96edf23696569f562 Mon Sep 17 00:00:00 2001 From: Vincent W Date: Wed, 8 Oct 2025 18:39:16 +0200 Subject: [PATCH 10/14] fix(documentation): Update installation instructions --- doc/installation/installation.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/doc/installation/installation.md b/doc/installation/installation.md index 370c8c0..7d63cf8 100644 --- a/doc/installation/installation.md +++ b/doc/installation/installation.md @@ -30,9 +30,14 @@ Any parameters can be changed before building by editing the `grade_calculator.l source install/setup.bash ``` -### Run the System - +### Start the database ```bash sudo docker-compose up +``` +You can configure specific database settings in the `docker-compose.yaml` in the root folder or the `config.toml` file in the `src/` folder + +### Start the Grade calculator program +```bash ros2 launch g2_2025_grade_calculator_pkg grade_calculator.launch.xml ``` +To change parameters when using the launch file it will need to be edited in the `src/g2_2025_grade_calculator_pkg/launch` folder. All parameters are already added to this document and thus only the values will need to be changed From 6ccbc95b15798f18de22ac7b1a51f1a4b22a9446 Mon Sep 17 00:00:00 2001 From: Vincent W Date: Wed, 8 Oct 2025 18:40:05 +0200 Subject: [PATCH 11/14] fix(launch): Added parameter --- .../{Managers => managers}/ConfigManager.md | 82 ------------------- .../{Managers => managers}/DatabaseManager.md | 66 --------------- doc/{Tests => tests}/ConfigManager.md | 0 doc/{Tests => tests}/DatabaseManager.md | 0 doc/{Tests => tests}/ExamResultGenerator.md | 0 .../FinalGradeDeterminator.md | 0 doc/{Tests => tests}/GradeCalculator.md | 0 .../launch/grade_calculator.launch.xml | 4 +- 8 files changed, 3 insertions(+), 149 deletions(-) rename doc/architecture/{Managers => managers}/ConfigManager.md (64%) rename doc/architecture/{Managers => managers}/DatabaseManager.md (64%) rename doc/{Tests => tests}/ConfigManager.md (100%) rename doc/{Tests => tests}/DatabaseManager.md (100%) rename doc/{Tests => tests}/ExamResultGenerator.md (100%) rename doc/{Tests => tests}/FinalGradeDeterminator.md (100%) rename doc/{Tests => tests}/GradeCalculator.md (100%) diff --git a/doc/architecture/Managers/ConfigManager.md b/doc/architecture/managers/ConfigManager.md similarity index 64% rename from doc/architecture/Managers/ConfigManager.md rename to doc/architecture/managers/ConfigManager.md index 3fcdc95..664b54f 100644 --- a/doc/architecture/Managers/ConfigManager.md +++ b/doc/architecture/managers/ConfigManager.md @@ -131,85 +131,3 @@ ssl = false min_connections = 1 max_connections = 10 ``` - ---- - -## Unit Tests - -Unit tests for `ConfigManager` are implemented in `src/g2_2025_grade_calculator_pkg/test/ConfigManager.test.cpp` using Google Test and ROS2 test utilities. The tests use temporary TOML files to validate configuration loading and parsing functionality. - -### Test Cases - -#### 1. ConstructorTest - -**Description:** Verifies that ConfigManager can be created with a ROS2 logger without crashing. - -- **Test Action:** Create ConfigManager instance with ROS2 logger -- **Expected Result:** Instance created successfully without exceptions - -#### 2. LoadValidConfigTest - -**Description:** Tests loading of valid TOML configuration files with proper parsing. - -- **Test Action:** - - Create temporary TOML file with valid configuration - - Call `load_config()` with file path -- **Expected Result:** - - Returns `true` - - `is_loaded()` returns `true` - -#### 3. LoadInvalidFileTest - -**Description:** Tests error handling when attempting to load non-existent configuration files. - -- **Test Action:** Call `load_config()` with non-existent file path -- **Expected Result:** - - Returns `false` - - `is_loaded()` returns `false` - -#### 4. DatabaseConfigParsingTest - -**Description:** Tests complete database configuration parsing with all parameters. - -- **Test Configuration:** - ```toml - [database] - host = "test_host" - port = 1234 - dbname = "test_db" - user = "test_user" - password = "test_password" - timeout = 60 - ssl = true - - [database.pool] - min_connections = 2 - max_connections = 20 - ``` -- **Expected Result:** All configuration values parsed correctly with proper types - -#### 5. DatabaseConfigWithoutPoolTest - -**Description:** Tests default values when optional pool section is missing from configuration. - -- **Test Configuration:** - ```toml - [database] - host = "localhost" - port = 5432 - dbname = "grades" - user = "postgres" - password = "postgres" - ``` -- **Expected Result:** - - Main database config parsed correctly - - Default pool values: `min_connections = 1`, `max_connections = 10` - -#### 6. GetConfigWithoutLoadingTest - -**Description:** Tests behavior when attempting to access configuration before loading any file. - -- **Test Action:** Call `get_database_config()` without loading configuration -- **Expected Result:** - - Returns `std::nullopt` - - `is_loaded()` returns `false` diff --git a/doc/architecture/Managers/DatabaseManager.md b/doc/architecture/managers/DatabaseManager.md similarity index 64% rename from doc/architecture/Managers/DatabaseManager.md rename to doc/architecture/managers/DatabaseManager.md index cd14e56..a4bc1d1 100644 --- a/doc/architecture/Managers/DatabaseManager.md +++ b/doc/architecture/managers/DatabaseManager.md @@ -123,69 +123,3 @@ if (db_manager.is_connected()) { } } ``` - ---- - -## Unit Tests - -Unit tests for `DatabaseManager` are implemented in `src/g2_2025_grade_calculator_pkg/test/DatabaseManager.test.cpp` using Google Test and ROS2 test utilities. The tests are designed to work reliably whether a database connection is available or not, focusing on error handling and method behavior validation. - -### Test Cases - -#### 1. ConstructorTest - -**Description:** Verifies that DatabaseManager can be created without crashing and proper initialization occurs. - -- **Test Action:** Create DatabaseManager instance with ROS2 logger -- **Expected Result:** Instance created successfully without exceptions - -#### 2. ConnectionStatusTest - -**Description:** Tests that the `is_connected()` method returns a valid boolean value. - -- **Test Action:** Call `is_connected()` method -- **Expected Result:** Returns either `true` or `false` (no crashes or invalid states) - -#### 3. QueuePendingCombinationsTest - -**Description:** Verifies retrieval of pending student-course combinations that need exam results. - -- **Test Action:** Call `queue_pending_combinations()` -- **Expected Result:** Returns valid vector (empty if no database connection, populated if connected) - -#### 4. StoreExamResultTest - -**Description:** Tests exam result storage with graceful handling of connection states. - -- **Test Action:** - - Student: `"TestStudent"` - - Course: `"TestCourse"` - - Grade: `85` -- **Expected Result:** Returns `false` if no connection, `true` if connected and successful - -#### 5. EnrollStudentTest - -**Description:** Tests student enrollment into courses. - -- **Test Action:** - - StudentCourse object with test data -- **Expected Result:** Returns `false` if no connection, `true` if connected and successful - -#### 6. GetFinalGradeTest - -**Description:** Tests final grade retrieval for non-existent student-course combinations. - -- **Test Action:** - - Student: `"NonExistentStudent"` - - Course: `"NonExistentCourse"` -- **Expected Result:** Returns `-1` (no results found or no connection) - -#### 7. StoreFinalResultTest - -**Description:** Tests storing calculated final course results. - -- **Test Action:** - - StudentCourse object - - Exam count: `3` - - Final grade: `75` -- **Expected Result:** Returns `false` if no connection, `true` if connected and successful diff --git a/doc/Tests/ConfigManager.md b/doc/tests/ConfigManager.md similarity index 100% rename from doc/Tests/ConfigManager.md rename to doc/tests/ConfigManager.md diff --git a/doc/Tests/DatabaseManager.md b/doc/tests/DatabaseManager.md similarity index 100% rename from doc/Tests/DatabaseManager.md rename to doc/tests/DatabaseManager.md diff --git a/doc/Tests/ExamResultGenerator.md b/doc/tests/ExamResultGenerator.md similarity index 100% rename from doc/Tests/ExamResultGenerator.md rename to doc/tests/ExamResultGenerator.md diff --git a/doc/Tests/FinalGradeDeterminator.md b/doc/tests/FinalGradeDeterminator.md similarity index 100% rename from doc/Tests/FinalGradeDeterminator.md rename to doc/tests/FinalGradeDeterminator.md diff --git a/doc/Tests/GradeCalculator.md b/doc/tests/GradeCalculator.md similarity index 100% rename from doc/Tests/GradeCalculator.md rename to doc/tests/GradeCalculator.md diff --git a/src/g2_2025_grade_calculator_pkg/launch/grade_calculator.launch.xml b/src/g2_2025_grade_calculator_pkg/launch/grade_calculator.launch.xml index d3c66b4..ab2ff34 100644 --- a/src/g2_2025_grade_calculator_pkg/launch/grade_calculator.launch.xml +++ b/src/g2_2025_grade_calculator_pkg/launch/grade_calculator.launch.xml @@ -1,5 +1,7 @@ - + + + From 493e69acd1351d4bf9f438f7aecd73e41033015b Mon Sep 17 00:00:00 2001 From: Wessel Tip Date: Wed, 8 Oct 2025 20:04:13 +0200 Subject: [PATCH 12/14] fix(database): Fix database structure, remove from table if requested - Fixes the `is_retake` field on all tables - Makes the `ExamResultGenerator` remove the enrollment from the table if it is popped from its queue - Added the option to submit a grade as retake --- .../src/database/DatabaseManager.cpp | 45 +++++++++++++++---- .../src/database/DatabaseManager.hpp | 8 +++- .../src/database/SQLQueries.hpp | 24 ++++++---- .../nodes/ExamResultGenerator.cpp | 1 + .../nodes/FinalGradeDeterminator.cpp | 3 +- .../test/DatabaseManager.test.cpp | 2 +- .../test/FinalGradeDeterminator.test.cpp | 10 ++++- 7 files changed, 72 insertions(+), 21 deletions(-) diff --git a/src/g2_2025_grade_calculator_pkg/src/database/DatabaseManager.cpp b/src/g2_2025_grade_calculator_pkg/src/database/DatabaseManager.cpp index 1e2c15c..d8819eb 100644 --- a/src/g2_2025_grade_calculator_pkg/src/database/DatabaseManager.cpp +++ b/src/g2_2025_grade_calculator_pkg/src/database/DatabaseManager.cpp @@ -91,7 +91,9 @@ std::vector DatabaseManager::queue_pending_combinations() { } bool DatabaseManager::enroll_student_into_course(const StudentCourse& sc) { - if (!is_connected()) return false; + if (!is_connected()) { + return false; + } try { pqxx::work txn(*conn_); @@ -110,6 +112,28 @@ bool DatabaseManager::enroll_student_into_course(const StudentCourse& sc) { } } +bool DatabaseManager::remove_student_from_course(const StudentCourse& sc) { + if (!is_connected()) { + return false; + } + + try { + pqxx::work txn(*conn_); + + txn.exec_params(SQL_DELETE_STUDENT_ENROLLMENT, sc.student_name, sc.course_name); + + txn.commit(); + return true; + + } catch (const pqxx::sql_error &e) { + RCLCPP_ERROR(logger_, "[DBS] unenroll student from course failed: %s", e.what()); + return false; + } catch (const std::exception &e) { + RCLCPP_ERROR(logger_, "[DBS] database error: %s", e.what()); + return false; + } +} + bool DatabaseManager::store_exam_result(const std::string& student_name, const std::string& course_name, int grade) { if (!is_connected()) return false; @@ -136,7 +160,8 @@ bool DatabaseManager::store_exam_result(const std::string& student_name, const s bool DatabaseManager::store_final_course_result( const StudentCourse& sc, int exam_count, - int final_grade + int final_grade, + bool is_retake ) { if (!is_connected()) return false; @@ -145,7 +170,7 @@ bool DatabaseManager::store_final_course_result( txn.exec_params( SQL_INSERT_FINAL_COURSE_RESULT, - sc.student_name, sc.course_name, exam_count, final_grade + sc.student_name, sc.course_name, exam_count, final_grade, is_retake ); txn.commit(); @@ -182,16 +207,20 @@ void DatabaseManager::create_tables() { } void DatabaseManager::insert_sample_data() { - if (!is_connected()) return; + if (!is_connected()) { + return; + } try { pqxx::work txn(*conn_); - // Check if we already have data - auto result = txn.exec(SQL_SELECT_STUDENT_LIST); - int count = result[0][0].as(); + auto student_result = txn.exec(SQL_SELECT_STUDENT_LIST); + int student_count = student_result[0][0].as(); - if (count == 0) { + auto grades_result = txn.exec(SQL_SELECT_EXAM_RESULTS_COUNT); + int grades_count = grades_result[0][0].as(); + + if (student_count == 0 && grades_count == 0) { for (const auto& [student, course] : sample_students_data_) { txn.exec_params(SQL_INSERT_STUDENT_ENROLLMENT, student, course); } 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 ac73413..fe426f3 100644 --- a/src/g2_2025_grade_calculator_pkg/src/database/DatabaseManager.hpp +++ b/src/g2_2025_grade_calculator_pkg/src/database/DatabaseManager.hpp @@ -35,7 +35,13 @@ 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); - virtual bool store_final_course_result(const StudentCourse& sc, int exam_count, int final_grade); + bool remove_student_from_course(const StudentCourse& sc); + virtual bool store_final_course_result( + const StudentCourse& sc, + int exam_count, + int final_grade, + bool is_retake + ); int get_final_course_grade(const StudentCourse& sc); diff --git a/src/g2_2025_grade_calculator_pkg/src/database/SQLQueries.hpp b/src/g2_2025_grade_calculator_pkg/src/database/SQLQueries.hpp index f1549c4..0670420 100644 --- a/src/g2_2025_grade_calculator_pkg/src/database/SQLQueries.hpp +++ b/src/g2_2025_grade_calculator_pkg/src/database/SQLQueries.hpp @@ -12,11 +12,10 @@ static const std::string SQL_CREATE_ENROLLMENTS_TABLE = R"( id SERIAL PRIMARY KEY, student_name VARCHAR(100) NOT NULL, course_name VARCHAR(100) NOT NULL, - is_retake BOOLEAN DEFAULT FALSE, - enrolled_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + enrolled_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE(student_name, course_name) ); )"; -// UNIQUE(student_name, course_name) static const std::string SQL_CREATE_EXAM_RESULTS = R"( CREATE TABLE IF NOT EXISTS exam_results ( @@ -24,7 +23,6 @@ static const std::string SQL_CREATE_EXAM_RESULTS = R"( student_name VARCHAR(100) NOT NULL, course_name VARCHAR(100) NOT NULL, exam_grade INTEGER NOT NULL, - is_retake BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); )"; @@ -37,10 +35,15 @@ static const std::string SQL_CREATE_COURSE_RESULTS = R"( course_name VARCHAR(100) NOT NULL, exam_count INTEGER NOT NULL, final_grade INTEGER NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - UNIQUE(student_name, course_name) + is_retake BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); )"; +// UNIQUE(student_name, course_name) + +static const std::string SQL_SELECT_EXAM_RESULTS_COUNT = R"( + SELECT COUNT(*) FROM exam_results +)"; static const std::string SQL_SELECT_MISSING_RESULTS = R"( SELECT se.student_name, se.course_name @@ -70,13 +73,18 @@ static const std::string SQL_INSERT_STUDENT_ENROLLMENT = R"( ON CONFLICT DO NOTHING )"; +static const std::string SQL_DELETE_STUDENT_ENROLLMENT = R"( + DELETE FROM student_enrollments + WHERE student_name = $1 AND course_name = $2 +)"; + static const std::string SQL_SELECT_STUDENT_LIST = R"( SELECT COUNT(*) FROM student_enrollments )"; static const std::string SQL_INSERT_FINAL_COURSE_RESULT = R"( - INSERT INTO final_course_results (student_name, course_name, exam_count, final_grade) - VALUES ($1, $2, $3, $4) + INSERT INTO final_course_results (student_name, course_name, exam_count, final_grade, is_retake) + VALUES ($1, $2, $3, $4, $5) )"; // ON CONFLICT (student_name, course_name) // DO UPDATE SET exam_count = EXCLUDED.exam_count, final_grade = EXCLUDED.final_grade, created_at = CURRENT_TIMESTAMP diff --git a/src/g2_2025_grade_calculator_pkg/src/exam_result_generator/nodes/ExamResultGenerator.cpp b/src/g2_2025_grade_calculator_pkg/src/exam_result_generator/nodes/ExamResultGenerator.cpp index daac8ca..1b78e9b 100644 --- a/src/g2_2025_grade_calculator_pkg/src/exam_result_generator/nodes/ExamResultGenerator.cpp +++ b/src/g2_2025_grade_calculator_pkg/src/exam_result_generator/nodes/ExamResultGenerator.cpp @@ -91,6 +91,7 @@ void ExamResultGenerator::student_management_callback(const g2_2025_interfaces:: if (it != operations_queue_.end()) { operations_queue_.erase(it); + db_manager_->remove_student_from_course(sc); RCLCPP_INFO(this->get_logger(), "removed from queue: %s - %s", sc.student_name.c_str(), sc.course_name.c_str() ); 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 7bebb39..8a38400 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 @@ -92,7 +92,8 @@ void FinalGradeDeterminator::grade_calculator_response( db_manager_->store_final_course_result( studentCourseCombo, grade_collection_amount_, - response->result + response->result, + false ); } diff --git a/src/g2_2025_grade_calculator_pkg/test/DatabaseManager.test.cpp b/src/g2_2025_grade_calculator_pkg/test/DatabaseManager.test.cpp index 417dd8e..398cd33 100644 --- a/src/g2_2025_grade_calculator_pkg/test/DatabaseManager.test.cpp +++ b/src/g2_2025_grade_calculator_pkg/test/DatabaseManager.test.cpp @@ -73,7 +73,7 @@ TEST_F(DatabaseManagerTest, StoreFinalResultTest) { sc.student_name = "TestStudent"; sc.course_name = "TestCourse"; - bool result = db_manager_->store_final_course_result(sc, 3, 75); + bool result = db_manager_->store_final_course_result(sc, 3, 75, false); EXPECT_TRUE(result == true || result == false); } 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 4be2461..c4c728f 100644 --- a/src/g2_2025_grade_calculator_pkg/test/FinalGradeDeterminator.test.cpp +++ b/src/g2_2025_grade_calculator_pkg/test/FinalGradeDeterminator.test.cpp @@ -18,6 +18,7 @@ struct MockStoredResult { StudentCourse sc; int exam_count; int final_grade; + bool is_retake; }; class MockDatabaseManager : public DatabaseManager { @@ -30,8 +31,13 @@ public: 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 }); + bool store_final_course_result( + const StudentCourse& sc, + int exam_count, + int final_grade, + bool is_retake + ) override { + stored_results_.push_back({ sc, exam_count, final_grade, is_retake }); return true; // Always succeed } From 644697326d89116bb4162b469e26d6691255ec73 Mon Sep 17 00:00:00 2001 From: Wessel Tip Date: Wed, 8 Oct 2025 20:20:17 +0200 Subject: [PATCH 13/14] feat(database): Add method to fetch failed courses --- .../src/database/DatabaseManager.cpp | 33 ++++++++++++++++++- .../src/database/DatabaseManager.hpp | 1 + .../src/database/SQLQueries.hpp | 9 ++++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/g2_2025_grade_calculator_pkg/src/database/DatabaseManager.cpp b/src/g2_2025_grade_calculator_pkg/src/database/DatabaseManager.cpp index d8819eb..f895e37 100644 --- a/src/g2_2025_grade_calculator_pkg/src/database/DatabaseManager.cpp +++ b/src/g2_2025_grade_calculator_pkg/src/database/DatabaseManager.cpp @@ -242,7 +242,7 @@ int DatabaseManager::get_final_course_grade(const StudentCourse& sc) { try { pqxx::work txn(*conn_); - auto result = txn.exec_params(SQL_SELECT_STUDENT_COURSE_RESULTS, sc.student_name, sc.course_name); + auto result = txn.exec_params(SQL_SELECT_STUDENT_EXAM_RESULTS, sc.student_name, sc.course_name); if (result.size() == 1) { int exam_count = result[0][0].as(); @@ -264,4 +264,35 @@ int DatabaseManager::get_final_course_grade(const StudentCourse& sc) { } } +std::vector DatabaseManager::get_failed_course_results() { + std::vector failed_courses; + + if (!is_connected()) { + return failed_courses; + } + + try { + pqxx::work txn(*conn_); + + auto result = txn.exec(SQL_SELECT_FAILED_COURSE_RESULTS); + + for (const auto& row : result) { + StudentCourse sc; + sc.student_name = row[0].as(); + sc.course_name = row[1].as(); + + failed_courses.push_back(sc); + } + + RCLCPP_INFO(logger_, "[DBS] found %zu failed course results", failed_courses.size()); + + } catch (const pqxx::sql_error &e) { + RCLCPP_ERROR(logger_, "[DBS] 'get failed course results' failed: %s", e.what()); + } catch (const std::exception &e) { + RCLCPP_ERROR(logger_, "[DBS] database error: %s", e.what()); + } + + return failed_courses; +} + } // namespace assignments::one 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 fe426f3..09c41cd 100644 --- a/src/g2_2025_grade_calculator_pkg/src/database/DatabaseManager.hpp +++ b/src/g2_2025_grade_calculator_pkg/src/database/DatabaseManager.hpp @@ -44,6 +44,7 @@ public: ); int get_final_course_grade(const StudentCourse& sc); + std::vector get_failed_course_results(); private: rclcpp::Logger logger_; diff --git a/src/g2_2025_grade_calculator_pkg/src/database/SQLQueries.hpp b/src/g2_2025_grade_calculator_pkg/src/database/SQLQueries.hpp index 0670420..0b9f5a8 100644 --- a/src/g2_2025_grade_calculator_pkg/src/database/SQLQueries.hpp +++ b/src/g2_2025_grade_calculator_pkg/src/database/SQLQueries.hpp @@ -54,12 +54,19 @@ static const std::string SQL_SELECT_MISSING_RESULTS = R"( WHERE fcr.id IS NULL )"; -static const std::string SQL_SELECT_STUDENT_COURSE_RESULTS = R"( +static const std::string SQL_SELECT_STUDENT_EXAM_RESULTS = R"( SELECT COUNT(*), AVG(exam_grade) FROM exam_results WHERE student_name = $1 AND course_name = $2 )"; + +static const std::string SQL_SELECT_FAILED_COURSE_RESULTS = R"( + SELECT fcr.student_name, fcr.course_name, fcr.final_grade, fcr.exam_count + FROM final_course_results fcr + WHERE fcr.final_grade < 55 AND fcr.is_retake = FALSE +)"; + static const std::string SQL_INSERT_EXAM_RESULT = R"( INSERT INTO exam_results (student_name, course_name, exam_grade) VALUES ($1, $2, $3) From 1ceb691fae24322b3414664922c39bf2f37e31e3 Mon Sep 17 00:00:00 2001 From: Vincent W Date: Thu, 9 Oct 2025 13:25:26 +0200 Subject: [PATCH 14/14] fix(documentation): Fix formatting, move texst to correct location --- README.md | 103 +----------------- doc/architecture/architecture.md | 12 +- doc/architecture/interfaces/interfaces.md | 17 ++- doc/architecture/nodes/ExamResultGenerator.md | 1 - .../nodes/FinalGradeDeterminator.md | 3 - doc/tests/DatabaseManager.md | 2 - doc/tests/ExamResultGenerator.md | 1 - 7 files changed, 19 insertions(+), 120 deletions(-) diff --git a/README.md b/README.md index f74c8b4..113f26c 100644 --- a/README.md +++ b/README.md @@ -19,109 +19,12 @@ Assignments made for the first semester of ROS2 ## System Architecture -The TI Minor Grade Generator is a ROS2-based distributed system for managing student exam results and calculating final grades. The system follows a microservices architecture with clear separation of concerns. - -### Overview - -The system consists of multiple ROS2 nodes that communicate through topics and services to process exam data, calculate grades, and store the results in a database. - -### Components - -#### Core Nodes - -1. **ExamResultGenerator** (`assignments::one::exam_resutl_generator`) - - Publishes random exam results for students and courses - - Simulates exam result generation for testing and demonstration - - ([Full documentation](doc/nodes/ExamResultGenerator.md)) - -2. **FinalGradeDeterminator** (`assignments::one::final_grade_determinator`) - - Collect grades from ExamResultGenerator - - Send results to the GradeCalculator - - Put resulting grade in the Database - - ([Full documentation](doc/nodes/FinalGradeDeterminator.md)) - -3. **GradeCalculator** (`assignments::one::grade_calculator`) - - Provides ROS2 service for calculating student exam grades - - Ensures grade results are within valid bounds (10-100) - - ([Full documentation](doc/nodes/GradeCalculator.md)) - -#### Database Management - -- **DatabaseManager**: Handles all database operations including storing final course results -- ([Full documentation](doc/DatabaseManager.md)) - -#### Configuration Management - -- **ConfigManager**: Loads and validates system configuration from TOML files -- Provides runtime access to configuration parameters for all nodes -- Ensures consistent environment setup and error handling for misconfigurations -- ([Full documentation](doc/ConfigManager.md)) - -#### Message Interfaces - -- **Exam Messages** (`g2_2025_interfaces::msg::Exam`): Contains student name, course name, result, and timestamp -- **Student Messages** (`g2_2025_interfaces::msg::Student`): Contains student and course information with timestamp -- **Grade Calculation Service** (`g2_2025_interfaces::srv::Exams`): Service interface for grade calculation requests - -### Workflow - -1. **Exam Result Collection**: Exam results are published to the `exam_results` topic -2. **Result Aggregation**: FinalGradeDeterminator collects results until the configured threshold is met -3. **Grade Calculation**: When enough results are available, a service call is made to GradeCalculator -4. **Result Processing**: Calculated grades are published and stored in the database -5. **Student Management**: Final student information is published to `student_course_management` topic as this stops the random generation of grades -6. **Retake scheduler**: Schedule an exam retake for the grades below 55 by resending information to `student_course_management` as this will re-enable grade generation -7. **Retake grade determination**: repeat steps 1 through 6 using the `retake_grade_determinator` node instead of `final_grade_determinator` until every student hase a passing grade - -### Configuration - -The system uses TOML configuration files for environment-specific settings: -- Database connection parameters -- ROS2 node configurations -- Grade collection thresholds +For the complete system architecture see [architecture.md](doc/architecture/architecture.md) located in the `doc/architecture` folder ### Testing -Unit tests are implemented using Google Test framework: -- Mock services and database managers for isolated testing -- Parameterized tests for various grade calculation scenarios -- Documentation on the tests are located in the full documentation of the nodes themselves +The testing documentation can be found in the [doc/tests](doc/tests/) folder for each node ## Installation -### Prerequisites - -- ROS2 Jazzy or newer installed ([ROS2 Installation Guide](https://docs.ros.org/en/jazzy/Installation.html)) -- CMake (version 3.8+) -- Python 3.8+ -- libtomlplusplus-dev -- libpqxx-dev -- Colcon build tool -- Docker compose - -### Clone the Repository - -```bash -git clone https://git.wessel.gg/inholland/ros2-assignments.git -cd ros2-assignments -``` - -### Build the Workspace - -```bash -colcon build -``` -Any parameters can be changed before building by editing the `grade_calculator.launch.xml` in the launch folder - -### Source the Workspace - -```bash -source install/setup.bash -``` - -### Run the System - -```bash -sudo docker-compose up -ros2 launch g2_2025_grade_calculator_pkg grade_calculator.launch.xml -``` +For installation instructions see [Installation.md](doc/installation/installation.md) located in the `doc/installation` folder diff --git a/doc/architecture/architecture.md b/doc/architecture/architecture.md index 06bae16..bd59016 100644 --- a/doc/architecture/architecture.md +++ b/doc/architecture/architecture.md @@ -2,7 +2,8 @@ ## Project Overview -The TI Minor Grade Generator is a distributed ROS2-based system designed to manage student exam results and calculate final course grades. The system follows microservices architecture principles with clear separation of concerns and robust data management. +Giving marks to students cost a lot of time and pain. For this a automatic solution is needed. +Therefore the overall Software architect (Tilmann K.) has designed a solution for the ROS2 architecture. ## System Architecture @@ -14,9 +15,8 @@ The system consists of multiple ROS2 nodes that communicate through standardized - **Microservices Architecture**: Each component has a single responsibility - **Asynchronous Communication**: Uses ROS2 topics and services for loose coupling -- **Data Persistence**: Centralized database management for reliable storage -- **Configurable Parameters**: TOML-based configuration for environment flexibility -- **Comprehensive Testing**: Unit and integration tests ensure reliability +- **Data Persistence**: Centralized database management for datastorage +- **Comprehensive Testing**: Unit tests ensure code reliability ## System Components @@ -145,3 +145,7 @@ max_grade = 100 level = "INFO" output_file = "grade_system.log" ``` + +## Testing + +The testing documentation can be found in the [doc/tests](/doc/tests/) folder for each node diff --git a/doc/architecture/interfaces/interfaces.md b/doc/architecture/interfaces/interfaces.md index 72f58d8..c6192aa 100644 --- a/doc/architecture/interfaces/interfaces.md +++ b/doc/architecture/interfaces/interfaces.md @@ -88,20 +88,23 @@ string course_name #### Result ``` -# Empty result section - completion indicates success +float32 result ``` #### Feedback ``` -float32 result +string status ``` **Goal Fields**: - `student_name`: Student for which a retake is requested - `course_name`: Course for which a retake is requested -**Feedback Fields**: -- `result`: Progress indicator or intermediate result (float value) +**Result Field** +- `result`: Progress indicator or intermediate result (float value) (*Not Implemented*) + +**Feedback Field**: +- `status`: Status indicator (*Not Implemented*) **Usage**: Used by RetakeScheduler node to handle long-running retake management operations with progress feedback. @@ -135,7 +138,7 @@ float32 result **Publishers**: - **Topic**: `student_course_management` - **Message Type**: `g2_2025_interfaces::msg::Student` -- **Purpose**: Communicate end of enrollment or re-enrolment +- **Purpose**: Communicate end of enrollment when grade has been calculated ### GradeCalculator Node @@ -164,7 +167,3 @@ float32 result - **Topic**: `retake_results` - **Message Type**: `g2_2025_interfaces::msg::Exam` - **Purpose**: Processes completed retake exam results - -## Related Documentation - -- [System Architecture](../Orig.md): Overall system design and communication patterns diff --git a/doc/architecture/nodes/ExamResultGenerator.md b/doc/architecture/nodes/ExamResultGenerator.md index 2f7fadb..ff760e8 100644 --- a/doc/architecture/nodes/ExamResultGenerator.md +++ b/doc/architecture/nodes/ExamResultGenerator.md @@ -40,4 +40,3 @@ ExamResultGenerator() **`void add_student_course_combination(const StudentCourse& sc)`** - Enrolls student into course via database - Adds combination to processing queue - diff --git a/doc/architecture/nodes/FinalGradeDeterminator.md b/doc/architecture/nodes/FinalGradeDeterminator.md index 57ee589..13395a2 100644 --- a/doc/architecture/nodes/FinalGradeDeterminator.md +++ b/doc/architecture/nodes/FinalGradeDeterminator.md @@ -32,11 +32,8 @@ FinalGradeDeterminator() - 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/tests/DatabaseManager.md b/doc/tests/DatabaseManager.md index e09a8be..b90b1ab 100644 --- a/doc/tests/DatabaseManager.md +++ b/doc/tests/DatabaseManager.md @@ -1,5 +1,3 @@ - - ## Unit Tests Unit tests for `DatabaseManager` are implemented in `src/g2_2025_grade_calculator_pkg/test/DatabaseManager.test.cpp` using Google Test and ROS2 test utilities. The tests are designed to work reliably whether a database connection is available or not, focusing on error handling and method behavior validation. diff --git a/doc/tests/ExamResultGenerator.md b/doc/tests/ExamResultGenerator.md index b1b7882..7fbbf7f 100644 --- a/doc/tests/ExamResultGenerator.md +++ b/doc/tests/ExamResultGenerator.md @@ -1,4 +1,3 @@ - ## Unit Tests Unit tests for `ExamResultGenerator` are implemented in `src/g2_2025_grade_calculator_pkg/test/ExamResultGenerator.test.cpp` using Google Test and ROS2 test utilities. The tests use a test subscriber node to capture published messages and validate node behavior.