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