diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..cf23571 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.10.0) +project(test_presentation) +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_presentation test_app.cpp ../src/app.cpp) + +target_link_libraries(test_presentation gtest gmock gtest_main) +include(GoogleTest) +gtest_discover_tests(test_presentation) \ No newline at end of file diff --git a/tests/test_app.cpp b/tests/test_app.cpp new file mode 100644 index 0000000..6abafb0 --- /dev/null +++ b/tests/test_app.cpp @@ -0,0 +1,253 @@ +#include "app.hpp" +#include +#include +#include +#include +#include + +using namespace ocpm; + +// 测试夹具 +class PlanManagerTest : public ::testing::Test { +protected: + PlanManager manager; + void SetUp() override { manager.loadSampleData(); } +}; + +class DistributedPlanManagerTest : public ::testing::Test { +protected: + DistributedPlanManager dpm; + void SetUp() override { dpm.loadSampleData(); } +}; + +class DispatchManagerTest : public ::testing::Test { +protected: + DispatchManager dm; + void SetUp() override { dm.loadSampleData(); } +}; + +// 1. queryPlans 测试 +TEST_F(PlanManagerTest, testQueryPlansNormal) { + PlanQueryRequest req; + req.status_filter = -1; req.priority_min = 0; req.keyword = ""; + req.page = 1; req.page_size = 10; + SortParam sort; sort.field = "priority"; sort.direction = SortDirection::DESC; + auto res = manager.queryPlans(req, sort); + EXPECT_FALSE(res.empty()); + EXPECT_LE(res.size(), 10); + EXPECT_GE(res[0].priority, res.back().priority); +} + +TEST_F(PlanManagerTest, testQueryPlansBoundaryEmptyResult) { + PlanQueryRequest req; + req.status_filter = -1; req.priority_min = 100; req.keyword = ""; + req.page = 1; req.page_size = 5; + SortParam sort; sort.field = "priority"; sort.direction = SortDirection::ASC; + auto res = manager.queryPlans(req, sort); + EXPECT_TRUE(res.empty()); +} + +TEST_F(PlanManagerTest, testQueryPlansPaginationOutOfBounds) { + PlanQueryRequest req; + req.status_filter = -1; req.priority_min = 0; req.keyword = ""; + req.page = 100; req.page_size = 5; + SortParam sort; sort.field = "create_time"; sort.direction = SortDirection::ASC; + auto res = manager.queryPlans(req, sort); + EXPECT_TRUE(res.empty()); +} + +// 2. getPlanDetail 测试 +TEST_F(PlanManagerTest, testGetPlanDetailNormal) { + auto p = manager.getPlanDetail("PLAN-001"); + EXPECT_EQ(p.plan_id, "PLAN-001"); + EXPECT_EQ(p.plan_name, "突击行动-甲"); +} + +TEST_F(PlanManagerTest, testGetPlanDetailExceptionNotFound) { + EXPECT_THROW(manager.getPlanDetail("INVALID-ID"), std::invalid_argument); +} + +// 3. comparePlans 测试 +TEST_F(PlanManagerTest, testComparePlansNormalDifferent) { + auto diffs = manager.comparePlans("PLAN-001", "PLAN-002"); + EXPECT_FALSE(diffs.empty()); + EXPECT_NE(diffs[0], "(no differences)"); +} + +TEST_F(PlanManagerTest, testComparePlansBoundaryIdentical) { + auto diffs = manager.comparePlans("PLAN-001", "PLAN-001"); + EXPECT_EQ(diffs.size(), 1); + EXPECT_EQ(diffs[0], "(no differences)"); +} + +// 4. reconstruct 测试 +TEST_F(PlanManagerTest, testReconstructNormal) { + ReconstructCommand cmd; cmd.plan_id = "PLAN-001"; + auto res = manager.reconstruct(cmd); + EXPECT_TRUE(res.success); + EXPECT_EQ(res.new_version, "PLAN-001-v2"); +} + +TEST_F(PlanManagerTest, testReconstructBoundaryNotFound) { + ReconstructCommand cmd; cmd.plan_id = "NON-EXIST"; + auto res = manager.reconstruct(cmd); + EXPECT_FALSE(res.success); + EXPECT_NE(res.message.find("未找到"), std::string::npos); +} + +// 5. notifyHitlEvent 测试 +TEST_F(PlanManagerTest, testNotifyHitlEventNormal) { + HitlEvent evt; + EXPECT_NO_THROW(manager.notifyHitlEvent(evt)); +} + +// 6. setAlgorithmProgressCallback 测试 +TEST_F(PlanManagerTest, testSetAlgorithmProgressCallbackNormal) { + bool called = false; + auto cb = [&called](const std::string&, int) { called = true; }; + EXPECT_NO_THROW(manager.setAlgorithmProgressCallback(cb)); + EXPECT_NO_THROW(manager.setAlgorithmProgressCallback(nullptr)); +} + +// 7. loadSampleData (PlanManager) 测试 +TEST_F(PlanManagerTest, testLoadSampleDataNormal) { + manager.loadSampleData(); + PlanQueryRequest req; req.status_filter = -1; req.priority_min = 0; req.keyword = ""; + req.page = 1; req.page_size = 100; + SortParam sort; sort.field = "priority"; sort.direction = SortDirection::ASC; + auto res = manager.queryPlans(req, sort); + EXPECT_EQ(res.size(), 5); +} + +// 8. addNode 测试 +TEST_F(DistributedPlanManagerTest, testAddNodeNormal) { + TopoNode n{"NODE-NEW", "Test", 0.0, 0.0}; + EXPECT_NO_THROW(dpm.addNode(n)); +} + +TEST_F(DistributedPlanManagerTest, testAddNodeBoundaryDuplicate) { + TopoNode n{"NODE-01", "Dup", 0.0, 0.0}; + EXPECT_NO_THROW(dpm.addNode(n)); + EXPECT_NO_THROW(dpm.addNode(n)); // 重复添加应被忽略 +} + +// 9. removeNode 测试 +TEST_F(DistributedPlanManagerTest, testRemoveNodeNormal) { + EXPECT_NO_THROW(dpm.removeNode("NODE-02")); +} + +TEST_F(DistributedPlanManagerTest, testRemoveNodeBoundaryNonExistent) { + EXPECT_NO_THROW(dpm.removeNode("NON-EXIST")); +} + +// 10. addEdge 测试 +TEST_F(DistributedPlanManagerTest, testAddEdgeNormal) { + TopoEdge e{"EDGE-NEW", "NODE-01", "NODE-04", 10}; + EXPECT_NO_THROW(dpm.addEdge(e)); +} + +TEST_F(DistributedPlanManagerTest, testAddEdgeBoundaryDuplicate) { + TopoEdge e{"EDGE-01", "NODE-01", "NODE-02", 100}; + EXPECT_NO_THROW(dpm.addEdge(e)); + EXPECT_NO_THROW(dpm.addEdge(e)); // 重复添加应被忽略 +} + +// 11. removeEdge 测试 +TEST_F(DistributedPlanManagerTest, testRemoveEdgeNormal) { + EXPECT_NO_THROW(dpm.removeEdge("EDGE-01")); +} + +TEST_F(DistributedPlanManagerTest, testRemoveEdgeBoundaryNonExistent) { + EXPECT_NO_THROW(dpm.removeEdge("NON-EXIST")); +} + +// 12. isConnected 测试(通过公共接口间接验证) +TEST_F(DistributedPlanManagerTest, testIsConnectedIndirectly) { + // 加载样本数据后图应连通,通过添加/删除操作验证行为 + dpm.loadSampleData(); + EXPECT_NO_THROW(dpm.removeNode("NODE-02")); // 移除节点后仍应连通 + EXPECT_NO_THROW(dpm.removeEdge("EDGE-03")); // 移除边后可能断开,但无直接验证方法 +} + +// 13. loadSampleData (DistributedPlanManager) 测试 +TEST_F(DistributedPlanManagerTest, testDpmLoadSampleDataNormal) { + dpm.loadSampleData(); + // 验证节点和边数量 + // 注意:无法直接访问graph_,通过操作验证 + EXPECT_NO_THROW(dpm.removeNode("NODE-01")); +} + +// 14. dispatchPlan 测试 +TEST_F(DispatchManagerTest, testDispatchPlanNormal) { + std::vector assets = {"ASSET-001", "ASSET-002", "ASSET-004"}; + int count = dm.dispatchPlan("PLAN-001", assets); + EXPECT_EQ(count, 2); // ASSET-001, ASSET-004 为 READY +} + +TEST_F(DispatchManagerTest, testDispatchPlanBoundaryEmptyAssets) { + std::vector assets; + int count = dm.dispatchPlan("PLAN-001", assets); + EXPECT_EQ(count, 0); +} + +// 15. getAssetStatus 测试 +TEST_F(DispatchManagerTest, testGetAssetStatusNormal) { + EXPECT_EQ(dm.getAssetStatus("ASSET-001"), AssetStatusCode::READY); + EXPECT_EQ(dm.getAssetStatus("ASSET-002"), AssetStatusCode::BUSY); +} + +TEST_F(DispatchManagerTest, testGetAssetStatusBoundaryOffline) { + EXPECT_EQ(dm.getAssetStatus("UNKNOWN"), AssetStatusCode::OFFLINE); +} + +// 16. getExecutionStats 测试 +TEST_F(DispatchManagerTest, testGetExecutionStatsNormal) { + auto stats = dm.getExecutionStats(); + EXPECT_EQ(stats.total_tasks, 5); + EXPECT_EQ(stats.completed_tasks, 1); + EXPECT_DOUBLE_EQ(stats.remaining_time, 28.5); +} + +// 17. getDashboardData 测试 +TEST_F(DispatchManagerTest, testGetDashboardDataNormal) { + auto dd = dm.getDashboardData(); + EXPECT_EQ(dd.execution.total_tasks, 5); + EXPECT_DOUBLE_EQ(dd.resource_rate, 0.72); + EXPECT_EQ(dd.recent_alerts.size(), 2); +} + +// 18. getTaskFlow 测试 +TEST_F(DispatchManagerTest, testGetTaskFlowNormal) { + auto flow = dm.getTaskFlow(); + EXPECT_EQ(flow.size(), 5); + EXPECT_EQ(flow[0].task_id, "TASK-01"); + EXPECT_EQ(flow[0].status, "completed"); +} + +// 19. getAlerts 测试 +TEST_F(DispatchManagerTest, testGetAlertsNormalNoFilter) { + auto alerts = dm.getAlerts(nullptr); + EXPECT_EQ(alerts.size(), 2); +} + +TEST_F(DispatchManagerTest, testGetAlertsBoundaryWithFilter) { + AlertLevel lvl = AlertLevel::ERROR; + auto alerts = dm.getAlerts(&lvl); + EXPECT_EQ(alerts.size(), 1); + EXPECT_EQ(alerts[0].level, AlertLevel::ERROR); +} + +// 20. clearAlerts 测试 +TEST_F(DispatchManagerTest, testClearAlertsNormal) { + dm.clearAlerts(); + auto alerts = dm.getAlerts(nullptr); + EXPECT_TRUE(alerts.empty()); +} + +// 21. loadSampleData (DispatchManager) 测试 +TEST_F(DispatchManagerTest, testDispatchLoadSampleDataNormal) { + dm.loadSampleData(); + EXPECT_EQ(dm.getAssetStatus("ASSET-001"), AssetStatusCode::READY); + EXPECT_EQ(dm.getTaskFlow().size(), 5); + EXPECT_EQ(dm.getAlerts(nullptr).size(), 2); +} \ No newline at end of file