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