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