Merge pull request #10613 from Icinga/clear-suppr-notif-after-tp-resume
Some checks failed
Container Image / Container Image (push) Has been cancelled
Linux / alpine:bash (push) Has been cancelled
Linux / amazonlinux:2 (push) Has been cancelled
Linux / amazonlinux:2023 (push) Has been cancelled
Linux / debian:11 (linux/386) (push) Has been cancelled
Linux / debian:11 (push) Has been cancelled
Linux / debian:12 (linux/386) (push) Has been cancelled
Linux / debian:12 (push) Has been cancelled
Linux / debian:13 (push) Has been cancelled
Linux / fedora:41 (push) Has been cancelled
Linux / fedora:42 (push) Has been cancelled
Linux / opensuse/leap:15.6 (push) Has been cancelled
Linux / opensuse/leap:16.0 (push) Has been cancelled
Linux / registry.suse.com/suse/sle15:15.6 (push) Has been cancelled
Linux / registry.suse.com/suse/sle15:15.7 (push) Has been cancelled
Linux / rockylinux/rockylinux:10 (push) Has been cancelled
Linux / rockylinux:8 (push) Has been cancelled
Linux / rockylinux:9 (push) Has been cancelled
Linux / ubuntu:22.04 (push) Has been cancelled
Linux / ubuntu:24.04 (push) Has been cancelled
Linux / ubuntu:25.04 (push) Has been cancelled
Windows / Windows (push) Has been cancelled

Clear suppressed notifications only after the TimePeriod is resumed
This commit is contained in:
Yonas Habteab 2025-11-03 17:52:13 +01:00 committed by GitHub
commit 35fdea8805
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 541 additions and 27 deletions

View file

@ -83,37 +83,34 @@ void FireSuppressedNotifications(const Notification::Ptr& notification)
int subtract = 0;
auto checkable (notification->GetCheckable());
for (auto type : {NotificationProblem, NotificationRecovery, NotificationFlappingStart, NotificationFlappingEnd}) {
if ((suppressedTypes & type) && !checkable->NotificationReasonApplies(type)) {
subtract |= type;
suppressedTypes &= ~type;
}
}
auto tp (notification->GetPeriod());
if (suppressedTypes) {
auto tp (notification->GetPeriod());
if ((!tp || tp->IsInside(Utility::GetTime())) && !checkable->IsLikelyToBeCheckedSoon()) {
for (auto type : {NotificationProblem, NotificationRecovery, NotificationFlappingStart, NotificationFlappingEnd}) {
if (!(suppressedTypes & type) || checkable->NotificationReasonSuppressed(type))
continue;
auto notificationName (notification->GetName());
Log(LogNotice, "NotificationComponent")
<< "Attempting to re-send previously suppressed notification '" << notificationName << "'.";
if ((!tp || tp->IsInside(Utility::GetTime())) && !checkable->IsLikelyToBeCheckedSoon()) {
for (auto type : {NotificationProblem, NotificationRecovery, NotificationFlappingStart, NotificationFlappingEnd}) {
if (!(suppressedTypes & type) || checkable->NotificationReasonSuppressed(type)) {
continue;
}
if (!checkable->NotificationReasonApplies(type)) {
subtract |= type;
SubtractSuppressedNotificationTypes(notification, subtract);
subtract = 0;
continue;
}
try {
notification->BeginExecuteNotification(type, checkable->GetLastCheckResult(), false, false);
} catch (const std::exception& ex) {
Log(LogWarning, "NotificationComponent")
<< "Exception occurred during notification for object '"
<< notificationName << "': " << DiagnosticInformation(ex, false);
}
auto notificationName (notification->GetName());
Log(LogNotice, "NotificationComponent")
<< "Attempting to re-send previously suppressed notification '" << notificationName << "'.";
subtract |= type;
SubtractSuppressedNotificationTypes(notification, subtract);
subtract = 0;
try {
notification->BeginExecuteNotification(type, checkable->GetLastCheckResult(), false, false);
} catch (const std::exception& ex) {
Log(LogWarning, "NotificationComponent")
<< "Exception occurred during notification for object '"
<< notificationName << "': " << DiagnosticInformation(ex, false);
}
}
}

View file

@ -131,6 +131,13 @@ set(base_test_SOURCES
$<TARGET_OBJECTS:methods>
)
if(ICINGA2_WITH_NOTIFICATION)
list(APPEND base_test_SOURCES
notification-notificationcomponent.cpp
$<TARGET_OBJECTS:notification>
)
endif()
if(ICINGA2_UNITY_BUILD)
mkunity_target(base test base_test_SOURCES)
endif()

View file

@ -52,6 +52,13 @@ public:
return ret;
}
void Clear()
{
std::lock_guard lock(m_Mutex);
m_Expects.clear();
m_LogEntries.clear();
}
private:
void ProcessLogEntry(const LogEntry& entry) override
{
@ -119,6 +126,8 @@ struct TestLoggerFixture
return testLogger->ExpectLogPattern(pattern, timeout);
}
void ClearTestLogger() const { testLogger->Clear(); }
TestLogger::Ptr testLogger = new TestLogger;
};

View file

@ -0,0 +1,501 @@
/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */
#include <BoostTestTargetConfig.h>
#include "base/defer.hpp"
#include "remote/apilistener.hpp"
#include "test/base-testloggerfixture.hpp"
#include "config/configcompiler.hpp"
#include "notification/notificationcomponent.hpp"
using namespace icinga;
namespace {
/**
* Gets the pointer to the private NotificationTimerHandler() by using Friend-Injection.
*
* This uses the exception the standard makes to private member access for explicit template
* instantiation by instantiating a type that defines an accessor to the member function pointer
* in the surrounding anonymous namespace.
*
* The reason for the anonymous namespace is that it doesn't violate the ODR if other
* instantiations are made to this template in other translation units.
* This isn't actually an issue here because the name is very specific to the single use-case of
* obtaining access to NotificationTimerHandler().
*/
template<auto privateMemberFnPtr>
struct InvokeTimerHandlerImpl
{
friend void InvokeTimerHandler(const NotificationComponent::Ptr& nc) { (*nc.*privateMemberFnPtr)(); }
};
void InvokeTimerHandler(const NotificationComponent::Ptr& nc);
template struct InvokeTimerHandlerImpl<&NotificationComponent::NotificationTimerHandler>;
} // namespace
class NotificationComponentFixture : public TestLoggerFixture
{
public:
NotificationComponentFixture()
{
auto createObjects = []() {
String config = R"CONFIG({
object CheckCommand "dummy" {
command = "/bin/echo"
}
object Host "h1" {
address = "h1"
check_command = "dummy"
enable_notifications = true
enable_active_checks = false
enable_passive_checks = true
}
object NotificationCommand "send" {
command = ["true"]
}
apply Notification "n1" to Host {
interval = 0
command = "send"
period = "tp1"
users = [ "u1" ]
assign where host.enable_notifications == true
}
object User "u1" {
enable_notifications = true
}
object TimePeriod "tp1" {
display_name = "Test TimePeriod"
ranges = {
"monday" = "00:00-24:00"
"tuesday" = "00:00-24:00"
"wednesday" = "00:00-24:00"
"thursday" = "00:00-24:00"
"friday" = "00:00-24:00"
"saturday" = "00:00-24:00"
"sunday" = "00:00-24:00"
}
}
object NotificationComponent "nc" {}
})CONFIG";
std::unique_ptr<Expression> expr = ConfigCompiler::CompileText("<test>", config);
expr->Evaluate(*ScriptFrame::GetCurrentFrame());
};
auto ret = ConfigItem::RunWithActivationContext(new Function("CreateTestObjects", createObjects));
BOOST_REQUIRE(ret);
m_Host = Host::GetByName("h1");
BOOST_REQUIRE(m_Host);
m_Notification = Notification::GetByName("h1!n1");
BOOST_REQUIRE(m_Notification);
m_TimePeriod = TimePeriod::GetByName("tp1");
BOOST_REQUIRE(m_TimePeriod);
ApiListener::UpdateObjectAuthority();
BOOST_REQUIRE(ApiListener::UpdatedObjectAuthority());
// Store the old periods from the config snippets to reuse them later.
m_AllTimePeriod = m_TimePeriod->GetRanges();
Checkable::OnNotificationSentToUser.connect(NotificationSentToUserHandler);
}
static void NotificationSentToUserHandler(
const Notification::Ptr&,
const Checkable::Ptr&,
const User::Ptr&,
const NotificationType& type,
const CheckResult::Ptr&,
const String&,
const String&,
const String&,
const MessageOrigin::Ptr&
)
{
std::unique_lock lock(m_NotificationMutex);
m_LastNotification = type;
m_NumNotifications++;
lock.unlock();
m_NotificationCv.notify_all();
}
bool WaitForExpectedNotificationCount(std::size_t expectedCount, std::chrono::milliseconds timeout = 5s)
{
Defer clearLog{[this]() { ClearTestLogger(); }};
std::unique_lock lock(m_NotificationMutex);
if (m_NumNotifications > expectedCount) {
return false;
}
return m_NotificationCv.wait_for(lock, timeout, [&]() { return m_NumNotifications == expectedCount; });
}
boost::test_tools::assertion_result AssertNoAttemptedSendLogPattern()
{
auto result = ExpectLogPattern("^(Sending|Attempting to (re-)?send).*?notification.*$", 0s);
ClearTestLogger();
return !result;
}
void BeginTimePeriod()
{
ObjectLock lock{m_TimePeriod};
m_TimePeriod->SetRanges(m_AllTimePeriod);
auto now = Utility::GetTime();
m_TimePeriod->UpdateRegion(now, now + 1e3, true);
BOOST_REQUIRE(m_TimePeriod->IsInside(now));
}
void EndTimePeriod()
{
ObjectLock lock{m_TimePeriod};
m_TimePeriod->SetRanges(new Dictionary);
auto now = Utility::GetTime();
m_TimePeriod->UpdateRegion(now, now + 1e3, true);
BOOST_REQUIRE(!m_TimePeriod->IsInside(now));
}
void SetNotificationInverval(double interval) { m_Notification->SetInterval(interval); }
void WaitUntilNextReminderScheduled()
{
Utility::Sleep(m_Notification->GetNextNotification() - Utility::GetTime() + 0.01);
BOOST_REQUIRE_LE(m_Notification->GetNextNotification(), Utility::GetTime());
}
static void NotificationTimerHandler()
{
auto nc = NotificationComponent::GetByName("nc");
InvokeTimerHandler(nc);
}
void ReceiveCheckResults(std::size_t num, ServiceState state)
{
StoppableWaitGroup::Ptr wg = new StoppableWaitGroup();
for (auto i = 0UL; i < num; ++i) {
CheckResult::Ptr cr = new CheckResult();
cr->SetState(state);
double now = Utility::GetTime();
cr->SetActive(false);
cr->SetScheduleStart(now);
cr->SetScheduleEnd(now);
cr->SetExecutionStart(now);
cr->SetExecutionEnd(now);
BOOST_REQUIRE(m_Host->ProcessCheckResult(cr, wg) == Checkable::ProcessingResult::Ok);
}
}
double GetLastNotificationTimestamp() { return m_Notification->GetLastNotification(); }
std::uint8_t GetSuppressedNotifications() { return m_Notification->GetSuppressedNotifications(); }
static NotificationType GetLastNotification() { return m_LastNotification; }
static std::size_t GetNotificationCount() { return m_NumNotifications; }
private:
static inline std::mutex m_NotificationMutex;
static inline std::condition_variable m_NotificationCv;
static inline std::size_t m_NumNotifications{};
static inline NotificationType m_LastNotification{};
Host::Ptr m_Host;
Notification::Ptr m_Notification;
TimePeriod::Ptr m_TimePeriod;
Dictionary::Ptr m_AllTimePeriod;
};
BOOST_FIXTURE_TEST_SUITE(notificationcomponent, NotificationComponentFixture);
/* Test sending out reminder notifications in a given interval.
*/
BOOST_AUTO_TEST_CASE(notify_send_reminders)
{
SetNotificationInverval(0.15);
ReceiveCheckResults(2, ServiceCritical);
// The first run of the timer sets up the next reminder notification.
NotificationTimerHandler();
BOOST_REQUIRE(WaitForExpectedNotificationCount(1));
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
// Rerunning the timer before the next interval should not trigger a reminder notification.
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 1);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
// After waiting until the interval has passed, a reminder will be queued.
WaitUntilNextReminderScheduled();
NotificationTimerHandler();
BOOST_REQUIRE(WaitForExpectedNotificationCount(2));
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
// Now we test that reminders are only sent for Critical states.
// Hard state is switched to OK.
ReceiveCheckResults(1, ServiceOK);
NotificationTimerHandler();
BOOST_REQUIRE(WaitForExpectedNotificationCount(3));
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationRecovery);
// Now we wait for one interval and check that no reminder has been sent.
WaitUntilNextReminderScheduled();
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 3);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationRecovery);
}
/* Tests simple sending of notifications on each state change.
*/
BOOST_AUTO_TEST_CASE(notify_simple)
{
BeginTimePeriod();
ReceiveCheckResults(2, ServiceCritical);
NotificationTimerHandler();
BOOST_REQUIRE(WaitForExpectedNotificationCount(1));
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
ReceiveCheckResults(1, ServiceCritical);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 1);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
ReceiveCheckResults(1, ServiceOK);
NotificationTimerHandler();
BOOST_REQUIRE(WaitForExpectedNotificationCount(2));
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationRecovery);
ReceiveCheckResults(2, ServiceOK);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 2);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationRecovery);
}
/* This tests the simplest case where a suppressed notification will be sent after resuming
* a TimePeriod. A single event occurs outside the TimePeriod and the notification should be
* sent as soon as the timer runs after the TimePeriod is resumed.
*/
BOOST_AUTO_TEST_CASE(notify_after_timeperiod_simple)
{
BeginTimePeriod();
ReceiveCheckResults(1, ServiceOK);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 0);
BOOST_REQUIRE_EQUAL(GetLastNotification(), 0);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
EndTimePeriod();
ReceiveCheckResults(3, ServiceCritical);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 0);
BOOST_REQUIRE_EQUAL(GetLastNotification(), 0);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), NotificationProblem);
BeginTimePeriod();
NotificationTimerHandler();
BOOST_REQUIRE(WaitForExpectedNotificationCount(1));
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
}
/* Similar to the test-case above, but has multiple state-changes outside of the TimePeriod
* This is important, since there are multiple places in the code that check on and make modifications
* to the list of suppressed events. A concrete example of a bug like this is #10575.
*/
BOOST_AUTO_TEST_CASE(notify_multiple_state_changes_outside_timeperiod)
{
BOOST_REQUIRE_EQUAL(GetLastNotificationTimestamp(), 0.0);
BeginTimePeriod();
ReceiveCheckResults(2, ServiceCritical);
BOOST_REQUIRE(WaitForExpectedNotificationCount(1));
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
EndTimePeriod();
ReceiveCheckResults(1, ServiceOK);
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 1);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), NotificationRecovery);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 1);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), NotificationRecovery);
ReceiveCheckResults(1, ServiceCritical);
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 1);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), NotificationRecovery);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 1);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), NotificationRecovery);
// Third Critical check result will set the Critical hard state.
ReceiveCheckResults(2, ServiceCritical);
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 1);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 1);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
ReceiveCheckResults(1, ServiceOK);
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 1);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), NotificationRecovery);
BeginTimePeriod();
NotificationTimerHandler();
BOOST_REQUIRE(WaitForExpectedNotificationCount(2));
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationRecovery);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
}
/* This tests if suppressed notifications of opposite types cancel each other out.
*/
BOOST_AUTO_TEST_CASE(no_notify_suppressed_cancel_out)
{
BOOST_REQUIRE_EQUAL(GetLastNotificationTimestamp(), 0.0);
BeginTimePeriod();
ReceiveCheckResults(1, ServiceOK);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 0);
BOOST_REQUIRE_EQUAL(GetLastNotification(), 0);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
EndTimePeriod();
ReceiveCheckResults(3, ServiceCritical);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 0);
BOOST_REQUIRE_EQUAL(GetLastNotification(), 0);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), NotificationProblem);
ReceiveCheckResults(1, ServiceOK);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 0);
BOOST_REQUIRE_EQUAL(GetLastNotification(), 0);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
BeginTimePeriod();
// Ensure no notification is sent after resuming the TimePeriod.
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 0);
BOOST_REQUIRE_EQUAL(GetLastNotification(), 0);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
// Now repeat the same starting from a Critical state
ReceiveCheckResults(3, ServiceCritical);
NotificationTimerHandler();
BOOST_REQUIRE(WaitForExpectedNotificationCount(1));
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
EndTimePeriod();
ReceiveCheckResults(1, ServiceOK);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 1);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), NotificationRecovery);
ReceiveCheckResults(3, ServiceCritical);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 1);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
BeginTimePeriod();
// Ensure no notification is sent after resuming the TimePeriod.
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 1);
BOOST_REQUIRE_EQUAL(GetLastNotification(), NotificationProblem);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
}
/* This may look similar to the test-case above, but the critical difference is that here
* the final state change happens inside the TimePeriod again, but before the timer runs.
* The outdated suppressed NotificationProblem will then be subtracted when the timer runs.
*/
BOOST_AUTO_TEST_CASE(no_notify_non_applicable_reason)
{
BOOST_REQUIRE_EQUAL(GetLastNotificationTimestamp(), 0.0);
BeginTimePeriod();
ReceiveCheckResults(1, ServiceOK);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 0);
BOOST_REQUIRE_EQUAL(GetLastNotification(), 0);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
EndTimePeriod();
// We queue a suppressed notification.
ReceiveCheckResults(3, ServiceCritical);
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 0);
BOOST_REQUIRE_EQUAL(GetLastNotification(), 0);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), NotificationProblem);
BeginTimePeriod();
// In this scenario a check result that goes against the suppressed notification is processed
// before the timer can run again. No notification should be sent, because the last state
// change the user was notified about was the same.
ReceiveCheckResults(1, ServiceOK);
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 0);
BOOST_REQUIRE_EQUAL(GetLastNotification(), 0);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), NotificationProblem);
// When the timer runs, it should clear the suppressed notification but not send anything.
NotificationTimerHandler();
BOOST_REQUIRE(AssertNoAttemptedSendLogPattern());
BOOST_REQUIRE_EQUAL(GetNotificationCount(), 0);
BOOST_REQUIRE_EQUAL(GetLastNotification(), 0);
BOOST_REQUIRE_EQUAL(GetSuppressedNotifications(), 0);
}
BOOST_AUTO_TEST_SUITE_END()