1 #include "dbus_environment.hpp"
2 #include "helpers.hpp"
3 #include "mocks/json_storage_mock.hpp"
4 #include "mocks/trigger_factory_mock.hpp"
5 #include "mocks/trigger_mock.hpp"
6 #include "params/trigger_params.hpp"
7 #include "trigger.hpp"
8 #include "trigger_manager.hpp"
9 #include "utils/conversion_trigger.hpp"
10 #include "utils/transform.hpp"
11 
12 using namespace testing;
13 using sdbusplus::message::object_path;
14 
15 class TestTriggerManager : public Test
16 {
17   public:
18     std::pair<boost::system::error_code, std::string>
19         addTrigger(const TriggerParams& params)
20     {
21         const auto sensorInfos =
22             utils::fromLabeledSensorsInfo(params.sensors());
23 
24         std::promise<std::pair<boost::system::error_code, std::string>>
25             addTriggerPromise;
26         DbusEnvironment::getBus()->async_method_call(
27             [&addTriggerPromise](boost::system::error_code ec,
28                                  const std::string& path) {
29                 addTriggerPromise.set_value({ec, path});
30             },
31             DbusEnvironment::serviceName(), TriggerManager::triggerManagerPath,
32             TriggerManager::triggerManagerIfaceName, "AddTrigger", params.id(),
33             params.name(),
34             utils::transform(
35                 params.triggerActions(),
36                 [](const auto& action) { return actionToString(action); }),
37             sensorInfos, params.reports(),
38             std::visit(utils::FromLabeledThresholdParamConversion(),
39                        params.thresholdParams()));
40         return DbusEnvironment::waitForFuture(addTriggerPromise.get_future());
41     }
42 
43     std::unique_ptr<TriggerManager> makeTriggerManager()
44     {
45         return std::make_unique<TriggerManager>(
46             std::move(triggerFactoryMockPtr), std::move(storageMockPtr),
47             DbusEnvironment::getObjServer());
48     }
49 
50     void SetUp() override
51     {
52         sut = makeTriggerManager();
53     }
54 
55     std::unique_ptr<StorageMock> storageMockPtr =
56         std::make_unique<NiceMock<StorageMock>>();
57     StorageMock& storageMock = *storageMockPtr;
58     std::unique_ptr<TriggerFactoryMock> triggerFactoryMockPtr =
59         std::make_unique<NiceMock<TriggerFactoryMock>>();
60     TriggerFactoryMock& triggerFactoryMock = *triggerFactoryMockPtr;
61     std::unique_ptr<TriggerMock> triggerMockPtr =
62         std::make_unique<NiceMock<TriggerMock>>(TriggerParams().id());
63     TriggerMock& triggerMock = *triggerMockPtr;
64     std::unique_ptr<TriggerManager> sut;
65     MockFunction<void(std::string)> checkPoint;
66 };
67 
68 TEST_F(TestTriggerManager, addTrigger)
69 {
70     triggerFactoryMock.expectMake(TriggerParams(), Ref(*sut), Ref(storageMock))
71         .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
72 
73     auto [ec, path] = addTrigger(TriggerParams());
74     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
75     EXPECT_THAT(path, Eq(triggerMock.getPath()));
76 }
77 
78 TEST_F(TestTriggerManager, addTriggerWithDiscreteThresholds)
79 {
80     TriggerParams triggerParamsDiscrete;
81     auto thresholds = std::vector<discrete::LabeledThresholdParam>{
82         {"discrete_threshold1", discrete::Severity::ok, 10, "11.0"},
83         {"discrete_threshold2", discrete::Severity::warning, 10, "12.0"},
84         {"discrete_threshold3", discrete::Severity::critical, 10, "13.0"}};
85 
86     triggerParamsDiscrete.thresholdParams(thresholds);
87 
88     auto [ec, path] = addTrigger(triggerParamsDiscrete);
89     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
90     EXPECT_THAT(path, Eq(triggerMock.getPath()));
91 }
92 
93 TEST_F(TestTriggerManager, addDiscreteTriggerWithoutThresholds)
94 {
95     TriggerParams triggerParamsDiscrete;
96     auto thresholds = std::vector<discrete::LabeledThresholdParam>();
97 
98     triggerParamsDiscrete.thresholdParams(thresholds);
99 
100     auto [ec, path] = addTrigger(triggerParamsDiscrete);
101     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
102     EXPECT_THAT(path, Eq(triggerMock.getPath()));
103 }
104 
105 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerTwice)
106 {
107     triggerFactoryMock.expectMake(TriggerParams(), Ref(*sut), Ref(storageMock))
108         .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
109 
110     addTrigger(TriggerParams());
111 
112     auto [ec, path] = addTrigger(TriggerParams());
113     EXPECT_THAT(ec.value(), Eq(boost::system::errc::file_exists));
114     EXPECT_THAT(path, Eq(std::string()));
115 }
116 
117 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerWithInvalidId)
118 {
119     triggerFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
120         .Times(0);
121 
122     auto [ec, path] = addTrigger(TriggerParams().id("not valid?"));
123     EXPECT_THAT(ec.value(), Eq(boost::system::errc::invalid_argument));
124     EXPECT_THAT(path, Eq(std::string()));
125 }
126 
127 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerWithDuplicatesInReportsIds)
128 {
129     triggerFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
130         .Times(0);
131 
132     auto [ec, path] = addTrigger(
133         TriggerParams().reportIds({"trigger1", "trigger2", "trigger1"}));
134     EXPECT_THAT(ec.value(), Eq(boost::system::errc::invalid_argument));
135     EXPECT_THAT(path, Eq(std::string()));
136 }
137 
138 TEST_F(TestTriggerManager, addTriggerWithProperReportPaths)
139 {
140     auto [ec, path] = addTrigger(TriggerParams().reports(
141         {object_path("/xyz/openbmc_project/Telemetry/Reports/MyReport"),
142          object_path(
143              "/xyz/openbmc_project/Telemetry/Reports/MyPrefix/MyReport")}));
144     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
145     EXPECT_THAT(path, Eq(triggerMock.getPath()));
146 }
147 
148 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerWithBadReportsPath)
149 {
150     triggerFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
151         .Times(0);
152 
153     auto [ec, path] = addTrigger(TriggerParams().reports(
154         {object_path("/xyz/openbmc_project/Telemetry/NotReports/MyReport")}));
155     EXPECT_THAT(ec.value(), Eq(boost::system::errc::invalid_argument));
156     EXPECT_THAT(path, Eq(std::string()));
157 }
158 
159 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerWithTooManyReportPrefixes)
160 {
161     triggerFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
162         .Times(0);
163 
164     auto [ec, path] = addTrigger(TriggerParams().reports({object_path(
165         "/xyz/openbmc_project/Telemetry/Reports/P1/P2/MyReport")}));
166     EXPECT_THAT(ec.value(), Eq(boost::system::errc::invalid_argument));
167     EXPECT_THAT(path, Eq(std::string()));
168 }
169 
170 TEST_F(TestTriggerManager, addTriggerWithoutIdAndName)
171 {
172     triggerFactoryMock
173         .expectMake(TriggerParams()
174                         .id(TriggerManager::triggerNameDefault)
175                         .name(TriggerManager::triggerNameDefault),
176                     Ref(*sut), Ref(storageMock))
177         .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
178 
179     auto [ec, path] = addTrigger(TriggerParams().id("").name(""));
180     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
181     EXPECT_THAT(path, Not(Eq("")));
182 }
183 
184 TEST_F(TestTriggerManager, addTriggerWithPrefixId)
185 {
186     triggerFactoryMock
187         .expectMake(TriggerParams()
188                         .id("TelemetryService/HackyName")
189                         .name("Hacky/Name!@#$"),
190                     Ref(*sut), Ref(storageMock))
191         .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
192 
193     auto [ec, path] = addTrigger(
194         TriggerParams().id("TelemetryService/").name("Hacky/Name!@#$"));
195     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
196     EXPECT_THAT(path, Not(Eq("")));
197 }
198 
199 TEST_F(TestTriggerManager, addTriggerWithoutIdTwice)
200 {
201     addTrigger(TriggerParams().id(""));
202 
203     auto [ec, path] = addTrigger(TriggerParams().id(""));
204     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
205     EXPECT_THAT(path, Not(Eq("")));
206 }
207 
208 TEST_F(TestTriggerManager, addTriggerWithoutIdAndWithLongNameTwice)
209 {
210     addTrigger(TriggerParams().id("").name(
211         std::string(2 * TriggerManager::maxTriggerIdLength, 'z')));
212 
213     auto [ec, path] = addTrigger(TriggerParams().id("").name(
214         std::string(2 * TriggerManager::maxTriggerIdLength, 'z')));
215     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
216     EXPECT_THAT(path, Not(Eq("")));
217 }
218 
219 TEST_F(TestTriggerManager, addTriggerWithMaxLengthId)
220 {
221     auto triggerId = std::string(TriggerManager::maxTriggerIdLength, 'z');
222     auto triggerParams = TriggerParams().id(triggerId);
223 
224     triggerFactoryMock.expectMake(triggerParams, Ref(*sut), Ref(storageMock))
225         .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
226 
227     auto [ec, path] = addTrigger(triggerParams);
228 
229     EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
230     EXPECT_THAT(path, Eq(triggerMock.getPath()));
231 }
232 
233 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerWithTooLongId)
234 {
235     auto triggerId = std::string(TriggerManager::maxTriggerIdLength + 1, 'z');
236     auto triggerParams = TriggerParams().id(triggerId);
237 
238     triggerFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
239         .Times(0);
240 
241     auto [ec, path] = addTrigger(triggerParams);
242 
243     EXPECT_THAT(ec.value(), Eq(boost::system::errc::invalid_argument));
244     EXPECT_THAT(path, Eq(std::string()));
245 }
246 
247 TEST_F(TestTriggerManager, DISABLED_failToAddTriggerWhenMaxTriggerIsReached)
248 {
249     auto triggerParams = TriggerParams();
250 
251     triggerFactoryMock.expectMake(std::nullopt, Ref(*sut), Ref(storageMock))
252         .Times(TriggerManager::maxTriggers);
253 
254     for (size_t i = 0; i < TriggerManager::maxTriggers; i++)
255     {
256         triggerParams.id(TriggerParams().id() + std::to_string(i));
257 
258         auto [ec, path] = addTrigger(triggerParams);
259         EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
260     }
261 
262     triggerParams.id(TriggerParams().id() +
263                      std::to_string(TriggerManager::maxTriggers));
264     auto [ec, path] = addTrigger(triggerParams);
265     EXPECT_THAT(ec.value(), Eq(boost::system::errc::too_many_files_open));
266     EXPECT_THAT(path, Eq(std::string()));
267 }
268 
269 TEST_F(TestTriggerManager, removeTrigger)
270 {
271     {
272         InSequence seq;
273         triggerFactoryMock
274             .expectMake(TriggerParams(), Ref(*sut), Ref(storageMock))
275             .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
276         EXPECT_CALL(triggerMock, Die());
277         EXPECT_CALL(checkPoint, Call("end"));
278     }
279 
280     addTrigger(TriggerParams());
281     sut->removeTrigger(&triggerMock);
282     checkPoint.Call("end");
283 }
284 
285 TEST_F(TestTriggerManager, removingTriggerThatIsNotInContainerHasNoEffect)
286 {
287     {
288         InSequence seq;
289         EXPECT_CALL(checkPoint, Call("end"));
290         EXPECT_CALL(triggerMock, Die());
291     }
292 
293     sut->removeTrigger(&triggerMock);
294     checkPoint.Call("end");
295 }
296 
297 TEST_F(TestTriggerManager, removingSameTriggerTwiceHasNoSideEffect)
298 {
299     {
300         InSequence seq;
301         triggerFactoryMock
302             .expectMake(TriggerParams(), Ref(*sut), Ref(storageMock))
303             .WillOnce(Return(ByMove(std::move(triggerMockPtr))));
304         EXPECT_CALL(triggerMock, Die());
305         EXPECT_CALL(checkPoint, Call("end"));
306     }
307 
308     addTrigger(TriggerParams());
309     sut->removeTrigger(&triggerMock);
310     sut->removeTrigger(&triggerMock);
311     checkPoint.Call("end");
312 }
313 class TestTriggerManagerStorage : public TestTriggerManager
314 {
315   public:
316     using FilePath = interfaces::JsonStorage::FilePath;
317     using DirectoryPath = interfaces::JsonStorage::DirectoryPath;
318 
319     void SetUp() override
320     {
321         ON_CALL(storageMock, list())
322             .WillByDefault(Return(std::vector<FilePath>{
323                 {FilePath("trigger1")}, {FilePath("trigger2")}}));
324 
325         ON_CALL(storageMock, load(FilePath("trigger1")))
326             .WillByDefault(InvokeWithoutArgs([this] { return data1; }));
327 
328         data2["Id"] = "Trigger2";
329         data2["Name"] = "Second Trigger";
330         ON_CALL(storageMock, load(FilePath("trigger2")))
331             .WillByDefault(InvokeWithoutArgs([this] { return data2; }));
332     }
333 
334     nlohmann::json data1 = nlohmann::json{
335         {"Version", Trigger::triggerVersion},
336         {"Id", TriggerParams().id()},
337         {"Name", TriggerParams().name()},
338         {"ThresholdParamsDiscriminator",
339          TriggerParams().thresholdParams().index()},
340         {"TriggerActions", utils::transform(TriggerParams().triggerActions(),
341                                             [](const auto& action) {
342                                                 return actionToString(action);
343                                             })},
344         {"ThresholdParams", utils::labeledThresholdParamsToJson(
345                                 TriggerParams().thresholdParams())},
346         {"ReportIds", TriggerParams().reportIds()},
347         {"Sensors", TriggerParams().sensors()}};
348 
349     nlohmann::json data2 = data1;
350 };
351 
352 TEST_F(TestTriggerManagerStorage, triggerManagerCtorAddTriggerFromStorage)
353 {
354     triggerFactoryMock.expectMake(TriggerParams(), _, Ref(storageMock));
355     triggerFactoryMock.expectMake(
356         TriggerParams().id("Trigger2").name("Second Trigger"), _,
357         Ref(storageMock));
358     EXPECT_CALL(storageMock, remove(_)).Times(0);
359 
360     sut = makeTriggerManager();
361 }
362 
363 TEST_F(TestTriggerManagerStorage,
364        triggerManagerCtorRemoveDiscreteTriggerFromStorage)
365 {
366     LabeledTriggerThresholdParams thresholdParams =
367         std::vector<discrete::LabeledThresholdParam>{
368             {"userId1", discrete::Severity::warning, 15, "10.0"},
369             {"userId2", discrete::Severity::critical, 5, "20.0"}};
370 
371     data1["ThresholdParamsDiscriminator"] = thresholdParams.index();
372 
373     data1["ThresholdParams"] =
374         utils::labeledThresholdParamsToJson(thresholdParams);
375 
376     EXPECT_CALL(storageMock, remove(FilePath("trigger1"))).Times(0);
377 
378     sut = makeTriggerManager();
379 }
380 
381 TEST_F(TestTriggerManagerStorage,
382        triggerManagerCtorRemoveDiscreteTriggerFromStorage2)
383 {
384     data1["IsDiscrete"] = true;
385 
386     EXPECT_CALL(storageMock, remove(FilePath("trigger1"))).Times(0);
387 
388     sut = makeTriggerManager();
389 }
390 
391 TEST_F(TestTriggerManagerStorage,
392        triggerManagerCtorAddProperRemoveInvalidTriggerFromStorage)
393 {
394     data1["Version"] = Trigger::triggerVersion - 1;
395 
396     triggerFactoryMock.expectMake(
397         TriggerParams().id("Trigger2").name("Second Trigger"), _,
398         Ref(storageMock));
399     EXPECT_CALL(storageMock, remove(FilePath("trigger1")));
400 
401     sut = makeTriggerManager();
402 }
403