Skip to content

Commit daf6a84

Browse files
committed
add tests for plugin entity ownership routing
Tests verify entity ownership tracking in PluginManager: registration, lookup, multi-plugin coexistence, provider resolution via add_plugin (in-process dynamic_cast), and nullptr returns for unknown entities and plugins without providers.
1 parent 40abdcb commit daf6a84

File tree

2 files changed

+181
-0
lines changed

2 files changed

+181
-0
lines changed

src/ros2_medkit_gateway/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,10 @@ if(BUILD_TESTING)
593593
ament_add_gtest(test_plugin_manager test/test_plugin_manager.cpp)
594594
target_link_libraries(test_plugin_manager gateway_lib)
595595

596+
# Plugin entity routing tests
597+
ament_add_gtest(test_plugin_entity_routing test/test_plugin_entity_routing.cpp)
598+
target_link_libraries(test_plugin_entity_routing gateway_lib)
599+
596600
# Plugin HTTP types tests
597601
ament_add_gtest(test_plugin_http_types test/test_plugin_http_types.cpp)
598602
target_link_libraries(test_plugin_http_types gateway_lib)
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
// Copyright 2026 bburda
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include <gtest/gtest.h>
16+
17+
#include "ros2_medkit_gateway/plugins/plugin_manager.hpp"
18+
#include "ros2_medkit_gateway/providers/data_provider.hpp"
19+
#include "ros2_medkit_gateway/providers/operation_provider.hpp"
20+
21+
using namespace ros2_medkit_gateway;
22+
using json = nlohmann::json;
23+
24+
// --- Mock plugin implementing DataProvider + OperationProvider ---
25+
26+
class MockDataOpPlugin : public GatewayPlugin, public DataProvider, public OperationProvider {
27+
public:
28+
std::string name() const override {
29+
return name_;
30+
}
31+
void configure(const json &) override {
32+
}
33+
void shutdown() override {
34+
}
35+
36+
// DataProvider
37+
tl::expected<json, DataProviderErrorInfo> list_data(const std::string & entity_id) override {
38+
return json{{"items", json::array({{{"id", "test_data"}, {"entity", entity_id}}})}};
39+
}
40+
tl::expected<json, DataProviderErrorInfo> read_data(const std::string &, const std::string & resource) override {
41+
return json{{"value", resource}};
42+
}
43+
tl::expected<json, DataProviderErrorInfo> write_data(const std::string &, const std::string &,
44+
const json &) override {
45+
return json{{"status", "ok"}};
46+
}
47+
48+
// OperationProvider
49+
tl::expected<json, OperationProviderErrorInfo> list_operations(const std::string & entity_id) override {
50+
return json{{"items", json::array({{{"id", "test_op"}, {"entity", entity_id}}})}};
51+
}
52+
tl::expected<json, OperationProviderErrorInfo> execute_operation(const std::string &, const std::string & op,
53+
const json &) override {
54+
return json{{"executed", op}};
55+
}
56+
57+
std::string name_ = "test_plugin";
58+
};
59+
60+
// --- Mock plugin without providers ---
61+
62+
class MockBarePlugin : public GatewayPlugin {
63+
public:
64+
std::string name() const override {
65+
return "bare_plugin";
66+
}
67+
void configure(const json &) override {
68+
}
69+
void shutdown() override {
70+
}
71+
};
72+
73+
// =============================================================================
74+
// Entity Ownership Tests
75+
// =============================================================================
76+
77+
TEST(PluginEntityRouting, UnknownEntityReturnsNullopt) {
78+
PluginManager mgr;
79+
EXPECT_FALSE(mgr.get_entity_owner("nonexistent").has_value());
80+
EXPECT_EQ(mgr.get_data_provider_for_entity("nonexistent"), nullptr);
81+
EXPECT_EQ(mgr.get_operation_provider_for_entity("nonexistent"), nullptr);
82+
}
83+
84+
TEST(PluginEntityRouting, RegisteredEntityReturnsOwner) {
85+
PluginManager mgr;
86+
mgr.register_entity_ownership("uds_gateway", {"ecu1", "ecu2"});
87+
88+
auto owner1 = mgr.get_entity_owner("ecu1");
89+
ASSERT_TRUE(owner1.has_value());
90+
EXPECT_EQ(*owner1, "uds_gateway");
91+
92+
auto owner2 = mgr.get_entity_owner("ecu2");
93+
ASSERT_TRUE(owner2.has_value());
94+
EXPECT_EQ(*owner2, "uds_gateway");
95+
96+
EXPECT_FALSE(mgr.get_entity_owner("ecu3").has_value());
97+
}
98+
99+
TEST(PluginEntityRouting, MultiplePluginsOwnDifferentEntities) {
100+
PluginManager mgr;
101+
mgr.register_entity_ownership("uds_gateway", {"ecu1"});
102+
mgr.register_entity_ownership("opcua_gateway", {"plc1"});
103+
104+
EXPECT_EQ(*mgr.get_entity_owner("ecu1"), "uds_gateway");
105+
EXPECT_EQ(*mgr.get_entity_owner("plc1"), "opcua_gateway");
106+
}
107+
108+
// =============================================================================
109+
// Provider Routing Tests (with in-process plugins via add_plugin)
110+
// =============================================================================
111+
112+
TEST(PluginEntityRouting, DataProviderResolvedForOwnedEntity) {
113+
PluginManager mgr;
114+
115+
auto plugin = std::make_unique<MockDataOpPlugin>();
116+
auto * raw = plugin.get();
117+
mgr.add_plugin(std::move(plugin));
118+
119+
// Register ownership
120+
mgr.register_entity_ownership("test_plugin", {"my_ecu"});
121+
122+
// Should resolve to the plugin's DataProvider
123+
auto * dp = mgr.get_data_provider_for_entity("my_ecu");
124+
ASSERT_NE(dp, nullptr);
125+
EXPECT_EQ(dp, static_cast<DataProvider *>(raw));
126+
127+
// Verify it actually works
128+
auto result = dp->list_data("my_ecu");
129+
ASSERT_TRUE(result.has_value());
130+
EXPECT_EQ((*result)["items"][0]["entity"], "my_ecu");
131+
}
132+
133+
TEST(PluginEntityRouting, OperationProviderResolvedForOwnedEntity) {
134+
PluginManager mgr;
135+
136+
auto plugin = std::make_unique<MockDataOpPlugin>();
137+
auto * raw = plugin.get();
138+
mgr.add_plugin(std::move(plugin));
139+
140+
mgr.register_entity_ownership("test_plugin", {"my_ecu"});
141+
142+
auto * op = mgr.get_operation_provider_for_entity("my_ecu");
143+
ASSERT_NE(op, nullptr);
144+
EXPECT_EQ(op, static_cast<OperationProvider *>(raw));
145+
146+
auto result = op->execute_operation("my_ecu", "reset", json::object());
147+
ASSERT_TRUE(result.has_value());
148+
EXPECT_EQ((*result)["executed"], "reset");
149+
}
150+
151+
TEST(PluginEntityRouting, BarePluginReturnsNullProviders) {
152+
PluginManager mgr;
153+
154+
auto plugin = std::make_unique<MockBarePlugin>();
155+
mgr.add_plugin(std::move(plugin));
156+
157+
mgr.register_entity_ownership("bare_plugin", {"entity1"});
158+
159+
// Entity is owned, but plugin doesn't implement providers
160+
auto owner = mgr.get_entity_owner("entity1");
161+
ASSERT_TRUE(owner.has_value());
162+
EXPECT_EQ(*owner, "bare_plugin");
163+
164+
EXPECT_EQ(mgr.get_data_provider_for_entity("entity1"), nullptr);
165+
EXPECT_EQ(mgr.get_operation_provider_for_entity("entity1"), nullptr);
166+
}
167+
168+
TEST(PluginEntityRouting, UnownedEntityReturnsNullProviders) {
169+
PluginManager mgr;
170+
171+
auto plugin = std::make_unique<MockDataOpPlugin>();
172+
mgr.add_plugin(std::move(plugin));
173+
174+
// Don't register ownership - entity is not owned by any plugin
175+
EXPECT_EQ(mgr.get_data_provider_for_entity("unowned"), nullptr);
176+
EXPECT_EQ(mgr.get_operation_provider_for_entity("unowned"), nullptr);
177+
}

0 commit comments

Comments
 (0)