1 #include "dbus_environment.hpp"
2 #include "helpers.hpp"
3 #include "messages/collect_trigger_id.hpp"
4 #include "messages/trigger_presence_changed_ind.hpp"
5 #include "mocks/json_storage_mock.hpp"
6 #include "mocks/report_manager_mock.hpp"
7 #include "mocks/sensor_mock.hpp"
8 #include "mocks/threshold_mock.hpp"
9 #include "mocks/trigger_factory_mock.hpp"
10 #include "mocks/trigger_manager_mock.hpp"
11 #include "params/trigger_params.hpp"
12 #include "trigger.hpp"
13 #include "trigger_manager.hpp"
14 #include "utils/conversion_trigger.hpp"
15 #include "utils/messanger.hpp"
16 #include "utils/transform.hpp"
17 #include "utils/tstring.hpp"
18 
19 #include <boost/range/combine.hpp>
20 
21 using namespace testing;
22 using namespace std::literals::string_literals;
23 
24 static constexpr size_t expectedTriggerVersion = 2;
25 
26 class TestTrigger : public Test
27 {
28   public:
29     TriggerParams triggerParams;
30     TriggerParams triggerDiscreteParams =
31         TriggerParams()
32             .id("DiscreteTrigger")
33             .name("My Discrete Trigger")
34             .thresholdParams(std::vector<discrete::LabeledThresholdParam>{
35                 discrete::LabeledThresholdParam{
36                     "userId", discrete::Severity::warning,
37                     Milliseconds(10).count(), "15.2"},
38                 discrete::LabeledThresholdParam{
39                     "userId_2", discrete::Severity::critical,
40                     Milliseconds(5).count(), "32.7"},
41             });
42 
43     std::unique_ptr<ReportManagerMock> reportManagerMockPtr =
44         std::make_unique<NiceMock<ReportManagerMock>>();
45     std::unique_ptr<TriggerManagerMock> triggerManagerMockPtr =
46         std::make_unique<NiceMock<TriggerManagerMock>>();
47     std::unique_ptr<TriggerFactoryMock> triggerFactoryMockPtr =
48         std::make_unique<NiceMock<TriggerFactoryMock>>();
49     testing::NiceMock<StorageMock> storageMock;
50     NiceMock<MockFunction<void(const messages::TriggerPresenceChangedInd)>>
51         triggerPresenceChanged;
52     std::vector<std::shared_ptr<interfaces::Threshold>> thresholdMocks;
53     utils::Messanger messanger;
54     std::unique_ptr<Trigger> sut;
55 
56     TestTrigger() : messanger(DbusEnvironment::getIoc())
57     {
58         messanger.on_receive<messages::TriggerPresenceChangedInd>(
59             [this](const auto& msg) { triggerPresenceChanged.Call(msg); });
60     }
61 
62     void SetUp() override
63     {
64         sut = makeTrigger(triggerParams);
65     }
66 
67     static std::vector<LabeledSensorInfo>
68         convertToLabeledSensor(const SensorsInfo& sensorsInfo)
69     {
70         return utils::transform(sensorsInfo, [](const auto& sensorInfo) {
71             const auto& [sensorPath, sensorMetadata] = sensorInfo;
72             return LabeledSensorInfo("service1", sensorPath, sensorMetadata);
73         });
74     }
75 
76     std::unique_ptr<Trigger> makeTrigger(const TriggerParams& params)
77     {
78         thresholdMocks =
79             ThresholdMock::makeThresholds(params.thresholdParams());
80 
81         auto id = std::make_unique<const std::string>(params.id());
82 
83         return std::make_unique<Trigger>(
84             DbusEnvironment::getIoc(), DbusEnvironment::getObjServer(),
85             std::move(id), params.name(), params.triggerActions(),
86             std::make_shared<std::vector<std::string>>(
87                 params.reportIds().begin(), params.reportIds().end()),
88             std::vector<std::shared_ptr<interfaces::Threshold>>(thresholdMocks),
89             *triggerManagerMockPtr, storageMock, *triggerFactoryMockPtr,
90             SensorMock::makeSensorMocks(params.sensors()));
91     }
92 
93     static interfaces::JsonStorage::FilePath to_file_path(std::string name)
94     {
95         return interfaces::JsonStorage::FilePath(
96             std::to_string(std::hash<std::string>{}(name)));
97     }
98 
99     template <class T>
100     static T getProperty(const std::string& path, const std::string& property)
101     {
102         return DbusEnvironment::getProperty<T>(path, Trigger::triggerIfaceName,
103                                                property);
104     }
105 
106     template <class T>
107     static boost::system::error_code setProperty(const std::string& path,
108                                                  const std::string& property,
109                                                  const T& newValue)
110     {
111         return DbusEnvironment::setProperty<T>(path, Trigger::triggerIfaceName,
112                                                property, newValue);
113     }
114 
115     boost::system::error_code deleteTrigger(const std::string& path)
116     {
117         std::promise<boost::system::error_code> methodPromise;
118         DbusEnvironment::getBus()->async_method_call(
119             [&methodPromise](boost::system::error_code ec) {
120                 methodPromise.set_value(ec);
121             },
122             DbusEnvironment::serviceName(), path, Trigger::deleteIfaceName,
123             "Delete");
124         return DbusEnvironment::waitForFuture(methodPromise.get_future());
125     }
126 };
127 
128 TEST_F(TestTrigger, checkIfPropertiesAreSet)
129 {
130     EXPECT_THAT(getProperty<std::string>(sut->getPath(), "Name"),
131                 Eq(triggerParams.name()));
132     EXPECT_THAT(getProperty<bool>(sut->getPath(), "Persistent"), Eq(true));
133     EXPECT_THAT(
134         getProperty<std::vector<std::string>>(sut->getPath(), "TriggerActions"),
135         Eq(utils::transform(
136             triggerParams.triggerActions(),
137             [](const auto& action) { return actionToString(action); })));
138     EXPECT_THAT((getProperty<SensorsInfo>(sut->getPath(), "Sensors")),
139                 Eq(utils::fromLabeledSensorsInfo(triggerParams.sensors())));
140     EXPECT_THAT(
141         getProperty<std::vector<std::string>>(sut->getPath(), "ReportNames"),
142         Eq(triggerParams.reportIds()));
143     EXPECT_THAT(
144         getProperty<bool>(sut->getPath(), "Discrete"),
145         Eq(isTriggerThresholdDiscrete(triggerParams.thresholdParams())));
146     EXPECT_THAT(
147         getProperty<TriggerThresholdParams>(sut->getPath(), "Thresholds"),
148         Eq(std::visit(utils::FromLabeledThresholdParamConversion(),
149                       triggerParams.thresholdParams())));
150 }
151 
152 TEST_F(TestTrigger, checkBasicGetters)
153 {
154     EXPECT_THAT(sut->getId(), Eq(triggerParams.id()));
155     EXPECT_THAT(sut->getPath(), Eq(Trigger::triggerDir + triggerParams.id()));
156 }
157 
158 TEST_F(TestTrigger, setPropertyNameToCorrectValue)
159 {
160     std::string name = "custom name 1234 %^#5";
161     EXPECT_THAT(setProperty(sut->getPath(), "Name", name),
162                 Eq(boost::system::errc::success));
163     EXPECT_THAT(getProperty<std::string>(sut->getPath(), "Name"), Eq(name));
164 }
165 
166 TEST_F(TestTrigger, setPropertyReportNames)
167 {
168     std::vector<std::string> newNames = {"abc", "one", "two"};
169     EXPECT_THAT(setProperty(sut->getPath(), "ReportNames", newNames),
170                 Eq(boost::system::errc::success));
171     EXPECT_THAT(
172         getProperty<std::vector<std::string>>(sut->getPath(), "ReportNames"),
173         Eq(newNames));
174 }
175 
176 TEST_F(TestTrigger, sendsUpdateWhenReportNamesChanges)
177 {
178     const std::vector<std::string> newPropertyVal = {"abc", "one", "two"};
179 
180     EXPECT_CALL(triggerPresenceChanged,
181                 Call(FieldsAre(messages::Presence::Exist, triggerParams.id(),
182                                UnorderedElementsAreArray(newPropertyVal))));
183 
184     EXPECT_THAT(setProperty(sut->getPath(), "ReportNames", newPropertyVal),
185                 Eq(boost::system::errc::success));
186 }
187 
188 TEST_F(TestTrigger, sendsUpdateWhenReportNamesChangesToSameValue)
189 {
190     const std::vector<std::string> newPropertyVal = triggerParams.reportIds();
191 
192     EXPECT_CALL(
193         triggerPresenceChanged,
194         Call(FieldsAre(messages::Presence::Exist, triggerParams.id(),
195                        UnorderedElementsAreArray(triggerParams.reportIds()))));
196 
197     EXPECT_THAT(setProperty(sut->getPath(), "ReportNames", newPropertyVal),
198                 Eq(boost::system::errc::success));
199 }
200 
201 TEST_F(TestTrigger,
202        DISABLED_settingPropertyReportNamesThrowsExceptionWhenDuplicateReportIds)
203 {
204     std::vector<std::string> newPropertyVal{"report1", "report2", "report1"};
205 
206     EXPECT_CALL(triggerPresenceChanged, Call(_)).Times(0);
207 
208     EXPECT_THAT(setProperty(sut->getPath(), "ReportNames", newPropertyVal),
209                 Eq(boost::system::errc::invalid_argument));
210 }
211 
212 TEST_F(TestTrigger, setPropertySensors)
213 {
214     EXPECT_CALL(*triggerFactoryMockPtr, updateSensors(_, _));
215     for (const auto& threshold : thresholdMocks)
216     {
217         auto thresholdMockPtr =
218             std::dynamic_pointer_cast<NiceMock<ThresholdMock>>(threshold);
219         EXPECT_CALL(*thresholdMockPtr, updateSensors(_));
220     }
221     SensorsInfo newSensors({std::make_pair(
222         sdbusplus::message::object_path("/abc/def"), "metadata")});
223     EXPECT_THAT(setProperty(sut->getPath(), "Sensors", newSensors),
224                 Eq(boost::system::errc::success));
225 }
226 
227 TEST_F(TestTrigger, setPropertyThresholds)
228 {
229     EXPECT_CALL(*triggerFactoryMockPtr, updateThresholds(_, _, _, _, _, _));
230     TriggerThresholdParams newThresholds =
231         std::vector<discrete::ThresholdParam>(
232             {std::make_tuple("discrete threshold", "OK", 10, "12.3")});
233     EXPECT_THAT(setProperty(sut->getPath(), "Thresholds", newThresholds),
234                 Eq(boost::system::errc::success));
235 }
236 
237 TEST_F(TestTrigger, checkIfNumericCoversionsAreGood)
238 {
239     const auto& labeledParamsBase =
240         std::get<std::vector<numeric::LabeledThresholdParam>>(
241             triggerParams.thresholdParams());
242     const auto paramsToCheck =
243         std::visit(utils::FromLabeledThresholdParamConversion(),
244                    triggerParams.thresholdParams());
245     const auto labeledParamsToCheck =
246         std::get<std::vector<numeric::LabeledThresholdParam>>(std::visit(
247             utils::ToLabeledThresholdParamConversion(), paramsToCheck));
248 
249     for (const auto& [tocheck, base] :
250          boost::combine(labeledParamsToCheck, labeledParamsBase))
251     {
252         EXPECT_THAT(tocheck.at_label<utils::tstring::Type>(),
253                     Eq(base.at_label<utils::tstring::Type>()));
254         EXPECT_THAT(tocheck.at_label<utils::tstring::Direction>(),
255                     Eq(base.at_label<utils::tstring::Direction>()));
256         EXPECT_THAT(tocheck.at_label<utils::tstring::DwellTime>(),
257                     Eq(base.at_label<utils::tstring::DwellTime>()));
258         EXPECT_THAT(tocheck.at_label<utils::tstring::ThresholdValue>(),
259                     Eq(base.at_label<utils::tstring::ThresholdValue>()));
260     }
261 }
262 
263 TEST_F(TestTrigger, checkIfDiscreteCoversionsAreGood)
264 {
265     const auto& labeledParamsBase =
266         std::get<std::vector<discrete::LabeledThresholdParam>>(
267             triggerDiscreteParams.thresholdParams());
268     const auto paramsToCheck =
269         std::visit(utils::FromLabeledThresholdParamConversion(),
270                    triggerDiscreteParams.thresholdParams());
271     const auto labeledParamsToCheck =
272         std::get<std::vector<discrete::LabeledThresholdParam>>(std::visit(
273             utils::ToLabeledThresholdParamConversion(), paramsToCheck));
274 
275     for (const auto& [tocheck, base] :
276          boost::combine(labeledParamsToCheck, labeledParamsBase))
277     {
278         EXPECT_THAT(tocheck.at_label<utils::tstring::UserId>(),
279                     Eq(base.at_label<utils::tstring::UserId>()));
280         EXPECT_THAT(tocheck.at_label<utils::tstring::Severity>(),
281                     Eq(base.at_label<utils::tstring::Severity>()));
282         EXPECT_THAT(tocheck.at_label<utils::tstring::DwellTime>(),
283                     Eq(base.at_label<utils::tstring::DwellTime>()));
284         EXPECT_THAT(tocheck.at_label<utils::tstring::ThresholdValue>(),
285                     Eq(base.at_label<utils::tstring::ThresholdValue>()));
286     }
287 }
288 
289 TEST_F(TestTrigger, deleteTrigger)
290 {
291     EXPECT_CALL(storageMock, remove(to_file_path(sut->getId())));
292     EXPECT_CALL(*triggerManagerMockPtr, removeTrigger(sut.get()));
293 
294     auto ec = deleteTrigger(sut->getPath());
295     EXPECT_THAT(ec, Eq(boost::system::errc::success));
296 }
297 
298 TEST_F(TestTrigger, sendUpdateWhenTriggerIsDeleted)
299 {
300     EXPECT_CALL(triggerPresenceChanged,
301                 Call(FieldsAre(messages::Presence::Removed, triggerParams.id(),
302                                UnorderedElementsAre())));
303 
304     auto ec = deleteTrigger(sut->getPath());
305     EXPECT_THAT(ec, Eq(boost::system::errc::success));
306 }
307 
308 TEST_F(TestTrigger, deletingNonExistingTriggerReturnInvalidRequestDescriptor)
309 {
310     auto ec = deleteTrigger(Trigger::triggerDir + "NonExisting"s);
311     EXPECT_THAT(ec.value(), Eq(EBADR));
312 }
313 
314 TEST_F(TestTrigger, settingPersistencyToFalseRemovesTriggerFromStorage)
315 {
316     EXPECT_CALL(storageMock, remove(to_file_path(sut->getId())));
317 
318     bool persistent = false;
319     EXPECT_THAT(setProperty(sut->getPath(), "Persistent", persistent),
320                 Eq(boost::system::errc::success));
321     EXPECT_THAT(getProperty<bool>(sut->getPath(), "Persistent"),
322                 Eq(persistent));
323 }
324 
325 class TestTriggerInitialization : public TestTrigger
326 {
327   public:
328     void SetUp() override
329     {}
330 
331     nlohmann::json storedConfiguration;
332 };
333 
334 TEST_F(TestTriggerInitialization,
335        exceptionDuringTriggerStoreDisablesPersistency)
336 {
337     EXPECT_CALL(storageMock, store(_, _))
338         .WillOnce(Throw(std::runtime_error("Generic error!")));
339 
340     sut = makeTrigger(triggerParams);
341 
342     EXPECT_THAT(getProperty<bool>(sut->getPath(), "Persistent"), Eq(false));
343 }
344 
345 TEST_F(TestTriggerInitialization, creatingTriggerThrowsExceptionWhenIdIsInvalid)
346 {
347     EXPECT_CALL(storageMock, store(_, _)).Times(0);
348 
349     EXPECT_THROW(makeTrigger(triggerParams.id("inv?lidId")),
350                  sdbusplus::exception::SdBusError);
351 }
352 
353 TEST_F(TestTriggerInitialization, creatingTriggerUpdatesTriggersIdsInReports)
354 {
355     EXPECT_CALL(
356         triggerPresenceChanged,
357         Call(FieldsAre(messages::Presence::Exist, triggerParams.id(),
358                        UnorderedElementsAreArray(triggerParams.reportIds()))));
359 
360     sut = makeTrigger(triggerParams);
361 }
362 
363 class TestTriggerStore : public TestTrigger
364 {
365   public:
366     nlohmann::json storedConfiguration;
367     nlohmann::json storedDiscreteConfiguration;
368     std::unique_ptr<Trigger> sutDiscrete;
369 
370     void SetUp() override
371     {
372         ON_CALL(storageMock, store(_, _))
373             .WillByDefault(SaveArg<1>(&storedConfiguration));
374         sut = makeTrigger(triggerParams);
375 
376         ON_CALL(storageMock, store(_, _))
377             .WillByDefault(SaveArg<1>(&storedDiscreteConfiguration));
378         sutDiscrete = makeTrigger(triggerDiscreteParams);
379     }
380 };
381 
382 TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerVersion)
383 {
384     ASSERT_THAT(storedConfiguration.at("Version"), Eq(expectedTriggerVersion));
385 }
386 
387 TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerId)
388 {
389     ASSERT_THAT(storedConfiguration.at("Id"), Eq(triggerParams.id()));
390 }
391 
392 TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerName)
393 {
394     ASSERT_THAT(storedConfiguration.at("Name"), Eq(triggerParams.name()));
395 }
396 
397 TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerTriggerActions)
398 {
399     ASSERT_THAT(storedConfiguration.at("TriggerActions"),
400                 Eq(utils::transform(triggerParams.triggerActions(),
401                                     [](const auto& action) {
402                                         return actionToString(action);
403                                     })));
404 }
405 
406 TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerReportIds)
407 {
408     ASSERT_THAT(storedConfiguration.at("ReportIds"),
409                 Eq(triggerParams.reportIds()));
410 }
411 
412 TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerSensors)
413 {
414     nlohmann::json expectedItem;
415     expectedItem["service"] = "service1";
416     expectedItem["path"] = "/xyz/openbmc_project/sensors/temperature/BMC_Temp";
417     expectedItem["metadata"] = "metadata1";
418 
419     ASSERT_THAT(storedConfiguration.at("Sensors"), ElementsAre(expectedItem));
420 }
421 
422 TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerThresholdParams)
423 {
424     nlohmann::json expectedItem0;
425     expectedItem0["type"] = 0;
426     expectedItem0["dwellTime"] = 10;
427     expectedItem0["direction"] = 1;
428     expectedItem0["thresholdValue"] = 0.5;
429 
430     nlohmann::json expectedItem1;
431     expectedItem1["type"] = 3;
432     expectedItem1["dwellTime"] = 10;
433     expectedItem1["direction"] = 2;
434     expectedItem1["thresholdValue"] = 90.2;
435 
436     ASSERT_THAT(storedConfiguration.at("ThresholdParamsDiscriminator"), Eq(0));
437     ASSERT_THAT(storedConfiguration.at("ThresholdParams"),
438                 ElementsAre(expectedItem0, expectedItem1));
439 }
440 
441 TEST_F(TestTriggerStore,
442        settingPersistencyToTrueStoresDiscreteTriggerThresholdParams)
443 {
444     nlohmann::json expectedItem0;
445     expectedItem0["userId"] = "userId";
446     expectedItem0["severity"] = discrete::Severity::warning;
447     expectedItem0["dwellTime"] = 10;
448     expectedItem0["thresholdValue"] = "15.2";
449 
450     nlohmann::json expectedItem1;
451     expectedItem1["userId"] = "userId_2";
452     expectedItem1["severity"] = discrete::Severity::critical;
453     expectedItem1["dwellTime"] = 5;
454     expectedItem1["thresholdValue"] = "32.7";
455 
456     ASSERT_THAT(storedDiscreteConfiguration.at("ThresholdParamsDiscriminator"),
457                 Eq(1));
458     ASSERT_THAT(storedDiscreteConfiguration.at("ThresholdParams"),
459                 ElementsAre(expectedItem0, expectedItem1));
460 }
461