diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..72e6990 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.10.0) +project(test_ODF) +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 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../include) + +include(CTest) +enable_testing() + +add_executable(test_ODF test_odf_manager.cpp ../app/main.py test_odf_service.cpp ../app/services.py) + +target_link_libraries(test_ODF gtest gmock gtest_main) +include(GoogleTest) +gtest_discover_tests(test_ODF) \ No newline at end of file diff --git a/tests/test_odf_manager.cpp b/tests/test_odf_manager.cpp new file mode 100644 index 0000000..5595991 --- /dev/null +++ b/tests/test_odf_manager.cpp @@ -0,0 +1,418 @@ +#include +#include +#include +#include +#include +#include + +// ==================== 模拟数据结构 ==================== + +enum class PortType { SC, LC, FC, ST }; + +struct RackCreate { + std::string rack_name; + std::string location; +}; + +struct UnitCreate { + int unit_number; + std::string unit_name; + int position; + int port_count; + PortType port_type; +}; + +struct ConnectionCreate { + std::string port_a_id; + std::string port_b_id; + double fiber_length; + std::string remark; +}; + +struct RackListResponse { + std::string rack_id; + std::string rack_name; + int unit_count; +}; + +struct PortResponse { + std::string port_id; + int port_number; + PortType port_type; + std::string status; +}; + +struct UnitResponse { + std::string unit_id; + int unit_number; + std::string unit_name; + int position; + std::vector ports; +}; + +struct RackResponse { + std::string rack_id; + std::string rack_name; + std::string location; + std::vector units; +}; + +struct FreePortResponse { + std::string port_id; + std::string rack_id; + int unit_number; + int port_number; + PortType port_type; +}; + +struct PortDetail { + std::string port_id; + int port_number; + PortType port_type; + std::string status; + std::string unit_id; + std::string rack_id; +}; + +struct ConnectionResponse { + std::string connection_id; + std::string port_a_id; + std::string port_b_id; + double fiber_length; + std::string remark; +}; + +// ==================== 被测服务类(模拟实现) ==================== + +class ODFService { +public: + virtual ~ODFService() = default; + + virtual std::vector list_racks() = 0; + virtual std::optional get_rack(const std::string& rack_id) = 0; + virtual RackResponse create_rack(const RackCreate& body) = 0; + virtual bool delete_rack(const std::string& rack_id) = 0; + virtual std::optional create_unit(const std::string& rack_id, const UnitCreate& body) = 0; + virtual bool delete_unit(const std::string& unit_id) = 0; + virtual std::vector get_free_ports(const std::optional& rack_id) = 0; + virtual std::optional get_port_detail(const std::string& port_id) = 0; + virtual std::vector list_connections() = 0; + virtual std::optional create_connection(const ConnectionCreate& body) = 0; + virtual bool delete_connection(const std::string& connection_id) = 0; + virtual std::optional get_port_path(const std::string& port_id) = 0; + virtual std::string health_check() = 0; +}; + +// 模拟实现(用于测试基类) +class MockODFService : public ODFService { +public: + MOCK_METHOD(std::vector, list_racks, (), (override)); + MOCK_METHOD(std::optional, get_rack, (const std::string& rack_id), (override)); + MOCK_METHOD(RackResponse, create_rack, (const RackCreate& body), (override)); + MOCK_METHOD(bool, delete_rack, (const std::string& rack_id), (override)); + MOCK_METHOD(std::optional, create_unit, (const std::string& rack_id, const UnitCreate& body), (override)); + MOCK_METHOD(bool, delete_unit, (const std::string& unit_id), (override)); + MOCK_METHOD(std::vector, get_free_ports, (const std::optional& rack_id), (override)); + MOCK_METHOD(std::optional, get_port_detail, (const std::string& port_id), (override)); + MOCK_METHOD(std::vector, list_connections, (), (override)); + MOCK_METHOD(std::optional, create_connection, (const ConnectionCreate& body), (override)); + MOCK_METHOD(bool, delete_connection, (const std::string& connection_id), (override)); + MOCK_METHOD(std::optional, get_port_path, (const std::string& port_id), (override)); + MOCK_METHOD(std::string, health_check, (), (override)); +}; + +// ==================== 测试夹具 ==================== + +class ODFServiceTest : public ::testing::Test { +protected: + void SetUp() override { + mock_service = std::make_shared(); + } + + void TearDown() override { + mock_service.reset(); + } + + std::shared_ptr mock_service; +}; + +// ==================== 机架 API 测试 ==================== + +TEST_F(ODFServiceTest, testListRacksNormal) { + std::vector expected = { + {"rack1", "Rack A", 3}, + {"rack2", "Rack B", 0} + }; + EXPECT_CALL(*mock_service, list_racks()) + .WillOnce(::testing::Return(expected)); + + auto result = mock_service->list_racks(); + ASSERT_EQ(result.size(), 2); + EXPECT_EQ(result[0].rack_id, "rack1"); + EXPECT_EQ(result[1].unit_count, 0); +} + +TEST_F(ODFServiceTest, testListRacksEmpty) { + EXPECT_CALL(*mock_service, list_racks()) + .WillOnce(::testing::Return(std::vector{})); + + auto result = mock_service->list_racks(); + EXPECT_TRUE(result.empty()); +} + +TEST_F(ODFServiceTest, testGetRackExists) { + RackResponse rack; + rack.rack_id = "rack1"; + rack.rack_name = "Main Rack"; + EXPECT_CALL(*mock_service, get_rack("rack1")) + .WillOnce(::testing::Return(std::optional(rack))); + + auto result = mock_service->get_rack("rack1"); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->rack_id, "rack1"); +} + +TEST_F(ODFServiceTest, testGetRackNotFound) { + EXPECT_CALL(*mock_service, get_rack("invalid")) + .WillOnce(::testing::Return(std::nullopt)); + + auto result = mock_service->get_rack("invalid"); + EXPECT_FALSE(result.has_value()); +} + +TEST_F(ODFServiceTest, testCreateRackNormal) { + RackCreate body{"New Rack", "Room 101"}; + RackResponse expected; + expected.rack_id = "rack_new"; + expected.rack_name = "New Rack"; + expected.location = "Room 101"; + + EXPECT_CALL(*mock_service, create_rack(body)) + .WillOnce(::testing::Return(expected)); + + auto result = mock_service->create_rack(body); + EXPECT_EQ(result.rack_name, "New Rack"); + EXPECT_EQ(result.location, "Room 101"); +} + +TEST_F(ODFServiceTest, testCreateRackEmptyName) { + RackCreate body{"", "Location"}; + EXPECT_CALL(*mock_service, create_rack(body)) + .WillOnce(::testing::Throw(std::invalid_argument("rack_name cannot be empty"))); + + EXPECT_THROW(mock_service->create_rack(body), std::invalid_argument); +} + +TEST_F(ODFServiceTest, testDeleteRackSuccess) { + EXPECT_CALL(*mock_service, delete_rack("rack1")) + .WillOnce(::testing::Return(true)); + + EXPECT_TRUE(mock_service->delete_rack("rack1")); +} + +TEST_F(ODFServiceTest, testDeleteRackNotFound) { + EXPECT_CALL(*mock_service, delete_rack("ghost")) + .WillOnce(::testing::Return(false)); + + EXPECT_FALSE(mock_service->delete_rack("ghost")); +} + +// ==================== 配线单元 API 测试 ==================== + +TEST_F(ODFServiceTest, testCreateUnitNormal) { + UnitCreate body{1, "Unit 1", 1, 12, PortType::SC}; + UnitResponse unit; + unit.unit_id = "unit1"; + unit.unit_number = 1; + unit.unit_name = "Unit 1"; + unit.position = 1; + unit.ports = std::vector(12); + + EXPECT_CALL(*mock_service, create_unit("rack1", body)) + .WillOnce(::testing::Return(std::optional(unit))); + + auto result = mock_service->create_unit("rack1", body); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->unit_number, 1); + EXPECT_EQ(result->ports.size(), 12); +} + +TEST_F(ODFServiceTest, testCreateUnitRackNotFound) { + UnitCreate body{1, "Unit 1", 1, 12, PortType::LC}; + EXPECT_CALL(*mock_service, create_unit("bad_rack", body)) + .WillOnce(::testing::Return(std::nullopt)); + + auto result = mock_service->create_unit("bad_rack", body); + EXPECT_FALSE(result.has_value()); +} + +TEST_F(ODFServiceTest, testCreateUnitInvalidPortCount) { + UnitCreate body{1, "Unit 1", 1, 0, PortType::FC}; + EXPECT_CALL(*mock_service, create_unit("rack1", body)) + .WillOnce(::testing::Throw(std::invalid_argument("port_count must be positive"))); + + EXPECT_THROW(mock_service->create_unit("rack1", body), std::invalid_argument); +} + +TEST_F(ODFServiceTest, testDeleteUnitSuccess) { + EXPECT_CALL(*mock_service, delete_unit("unit1")) + .WillOnce(::testing::Return(true)); + + EXPECT_TRUE(mock_service->delete_unit("unit1")); +} + +TEST_F(ODFServiceTest, testDeleteUnitNotFound) { + EXPECT_CALL(*mock_service, delete_unit("unknown")) + .WillOnce(::testing::Return(false)); + + EXPECT_FALSE(mock_service->delete_unit("unknown")); +} + +// ==================== 端口 API 测试 ==================== + +TEST_F(ODFServiceTest, testGetFreePortsAll) { + std::vector ports = { + {"port1", "rack1", 1, 1, PortType::SC}, + {"port2", "rack1", 1, 2, PortType::SC} + }; + EXPECT_CALL(*mock_service, get_free_ports(std::nullopt)) + .WillOnce(::testing::Return(ports)); + + auto result = mock_service->get_free_ports(std::nullopt); + ASSERT_EQ(result.size(), 2); + EXPECT_EQ(result[0].port_id, "port1"); +} + +TEST_F(ODFServiceTest, testGetFreePortsFilterByRack) { + std::vector ports = { + {"port3", "rack2", 2, 1, PortType::LC} + }; + EXPECT_CALL(*mock_service, get_free_ports(std::optional("rack2"))) + .WillOnce(::testing::Return(ports)); + + auto result = mock_service->get_free_ports("rack2"); + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result[0].rack_id, "rack2"); +} + +TEST_F(ODFServiceTest, testGetFreePortsRackNotFound) { + EXPECT_CALL(*mock_service, get_free_ports(std::optional("ghost"))) + .WillOnce(::testing::Return(std::vector{})); + + auto result = mock_service->get_free_ports("ghost"); + EXPECT_TRUE(result.empty()); +} + +TEST_F(ODFServiceTest, testGetPortDetailExists) { + PortDetail detail{"port1", 1, PortType::SC, "FREE", "unit1", "rack1"}; + EXPECT_CALL(*mock_service, get_port_detail("port1")) + .WillOnce(::testing::Return(std::optional(detail))); + + auto result = mock_service->get_port_detail("port1"); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->status, "FREE"); + EXPECT_EQ(result->rack_id, "rack1"); +} + +TEST_F(ODFServiceTest, testGetPortDetailNotFound) { + EXPECT_CALL(*mock_service, get_port_detail("bad_port")) + .WillOnce(::testing::Return(std::nullopt)); + + auto result = mock_service->get_port_detail("bad_port"); + EXPECT_FALSE(result.has_value()); +} + +// ==================== 跳接连接 API 测试 ==================== + +TEST_F(ODFServiceTest, testListConnectionsNormal) { + std::vector conns = { + {"conn1", "port1", "port2", 10.5, "test"} + }; + EXPECT_CALL(*mock_service, list_connections()) + .WillOnce(::testing::Return(conns)); + + auto result = mock_service->list_connections(); + ASSERT_EQ(result.size(), 1); + EXPECT_EQ(result[0].connection_id, "conn1"); +} + +TEST_F(ODFServiceTest, testListConnectionsEmpty) { + EXPECT_CALL(*mock_service, list_connections()) + .WillOnce(::testing::Return(std::vector{})); + + auto result = mock_service->list_connections(); + EXPECT_TRUE(result.empty()); +} + +TEST_F(ODFServiceTest, testCreateConnectionNormal) { + ConnectionCreate body{"port1", "port2", 5.0, "link"}; + ConnectionResponse conn{"conn1", "port1", "port2", 5.0, "link"}; + EXPECT_CALL(*mock_service, create_connection(body)) + .WillOnce(::testing::Return(std::optional(conn))); + + auto result = mock_service->create_connection(body); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->fiber_length, 5.0); +} + +TEST_F(ODFServiceTest, testCreateConnectionSamePort) { + ConnectionCreate body{"port1", "port1", 1.0, ""}; + EXPECT_CALL(*mock_service, create_connection(body)) + .WillOnce(::testing::Return(std::nullopt)); + + auto result = mock_service->create_connection(body); + EXPECT_FALSE(result.has_value()); +} + +TEST_F(ODFServiceTest, testCreateConnectionPortNotFree) { + ConnectionCreate body{"port1", "port3", 2.0, ""}; + EXPECT_CALL(*mock_service, create_connection(body)) + .WillOnce(::testing::Return(std::nullopt)); + + auto result = mock_service->create_connection(body); + EXPECT_FALSE(result.has_value()); +} + +TEST_F(ODFServiceTest, testDeleteConnectionSuccess) { + EXPECT_CALL(*mock_service, delete_connection("conn1")) + .WillOnce(::testing::Return(true)); + + EXPECT_TRUE(mock_service->delete_connection("conn1")); +} + +TEST_F(ODFServiceTest, testDeleteConnectionNotFound) { + EXPECT_CALL(*mock_service, delete_connection("bad_conn")) + .WillOnce(::testing::Return(false)); + + EXPECT_FALSE(mock_service->delete_connection("bad_conn")); +} + +// ==================== 路径查询 API 测试 ==================== + +TEST_F(ODFServiceTest, testGetPortPathExists) { + std::string path_info = "port1 -> conn1 -> port2"; + EXPECT_CALL(*mock_service, get_port_path("port1")) + .WillOnce(::testing::Return(std::optional(path_info))); + + auto result = mock_service->get_port_path("port1"); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(*result, path_info); +} + +TEST_F(ODFServiceTest, testGetPortPathNotFound) { + EXPECT_CALL(*mock_service, get_port_path("unknown")) + .WillOnce(::testing::Return(std::nullopt)); + + auto result = mock_service->get_port_path("unknown"); + EXPECT_FALSE(result.has_value()); +} + +// ==================== 健康检查 API 测试 ==================== + +TEST_F(ODFServiceTest, testHealthCheck) { + EXPECT_CALL(*mock_service, health_check()) + .WillOnce(::testing::Return("{\"status\": \"ok\"}")); + + auto result = mock_service->health_check(); + EXPECT_NE(result.find("ok"), std::string::npos); +} diff --git a/tests/test_odf_service.cpp b/tests/test_odf_service.cpp new file mode 100644 index 0000000..c050fea --- /dev/null +++ b/tests/test_odf_service.cpp @@ -0,0 +1,259 @@ +#include +#include "odf_service.h" +#include +#include + +using namespace odf; // 假设模型和服务在 odf 命名空间 + +class OdfServiceTest : public ::testing::Test { +protected: + void SetUp() override { + service = std::make_unique(); + } + + void TearDown() override { + service.reset(); + } + + std::unique_ptr service; +}; + +// 测试构造函数和演示数据初始化 +TEST_F(OdfServiceTest, ConstructorInitializesDemoData) { + // 验证初始化后机架数量为2 + auto racks = service->list_racks(); + EXPECT_EQ(racks.size(), 2); + EXPECT_EQ(racks[0]["rack_name"], "ODF-001"); + EXPECT_EQ(racks[1]["rack_name"], "ODF-002"); + + // 验证机架1包含2个单元 + auto rack1 = service->get_rack("rack-001"); + ASSERT_NE(rack1, nullptr); + EXPECT_EQ(rack1->units.size(), 2); + + // 验证存在一条示例连接 + auto connections = service->list_connections(); + EXPECT_EQ(connections.size(), 1); +} + +// 测试 list_racks 正常返回 +TEST_F(OdfServiceTest, ListRacksReturnsAllRacks) { + auto racks = service->list_racks(); + EXPECT_GE(racks.size(), 2); + for (const auto& rack : racks) { + EXPECT_TRUE(rack.find("rack_id") != rack.end()); + EXPECT_TRUE(rack.find("rack_name") != rack.end()); + EXPECT_TRUE(rack.find("location") != rack.end()); + EXPECT_TRUE(rack.find("unit_count") != rack.end()); + } +} + +// 测试 get_rack 存在和不存在的情况 +TEST_F(OdfServiceTest, GetRackValidIdReturnsRack) { + auto rack = service->get_rack("rack-001"); + ASSERT_NE(rack, nullptr); + EXPECT_EQ(rack->rack_name, "ODF-001"); + EXPECT_EQ(rack->location, "A机房-1列-01排"); + EXPECT_EQ(rack->units.size(), 2); +} + +TEST_F(OdfServiceTest, GetRackInvalidIdReturnsNull) { + auto rack = service->get_rack("non-existent"); + EXPECT_EQ(rack, nullptr); +} + +// 测试 create_rack 正常创建 +TEST_F(OdfServiceTest, CreateRackReturnsNewRack) { + auto rack = service->create_rack("ODF-003", "B机房-2列-03排"); + ASSERT_NE(rack, nullptr); + EXPECT_EQ(rack->rack_name, "ODF-003"); + EXPECT_EQ(rack->location, "B机房-2列-03排"); + EXPECT_TRUE(rack->units.empty()); + + // 验证机架已加入列表 + auto racks = service->list_racks(); + EXPECT_EQ(racks.size(), 3); +} + +// 测试 delete_rack 存在和不存在的情况 +TEST_F(OdfServiceTest, DeleteRackExistingReturnsTrue) { + EXPECT_TRUE(service->delete_rack("rack-001")); + EXPECT_EQ(service->get_rack("rack-001"), nullptr); + // 验证关联的单元和端口也被删除 + EXPECT_EQ(service->get_free_ports("rack-001").size(), 0); +} + +TEST_F(OdfServiceTest, DeleteRackNonExistentReturnsFalse) { + EXPECT_FALSE(service->delete_rack("ghost-rack")); +} + +// 测试 create_unit 正常和机架不存在 +TEST_F(OdfServiceTest, CreateUnitInExistingRackReturnsUnit) { + auto unit = service->create_unit("rack-001", 3, "测试单元", "中间", 8, PortType::LC); + ASSERT_NE(unit, nullptr); + EXPECT_EQ(unit->unit_number, 3); + EXPECT_EQ(unit->ports.size(), 8); + // 验证机架单元数增加 + auto rack = service->get_rack("rack-001"); + EXPECT_EQ(rack->units.size(), 3); +} + +TEST_F(OdfServiceTest, CreateUnitInNonExistentRackReturnsNull) { + auto unit = service->create_unit("bad-rack", 1, "测试", "上", 4, PortType::SC); + EXPECT_EQ(unit, nullptr); +} + +// 测试 delete_unit 存在和不存在 +TEST_F(OdfServiceTest, DeleteUnitExistingReturnsTrue) { + // 获取一个已存在的单元ID + auto rack = service->get_rack("rack-001"); + ASSERT_FALSE(rack->units.empty()); + std::string unit_id = rack->units[0].unit_id; + EXPECT_TRUE(service->delete_unit(unit_id)); + // 验证单元已移除 + EXPECT_EQ(service->get_rack("rack-001")->units.size(), 1); +} + +TEST_F(OdfServiceTest, DeleteUnitNonExistentReturnsFalse) { + EXPECT_FALSE(service->delete_unit("fake-unit-id")); +} + +// 测试 get_free_ports 无过滤和按机架过滤 +TEST_F(OdfServiceTest, GetFreePortsReturnsAllFreePorts) { + auto ports = service->get_free_ports(); + // 演示数据中除了一条连接的两个端口外,其余都是空闲 + EXPECT_GT(ports.size(), 0); + for (const auto& p : ports) { + EXPECT_EQ(p["status"], "FREE"); + } +} + +TEST_F(OdfServiceTest, GetFreePortsFilterByRack) { + auto ports = service->get_free_ports("rack-002"); + EXPECT_GT(ports.size(), 0); + for (const auto& p : ports) { + EXPECT_EQ(p["rack_id"], "rack-002"); + } +} + +TEST_F(OdfServiceTest, GetFreePortsNonExistentRackReturnsEmpty) { + auto ports = service->get_free_ports("no-rack"); + EXPECT_TRUE(ports.empty()); +} + +// 测试 get_port_detail 存在和不存在 +TEST_F(OdfServiceTest, GetPortDetailValidIdReturnsInfo) { + // 获取一个已知空闲端口 + auto free_ports = service->get_free_ports(); + ASSERT_FALSE(free_ports.empty()); + std::string port_id = free_ports[0]["port_id"]; + auto detail = service->get_port_detail(port_id); + ASSERT_TRUE(detail.has_value()); + EXPECT_EQ(detail->at("port_id"), port_id); + EXPECT_EQ(detail->at("status"), "FREE"); +} + +TEST_F(OdfServiceTest, GetPortDetailInvalidIdReturnsNullOpt) { + auto detail = service->get_port_detail("bad-port"); + EXPECT_FALSE(detail.has_value()); +} + +// 测试 create_connection 正常、端口不存在、端口繁忙、相同端口 +TEST_F(OdfServiceTest, CreateConnectionWithFreePortsSucceeds) { + auto free_ports = service->get_free_ports(); + ASSERT_GE(free_ports.size(), 2); + std::string port_a = free_ports[0]["port_id"]; + std::string port_b = free_ports[1]["port_id"]; + auto conn = service->create_connection(port_a, port_b, 10.0, "测试连接"); + ASSERT_NE(conn, nullptr); + EXPECT_EQ(conn->fiber_length, 10.0); + EXPECT_EQ(conn->remark, "测试连接"); + // 验证端口状态变为 IN_USE + auto detail_a = service->get_port_detail(port_a); + EXPECT_EQ(detail_a->at("status"), "IN_USE"); + auto detail_b = service->get_port_detail(port_b); + EXPECT_EQ(detail_b->at("status"), "IN_USE"); +} + +TEST_F(OdfServiceTest, CreateConnectionWithNonExistentPortReturnsNull) { + auto free_ports = service->get_free_ports(); + ASSERT_FALSE(free_ports.empty()); + std::string port_a = free_ports[0]["port_id"]; + auto conn = service->create_connection(port_a, "ghost-port", 5.0, ""); + EXPECT_EQ(conn, nullptr); +} + +TEST_F(OdfServiceTest, CreateConnectionWithBusyPortReturnsNull) { + // 获取已使用的端口(演示数据中已有一条连接) + auto connections = service->list_connections(); + ASSERT_FALSE(connections.empty()); + std::string busy_port = connections[0].port_a_id; + auto free_ports = service->get_free_ports(); + ASSERT_FALSE(free_ports.empty()); + std::string free_port = free_ports[0]["port_id"]; + auto conn = service->create_connection(busy_port, free_port, 1.0, ""); + EXPECT_EQ(conn, nullptr); +} + +TEST_F(OdfServiceTest, CreateConnectionSamePortReturnsNull) { + auto free_ports = service->get_free_ports(); + ASSERT_FALSE(free_ports.empty()); + std::string port = free_ports[0]["port_id"]; + auto conn = service->create_connection(port, port, 1.0, ""); + EXPECT_EQ(conn, nullptr); +} + +// 测试 list_connections +TEST_F(OdfServiceTest, ListConnectionsReturnsAllConnections) { + auto connections = service->list_connections(); + EXPECT_EQ(connections.size(), 1); // 演示数据有一条 + // 新增一条 + auto free_ports = service->get_free_ports(); + ASSERT_GE(free_ports.size(), 2); + service->create_connection(free_ports[0]["port_id"], free_ports[1]["port_id"], 2.0, ""); + connections = service->list_connections(); + EXPECT_EQ(connections.size(), 2); +} + +// 测试 delete_connection 存在和不存在 +TEST_F(OdfServiceTest, DeleteConnectionExistingReturnsTrue) { + auto connections = service->list_connections(); + ASSERT_FALSE(connections.empty()); + std::string conn_id = connections[0].connection_id; + EXPECT_TRUE(service->delete_connection(conn_id)); + EXPECT_TRUE(service->list_connections().empty()); + // 验证端口恢复空闲 + auto detail_a = service->get_port_detail(connections[0].port_a_id); + EXPECT_EQ(detail_a->at("status"), "FREE"); +} + +TEST_F(OdfServiceTest, DeleteConnectionNonExistentReturnsFalse) { + EXPECT_FALSE(service->delete_connection("no-conn")); +} + +// 测试 get_port_path 存在和不存在 +TEST_F(OdfServiceTest, GetPortPathForConnectedPortReturnsPath) { + auto connections = service->list_connections(); + ASSERT_FALSE(connections.empty()); + std::string port_id = connections[0].port_a_id; + auto path = service->get_port_path(port_id); + ASSERT_TRUE(path.has_value()); + EXPECT_EQ(path->at("port")["port_id"], port_id); + EXPECT_TRUE(path->at("connection").has_value()); + EXPECT_TRUE(path->at("connected_port").has_value()); +} + +TEST_F(OdfServiceTest, GetPortPathForFreePortReturnsNoConnection) { + auto free_ports = service->get_free_ports(); + ASSERT_FALSE(free_ports.empty()); + std::string port_id = free_ports[0]["port_id"]; + auto path = service->get_port_path(port_id); + ASSERT_TRUE(path.has_value()); + EXPECT_FALSE(path->at("connection").has_value()); + EXPECT_FALSE(path->at("connected_port").has_value()); +} + +TEST_F(OdfServiceTest, GetPortPathInvalidPortReturnsNullOpt) { + auto path = service->get_port_path("invalid"); + EXPECT_FALSE(path.has_value()); +}