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