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(
159             triggerParams.triggerActions(),
160             [](const auto& action) { return actionToString(action); })));
161     EXPECT_THAT((getProperty<SensorsInfo>(sut->getPath(), "Sensors")),
162                 Eq(utils::fromLabeledSensorsInfo(triggerParams.sensors())));
163     EXPECT_THAT(
164         getProperty<std::vector<object_path>>(sut->getPath(), "Reports"),
165         Eq(triggerParams.reports()));
166     EXPECT_THAT(
167         getProperty<bool>(sut->getPath(), "Discrete"),
168         Eq(isTriggerThresholdDiscrete(triggerParams.thresholdParams())));
169     EXPECT_THAT(
170         getProperty<TriggerThresholdParams>(sut->getPath(), "Thresholds"),
171         Eq(std::visit(utils::FromLabeledThresholdParamConversion(),
172                       triggerParams.thresholdParams())));
173 }
174 
175 TEST_F(TestTrigger, checkBasicGetters)
176 {
177     EXPECT_THAT(sut->getId(), Eq(triggerParams.id()));
178     EXPECT_THAT(sut->getPath(),
179                 Eq(utils::constants::triggerDirPath.str + triggerParams.id()));
180 }
181 
182 TEST_F(TestTrigger, setPropertyNameToCorrectValue)
183 {
184     std::string name = "custom name 1234 %^#5";
185     EXPECT_THAT(setProperty(sut->getPath(), "Name", name),
186                 Eq(boost::system::errc::success));
187     EXPECT_THAT(getProperty<std::string>(sut->getPath(), "Name"), Eq(name));
188 }
189 
190 TEST_F(TestTrigger, setPropertyReportNames)
191 {
192     std::vector<object_path> newNames = {
193         utils::constants::reportDirPath / "abc",
194         utils::constants::reportDirPath / "one",
195         utils::constants::reportDirPath / "prefix" / "two"};
196     EXPECT_THAT(setProperty(sut->getPath(), "Reports", newNames),
197                 Eq(boost::system::errc::success));
198     EXPECT_THAT(
199         getProperty<std::vector<object_path>>(sut->getPath(), "Reports"),
200         Eq(newNames));
201 }
202 
203 TEST_F(TestTrigger, sendsUpdateWhenReportNamesChanges)
204 {
205     std::vector<object_path> newPropertyVal = {
206         utils::constants::reportDirPath / "abc",
207         utils::constants::reportDirPath / "one",
208         utils::constants::reportDirPath / "two"};
209 
210     EXPECT_CALL(triggerPresenceChanged,
211                 Call(FieldsAre(messages::Presence::Exist, triggerParams.id(),
212                                UnorderedElementsAre("abc", "one", "two"))));
213 
214     EXPECT_THAT(setProperty(sut->getPath(), "Reports", newPropertyVal),
215                 Eq(boost::system::errc::success));
216 }
217 
218 TEST_F(TestTrigger, sendsUpdateWhenReportNamesChangesToSameValue)
219 {
220     const std::vector<object_path> newPropertyVal = triggerParams.reports();
221 
222     EXPECT_CALL(
223         triggerPresenceChanged,
224         Call(FieldsAre(messages::Presence::Exist, triggerParams.id(),
225                        UnorderedElementsAreArray(triggerParams.reportIds()))));
226 
227     EXPECT_THAT(setProperty(sut->getPath(), "Reports", newPropertyVal),
228                 Eq(boost::system::errc::success));
229 }
230 
231 TEST_F(TestTrigger,
232        DISABLED_settingPropertyReportNamesThrowsExceptionWhenDuplicateReportIds)
233 {
234     std::vector<object_path> newPropertyVal{
235         utils::constants::reportDirPath / "report1",
236         utils::constants::reportDirPath / "report2",
237         utils::constants::reportDirPath / "report1"};
238 
239     EXPECT_CALL(triggerPresenceChanged, Call(_)).Times(0);
240 
241     EXPECT_THAT(setProperty(sut->getPath(), "Reports", newPropertyVal),
242                 Eq(boost::system::errc::invalid_argument));
243 }
244 
245 TEST_F(
246     TestTrigger,
247     DISABLED_settingPropertyReportNamesThrowsExceptionWhenReportWithTooManyPrefixes)
248 {
249     std::vector<object_path> newPropertyVal{
250         object_path("/xyz/openbmc_project/Telemetry/Reports/P1/P2/MyReport")};
251 
252     EXPECT_CALL(triggerPresenceChanged, Call(_)).Times(0);
253 
254     EXPECT_THAT(setProperty(sut->getPath(), "Reports", newPropertyVal),
255                 Eq(boost::system::errc::invalid_argument));
256 }
257 
258 TEST_F(
259     TestTrigger,
260     DISABLED_settingPropertyReportNamesThrowsExceptionWhenReportWithTooLongPrefix)
261 {
262     std::vector<object_path> newPropertyVal{
263         object_path("/xyz/openbmc_project/Telemetry/Reports/" +
264                     utils::string_utils::getTooLongPrefix() + "/MyReport")};
265 
266     EXPECT_CALL(triggerPresenceChanged, Call(_)).Times(0);
267 
268     EXPECT_THAT(setProperty(sut->getPath(), "Reports", newPropertyVal),
269                 Eq(boost::system::errc::invalid_argument));
270 }
271 
272 TEST_F(
273     TestTrigger,
274     DISABLED_settingPropertyReportNamesThrowsExceptionWhenReportWithTooLongId)
275 {
276     std::vector<object_path> newPropertyVal{
277         object_path("/xyz/openbmc_project/Telemetry/Reports/Prefix/" +
278                     utils::string_utils::getTooLongId())};
279 
280     EXPECT_CALL(triggerPresenceChanged, Call(_)).Times(0);
281 
282     EXPECT_THAT(setProperty(sut->getPath(), "Reports", newPropertyVal),
283                 Eq(boost::system::errc::invalid_argument));
284 }
285 
286 TEST_F(TestTrigger,
287        DISABLED_settingPropertyReportNamesThrowsExceptionWhenReportWithBadPath)
288 {
289     std::vector<object_path> newPropertyVal{
290         object_path("/xyz/openbmc_project/Telemetry/NotReports/MyReport")};
291 
292     EXPECT_CALL(triggerPresenceChanged, Call(_)).Times(0);
293 
294     EXPECT_THAT(setProperty(sut->getPath(), "Reports", newPropertyVal),
295                 Eq(boost::system::errc::invalid_argument));
296 }
297 
298 TEST_F(TestTrigger, setPropertySensors)
299 {
300     EXPECT_CALL(*triggerFactoryMockPtr, updateSensors(_, _));
301     for (const auto& threshold : thresholdMocks)
302     {
303         auto thresholdMockPtr =
304             std::dynamic_pointer_cast<NiceMock<ThresholdMock>>(threshold);
305         EXPECT_CALL(*thresholdMockPtr, updateSensors(_));
306     }
307     SensorsInfo newSensors(
308         {std::make_pair(object_path("/abc/def"), "metadata")});
309     EXPECT_THAT(setProperty(sut->getPath(), "Sensors", newSensors),
310                 Eq(boost::system::errc::success));
311 }
312 
313 TEST_F(TestTrigger, setPropertyThresholds)
314 {
315     EXPECT_CALL(*triggerFactoryMockPtr, updateThresholds(_, _, _, _, _, _));
316     TriggerThresholdParams newThresholds =
317         std::vector<discrete::ThresholdParam>({std::make_tuple(
318             "discrete threshold", utils::enumToString(discrete::Severity::ok),
319             10, "12.3")});
320     EXPECT_THAT(setProperty(sut->getPath(), "Thresholds", newThresholds),
321                 Eq(boost::system::errc::success));
322 }
323 
324 TEST_F(TestTrigger, setThresholdParamsWithTooLongDiscreteName)
325 {
326     const TriggerThresholdParams currentValue =
327         std::visit(utils::FromLabeledThresholdParamConversion(),
328                    triggerParams.thresholdParams());
329 
330     TriggerThresholdParams newThresholds =
331         std::vector<discrete::ThresholdParam>({std::make_tuple(
332             utils::string_utils::getTooLongName(),
333             utils::enumToString(discrete::Severity::ok), 10, "12.3")});
334 
335     changeProperty<TriggerThresholdParams>(
336         sut->getPath(), "Thresholds",
337         {.valueBefore = Eq(currentValue),
338          .newValue = newThresholds,
339          .ec = Eq(boost::system::errc::invalid_argument),
340          .valueAfter = Eq(currentValue)});
341 }
342 
343 TEST_F(TestTrigger, setNameTooLong)
344 {
345     std::string currentValue = TriggerParams().name();
346 
347     changeProperty<std::string>(
348         sut->getPath(), "Name",
349         {.valueBefore = Eq(currentValue),
350          .newValue = utils::string_utils::getTooLongName(),
351          .ec = Eq(boost::system::errc::invalid_argument),
352          .valueAfter = Eq(currentValue)});
353 }
354 
355 TEST_F(TestTrigger, checkIfNumericCoversionsAreGood)
356 {
357     const auto& labeledParamsBase =
358         std::get<std::vector<numeric::LabeledThresholdParam>>(
359             triggerParams.thresholdParams());
360     const auto paramsToCheck =
361         std::visit(utils::FromLabeledThresholdParamConversion(),
362                    triggerParams.thresholdParams());
363     const auto labeledParamsToCheck =
364         std::get<std::vector<numeric::LabeledThresholdParam>>(std::visit(
365             utils::ToLabeledThresholdParamConversion(), paramsToCheck));
366 
367     for (const auto& [tocheck, base] :
368          boost::combine(labeledParamsToCheck, labeledParamsBase))
369     {
370         EXPECT_THAT(tocheck.at_label<utils::tstring::Type>(),
371                     Eq(base.at_label<utils::tstring::Type>()));
372         EXPECT_THAT(tocheck.at_label<utils::tstring::Direction>(),
373                     Eq(base.at_label<utils::tstring::Direction>()));
374         EXPECT_THAT(tocheck.at_label<utils::tstring::DwellTime>(),
375                     Eq(base.at_label<utils::tstring::DwellTime>()));
376         EXPECT_THAT(tocheck.at_label<utils::tstring::ThresholdValue>(),
377                     Eq(base.at_label<utils::tstring::ThresholdValue>()));
378     }
379 }
380 
381 TEST_F(TestTrigger, checkIfDiscreteCoversionsAreGood)
382 {
383     const auto& labeledParamsBase =
384         std::get<std::vector<discrete::LabeledThresholdParam>>(
385             triggerDiscreteParams.thresholdParams());
386     const auto paramsToCheck =
387         std::visit(utils::FromLabeledThresholdParamConversion(),
388                    triggerDiscreteParams.thresholdParams());
389     const auto labeledParamsToCheck =
390         std::get<std::vector<discrete::LabeledThresholdParam>>(std::visit(
391             utils::ToLabeledThresholdParamConversion(), paramsToCheck));
392 
393     for (const auto& [tocheck, base] :
394          boost::combine(labeledParamsToCheck, labeledParamsBase))
395     {
396         EXPECT_THAT(tocheck.at_label<utils::tstring::UserId>(),
397                     Eq(base.at_label<utils::tstring::UserId>()));
398         EXPECT_THAT(tocheck.at_label<utils::tstring::Severity>(),
399                     Eq(base.at_label<utils::tstring::Severity>()));
400         EXPECT_THAT(tocheck.at_label<utils::tstring::DwellTime>(),
401                     Eq(base.at_label<utils::tstring::DwellTime>()));
402         EXPECT_THAT(tocheck.at_label<utils::tstring::ThresholdValue>(),
403                     Eq(base.at_label<utils::tstring::ThresholdValue>()));
404     }
405 }
406 
407 TEST_F(TestTrigger, deleteTrigger)
408 {
409     EXPECT_CALL(storageMock, remove(to_file_path(sut->getId())));
410     EXPECT_CALL(*triggerManagerMockPtr, removeTrigger(sut.get()));
411 
412     auto ec = deleteTrigger(sut->getPath());
413     EXPECT_THAT(ec, Eq(boost::system::errc::success));
414 }
415 
416 TEST_F(TestTrigger, sendUpdateWhenTriggerIsDeleted)
417 {
418     EXPECT_CALL(triggerPresenceChanged,
419                 Call(FieldsAre(messages::Presence::Removed, triggerParams.id(),
420                                UnorderedElementsAre())));
421 
422     auto ec = deleteTrigger(sut->getPath());
423     EXPECT_THAT(ec, Eq(boost::system::errc::success));
424 }
425 
426 TEST_F(TestTrigger, deletingNonExistingTriggerReturnInvalidRequestDescriptor)
427 {
428     auto ec =
429         deleteTrigger(utils::constants::triggerDirPath.str + "NonExisting"s);
430     EXPECT_THAT(ec.value(), Eq(EBADR));
431 }
432 
433 TEST_F(TestTrigger, settingPersistencyToFalseRemovesTriggerFromStorage)
434 {
435     EXPECT_CALL(storageMock, remove(to_file_path(sut->getId())));
436 
437     bool persistent = false;
438     EXPECT_THAT(setProperty(sut->getPath(), "Persistent", persistent),
439                 Eq(boost::system::errc::success));
440     EXPECT_THAT(getProperty<bool>(sut->getPath(), "Persistent"),
441                 Eq(persistent));
442 }
443 
444 class TestTriggerInitialization : public TestTrigger
445 {
446   public:
447     void SetUp() override {}
448 
449     nlohmann::json storedConfiguration;
450 };
451 
452 TEST_F(TestTriggerInitialization,
453        exceptionDuringTriggerStoreDisablesPersistency)
454 {
455     EXPECT_CALL(storageMock, store(_, _))
456         .WillOnce(Throw(std::runtime_error("Generic error!")));
457 
458     sut = makeTrigger(triggerParams);
459 
460     EXPECT_THAT(getProperty<bool>(sut->getPath(), "Persistent"), Eq(false));
461 }
462 
463 TEST_F(TestTriggerInitialization, creatingTriggerThrowsExceptionWhenIdIsInvalid)
464 {
465     EXPECT_CALL(storageMock, store(_, _)).Times(0);
466 
467     EXPECT_THROW(makeTrigger(triggerParams.id("inv?lidId")),
468                  sdbusplus::exception::SdBusError);
469 }
470 
471 TEST_F(TestTriggerInitialization, creatingTriggerUpdatesTriggersIdsInReports)
472 {
473     EXPECT_CALL(
474         triggerPresenceChanged,
475         Call(FieldsAre(messages::Presence::Exist, triggerParams.id(),
476                        UnorderedElementsAreArray(triggerParams.reportIds()))));
477 
478     sut = makeTrigger(triggerParams);
479 }
480 
481 class TestTriggerStore : public TestTrigger
482 {
483   public:
484     nlohmann::json storedConfiguration;
485     nlohmann::json storedDiscreteConfiguration;
486     std::unique_ptr<Trigger> sutDiscrete;
487 
488     void SetUp() override
489     {
490         ON_CALL(storageMock, store(_, _))
491             .WillByDefault(SaveArg<1>(&storedConfiguration));
492         sut = makeTrigger(triggerParams);
493 
494         ON_CALL(storageMock, store(_, _))
495             .WillByDefault(SaveArg<1>(&storedDiscreteConfiguration));
496         sutDiscrete = makeTrigger(triggerDiscreteParams);
497     }
498 };
499 
500 TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerVersion)
501 {
502     ASSERT_THAT(storedConfiguration.at("Version"), Eq(expectedTriggerVersion));
503 }
504 
505 TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerId)
506 {
507     ASSERT_THAT(storedConfiguration.at("Id"), Eq(triggerParams.id()));
508 }
509 
510 TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerName)
511 {
512     ASSERT_THAT(storedConfiguration.at("Name"), Eq(triggerParams.name()));
513 }
514 
515 TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerTriggerActions)
516 {
517     ASSERT_THAT(storedConfiguration.at("TriggerActions"),
518                 Eq(utils::transform(triggerParams.triggerActions(),
519                                     [](const auto& action) {
520         return actionToString(action);
521     })));
522 }
523 
524 TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerReportIds)
525 {
526     ASSERT_THAT(storedConfiguration.at("ReportIds"),
527                 Eq(triggerParams.reportIds()));
528 }
529 
530 TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerSensors)
531 {
532     nlohmann::json expectedItem;
533     expectedItem["service"] = "service1";
534     expectedItem["path"] = "/xyz/openbmc_project/sensors/temperature/BMC_Temp";
535     expectedItem["metadata"] = "metadata1";
536 
537     ASSERT_THAT(storedConfiguration.at("Sensors"), ElementsAre(expectedItem));
538 }
539 
540 TEST_F(TestTriggerStore, settingPersistencyToTrueStoresTriggerThresholdParams)
541 {
542     nlohmann::json expectedItem0;
543     expectedItem0["type"] = 0;
544     expectedItem0["dwellTime"] = 10;
545     expectedItem0["direction"] = 1;
546     expectedItem0["thresholdValue"] = 0.5;
547 
548     nlohmann::json expectedItem1;
549     expectedItem1["type"] = 3;
550     expectedItem1["dwellTime"] = 10;
551     expectedItem1["direction"] = 2;
552     expectedItem1["thresholdValue"] = 90.2;
553 
554     ASSERT_THAT(storedConfiguration.at("ThresholdParamsDiscriminator"), Eq(0));
555     ASSERT_THAT(storedConfiguration.at("ThresholdParams"),
556                 ElementsAre(expectedItem0, expectedItem1));
557 }
558 
559 TEST_F(TestTriggerStore,
560        settingPersistencyToTrueStoresDiscreteTriggerThresholdParams)
561 {
562     nlohmann::json expectedItem0;
563     expectedItem0["userId"] = "userId";
564     expectedItem0["severity"] = discrete::Severity::warning;
565     expectedItem0["dwellTime"] = 10;
566     expectedItem0["thresholdValue"] = "15.2";
567 
568     nlohmann::json expectedItem1;
569     expectedItem1["userId"] = "userId_2";
570     expectedItem1["severity"] = discrete::Severity::critical;
571     expectedItem1["dwellTime"] = 5;
572     expectedItem1["thresholdValue"] = "32.7";
573 
574     ASSERT_THAT(storedDiscreteConfiguration.at("ThresholdParamsDiscriminator"),
575                 Eq(1));
576     ASSERT_THAT(storedDiscreteConfiguration.at("ThresholdParams"),
577                 ElementsAre(expectedItem0, expectedItem1));
578 }
579