diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..a6dba10 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.10.0) +project(test_plan_execute_t1) +include(FetchContent) +if (MSVC) + add_compile_options(/utf-8) +endif() +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip +) +# For Windows: Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../include) + +include(CTest) +enable_testing() + +add_executable(test_plan_execute_t1 test_cms_engine.cpp ../src/app.cpp) + +target_link_libraries(test_plan_execute_t1 gtest gmock gtest_main) +include(GoogleTest) +gtest_discover_tests(test_plan_execute_t1) \ No newline at end of file diff --git a/tests/test_cms_engine.cpp b/tests/test_cms_engine.cpp new file mode 100644 index 0000000..e0969f6 --- /dev/null +++ b/tests/test_cms_engine.cpp @@ -0,0 +1,316 @@ +#include +#include +#include +#include "app.hpp" + +// ============================================================================ +// Test Fixture for CmsEngine +// ============================================================================ +class CmsEngineTest : public ::testing::Test { +protected: + CmsEngine engine; + + void SetUp() override { + // Initialize with some plans for findPlanById and updatePlanStatus tests + TaskPlan plan1; + plan1.name = "Plan1"; + engine.createTaskPlan(plan1); + + TaskPlan plan2; + plan2.name = "Plan2"; + engine.createTaskPlan(plan2); + + // Register a template for registerTemplate tests + TemplateInstance tmpl; + tmpl.templateId = "TMPL-001"; + tmpl.confidence = 0.8; + engine.registerTemplate(tmpl); + } + + // Helper to get the first plan's ID + std::string getFirstPlanId() const { + const auto& plans = engine.getAllPlans(); + if (!plans.empty()) { + return plans[0].id; + } + return ""; + } + + // Helper to get the second plan's ID + std::string getSecondPlanId() const { + const auto& plans = engine.getAllPlans(); + if (plans.size() > 1) { + return plans[1].id; + } + return ""; + } +}; + +// ============================================================================ +// Tests for findPlanById +// ============================================================================ + +/** + * @test 正常输入测试 - 查找存在的方案 + * @description 使用有效的 planId 查找已存在的方案,应返回正确的方案指针 + */ +TEST_F(CmsEngineTest, testFindPlanByIdExistingPlan) { + std::string planId = getFirstPlanId(); + ASSERT_FALSE(planId.empty()) << "Plan ID should not be empty"; + + const TaskPlan* result = engine.findPlanById(planId); + ASSERT_NE(result, nullptr) << "Should find the existing plan"; + EXPECT_EQ(result->id, planId) << "Plan ID should match"; + EXPECT_EQ(result->name, "Plan1") << "Plan name should match"; +} + +/** + * @test 边界值测试 - 查找不存在的方案 + * @description 使用不存在的 planId 查找方案,应返回 nullptr + */ +TEST_F(CmsEngineTest, testFindPlanByIdNonExistingPlan) { + const std::string nonExistingId = "ID-99999"; + const TaskPlan* result = engine.findPlanById(nonExistingId); + EXPECT_EQ(result, nullptr) << "Should return nullptr for non-existing plan"; +} + +/** + * @test 边界值测试 - 查找空字符串 ID + * @description 使用空字符串作为 planId 查找方案,应返回 nullptr + */ +TEST_F(CmsEngineTest, testFindPlanByIdEmptyId) { + const std::string emptyId = ""; + const TaskPlan* result = engine.findPlanById(emptyId); + EXPECT_EQ(result, nullptr) << "Should return nullptr for empty ID"; +} + +/** + * @test 特殊场景测试 - 查找多个方案中的特定方案 + * @description 在多个方案中查找第二个方案,应返回正确的方案 + */ +TEST_F(CmsEngineTest, testFindPlanByIdSecondPlan) { + std::string planId = getSecondPlanId(); + ASSERT_FALSE(planId.empty()) << "Second plan ID should not be empty"; + + const TaskPlan* result = engine.findPlanById(planId); + ASSERT_NE(result, nullptr) << "Should find the second plan"; + EXPECT_EQ(result->id, planId) << "Plan ID should match"; + EXPECT_EQ(result->name, "Plan2") << "Plan name should match"; +} + +/** + * @test 异常输入测试 - 查找特殊字符 ID + * @description 使用包含特殊字符的 planId 查找方案,应返回 nullptr + */ +TEST_F(CmsEngineTest, testFindPlanByIdSpecialChars) { + const std::string specialId = "!@#$%^&*()"; + const TaskPlan* result = engine.findPlanById(specialId); + EXPECT_EQ(result, nullptr) << "Should return nullptr for special character ID"; +} + +// ============================================================================ +// Tests for updatePlanStatus +// ============================================================================ + +/** + * @test 正常输入测试 - 更新方案状态 + * @description 使用有效的 planId 更新方案状态,应返回 true 并更新状态 + */ +TEST_F(CmsEngineTest, testUpdatePlanStatusSuccess) { + std::string planId = getFirstPlanId(); + ASSERT_FALSE(planId.empty()) << "Plan ID should not be empty"; + + PlanStatus newStatus = PlanStatus::Approved; + bool result = engine.updatePlanStatus(planId, newStatus); + EXPECT_TRUE(result) << "Should return true for successful update"; + + // Verify the status was updated + const TaskPlan* updatedPlan = engine.findPlanById(planId); + ASSERT_NE(updatedPlan, nullptr) << "Plan should still exist"; + EXPECT_EQ(updatedPlan->status, newStatus) << "Plan status should be updated"; +} + +/** + * @test 边界值测试 - 更新不存在的方案状态 + * @description 使用不存在的 planId 更新方案状态,应返回 false + */ +TEST_F(CmsEngineTest, testUpdatePlanStatusNonExistingPlan) { + const std::string nonExistingId = "ID-99999"; + bool result = engine.updatePlanStatus(nonExistingId, PlanStatus::Approved); + EXPECT_FALSE(result) << "Should return false for non-existing plan"; +} + +/** + * @test 边界值测试 - 更新空字符串 ID 的方案状态 + * @description 使用空字符串作为 planId 更新方案状态,应返回 false + */ +TEST_F(CmsEngineTest, testUpdatePlanStatusEmptyId) { + const std::string emptyId = ""; + bool result = engine.updatePlanStatus(emptyId, PlanStatus::Approved); + EXPECT_FALSE(result) << "Should return false for empty ID"; +} + +/** + * @test 特殊场景测试 - 更新方案状态为 Drafting + * @description 更新方案状态为 Drafting,应返回 true 并更新状态 + */ +TEST_F(CmsEngineTest, testUpdatePlanStatusToDrafting) { + std::string planId = getFirstPlanId(); + ASSERT_FALSE(planId.empty()) << "Plan ID should not be empty"; + + bool result = engine.updatePlanStatus(planId, PlanStatus::Drafting); + EXPECT_TRUE(result) << "Should return true for successful update"; + + const TaskPlan* updatedPlan = engine.findPlanById(planId); + ASSERT_NE(updatedPlan, nullptr) << "Plan should still exist"; + EXPECT_EQ(updatedPlan->status, PlanStatus::Drafting) << "Plan status should be Drafting"; +} + +/** + * @test 特殊场景测试 - 多次更新方案状态 + * @description 连续多次更新同一方案的状态,应每次返回 true 并更新状态 + */ +TEST_F(CmsEngineTest, testUpdatePlanStatusMultipleTimes) { + std::string planId = getFirstPlanId(); + ASSERT_FALSE(planId.empty()) << "Plan ID should not be empty"; + + // First update + bool result1 = engine.updatePlanStatus(planId, PlanStatus::Approved); + EXPECT_TRUE(result1) << "First update should succeed"; + + // Second update + bool result2 = engine.updatePlanStatus(planId, PlanStatus::Executing); + EXPECT_TRUE(result2) << "Second update should succeed"; + + // Third update + bool result3 = engine.updatePlanStatus(planId, PlanStatus::Completed); + EXPECT_TRUE(result3) << "Third update should succeed"; + + // Verify final status + const TaskPlan* updatedPlan = engine.findPlanById(planId); + ASSERT_NE(updatedPlan, nullptr) << "Plan should still exist"; + EXPECT_EQ(updatedPlan->status, PlanStatus::Completed) << "Plan status should be Completed"; +} + +// ============================================================================ +// Tests for registerTemplate +// ============================================================================ + +/** + * @test 正常输入测试 - 注册模板 + * @description 注册一个有效的模板实例,应成功添加 + */ +TEST_F(CmsEngineTest, testRegisterTemplateSuccess) { + TemplateInstance tmpl; + tmpl.templateId = "TMPL-002"; + tmpl.confidence = 0.9; + + engine.registerTemplate(tmpl); + + // Verify the template was registered by matching + const TemplateInstance* matched = engine.matchTemplate("test_scenario"); + ASSERT_NE(matched, nullptr) << "Should find a template after registration"; + EXPECT_EQ(matched->templateId, "TMPL-002") << "Should match the newly registered template"; + EXPECT_EQ(matched->confidence, 0.9) << "Confidence should match"; +} + +/** + * @test 边界值测试 - 注册空 ID 模板 + * @description 注册一个 templateId 为空的模板实例,应成功添加(允许空 ID) + */ +TEST_F(CmsEngineTest, testRegisterTemplateEmptyId) { + TemplateInstance tmpl; + tmpl.templateId = ""; + tmpl.confidence = 0.5; + + engine.registerTemplate(tmpl); + + // Verify the template was registered + const TemplateInstance* matched = engine.matchTemplate("test_scenario"); + ASSERT_NE(matched, nullptr) << "Should find a template after registration"; + EXPECT_EQ(matched->templateId, "") << "Template ID should be empty"; +} + +/** + * @test 边界值测试 - 注册零置信度模板 + * @description 注册一个置信度为 0 的模板实例,应成功添加 + */ +TEST_F(CmsEngineTest, testRegisterTemplateZeroConfidence) { + TemplateInstance tmpl; + tmpl.templateId = "TMPL-003"; + tmpl.confidence = 0.0; + + engine.registerTemplate(tmpl); + + // Verify the template was registered + const TemplateInstance* matched = engine.matchTemplate("test_scenario"); + ASSERT_NE(matched, nullptr) << "Should find a template after registration"; + EXPECT_EQ(matched->confidence, 0.0) << "Confidence should be 0.0"; +} + +/** + * @test 特殊场景测试 - 注册多个模板 + * @description 注册多个模板实例,应全部成功添加 + */ +TEST_F(CmsEngineTest, testRegisterTemplateMultiple) { + TemplateInstance tmpl1; + tmpl1.templateId = "TMPL-004"; + tmpl1.confidence = 0.7; + + TemplateInstance tmpl2; + tmpl2.templateId = "TMPL-005"; + tmpl2.confidence = 0.9; + + TemplateInstance tmpl3; + tmpl3.templateId = "TMPL-006"; + tmpl3.confidence = 0.5; + + engine.registerTemplate(tmpl1); + engine.registerTemplate(tmpl2); + engine.registerTemplate(tmpl3); + + // Verify the best template is the one with highest confidence + const TemplateInstance* matched = engine.matchTemplate("test_scenario"); + ASSERT_NE(matched, nullptr) << "Should find a template after registration"; + EXPECT_EQ(matched->templateId, "TMPL-005") << "Should match the template with highest confidence"; + EXPECT_EQ(matched->confidence, 0.9) << "Confidence should be 0.9"; +} + +/** + * @test 特殊场景测试 - 注册相同 ID 的模板 + * @description 注册两个 templateId 相同的模板实例,应都成功添加 + */ +TEST_F(CmsEngineTest, testRegisterTemplateDuplicateId) { + TemplateInstance tmpl1; + tmpl1.templateId = "TMPL-007"; + tmpl1.confidence = 0.6; + + TemplateInstance tmpl2; + tmpl2.templateId = "TMPL-007"; + tmpl2.confidence = 0.8; + + engine.registerTemplate(tmpl1); + engine.registerTemplate(tmpl2); + + // Verify both templates are registered and the one with higher confidence is matched + const TemplateInstance* matched = engine.matchTemplate("test_scenario"); + ASSERT_NE(matched, nullptr) << "Should find a template after registration"; + EXPECT_EQ(matched->confidence, 0.8) << "Should match the template with higher confidence"; +} + +/** + * @test 异常输入测试 - 注册后立即匹配 + * @description 注册一个模板后立即匹配,应返回刚注册的模板 + */ +TEST_F(CmsEngineTest, testRegisterTemplateAndMatchImmediately) { + TemplateInstance tmpl; + tmpl.templateId = "TMPL-008"; + tmpl.confidence = 1.0; + + engine.registerTemplate(tmpl); + + const TemplateInstance* matched = engine.matchTemplate("any_scenario"); + ASSERT_NE(matched, nullptr) << "Should find a template immediately after registration"; + EXPECT_EQ(matched->templateId, "TMPL-008") << "Should match the just registered template"; + EXPECT_EQ(matched->confidence, 1.0) << "Confidence should be 1.0"; +}