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