hardware: push alarm-inventory alarms in one batch

This reduces the first initial push to alarm-inventory from 4 edits to
1.

What a simple diff in src/ and what a mess under tests/.

Change-Id: I07f38e529341f2bcdcc51e2e9562b641971c553e
diff --git a/src/health/SystemdUnits.cpp b/src/health/SystemdUnits.cpp
index 105c56a..ecb8c61 100644
--- a/src/health/SystemdUnits.cpp
+++ b/src/health/SystemdUnits.cpp
@@ -42,7 +42,7 @@
     // First, fetch all currently loaded units, register to their PropertiesChanged signal and create the alarm-inventory entries in a *single* edit
     m_proxyManager->callMethod("ListUnits").onInterface(managerIface).storeResultsTo(units);
     std::transform(units.begin(), units.end(), std::back_inserter(unitNames), [](const auto& unit) { return unit.template get<0>(); });
-    alarms::pushInventory(m_srSession, ALARM_ID, ALARM_INVENTORY_DESCRIPTION, unitNames, {ALARM_SEVERITY});
+    alarms::pushInventory(m_srSession, {{ALARM_ID, ALARM_INVENTORY_DESCRIPTION, unitNames, {ALARM_SEVERITY}}});
 
     for (const auto& unit : units) {
         registerSystemdUnit(connection, unit.get<0>(), unit.get<6>(), UnitState{unit.get<3>(), unit.get<4>()}, RegisterAlarmInventory::No);
diff --git a/src/ietf-hardware/sysrepo/Sysrepo.cpp b/src/ietf-hardware/sysrepo/Sysrepo.cpp
index 02e6720..7381b70 100644
--- a/src/ietf-hardware/sysrepo/Sysrepo.cpp
+++ b/src/ietf-hardware/sysrepo/Sysrepo.cpp
@@ -95,10 +95,14 @@
         std::map<std::string, State> thresholdsStates;
         std::set<std::pair<std::string, std::string>> activeSideLoadedAlarms;
 
-        alarms::pushInventory(m_session, ALARM_THRESHOLD_CROSSING_LOW, "Sensor value is below the low threshold.", {});
-        alarms::pushInventory(m_session, ALARM_THRESHOLD_CROSSING_HIGH, "Sensor value is above the high threshold.", {});
-        alarms::pushInventory(m_session, ALARM_SENSOR_MISSING, "Sensor is missing.", {});
-        alarms::pushInventory(m_session, ALARM_SENSOR_NONOPERATIONAL, "Sensor is flagged as nonoperational.", {});
+        alarms::pushInventory(
+            m_session,
+            {
+                {ALARM_THRESHOLD_CROSSING_LOW, "Sensor value is below the low threshold."},
+                {ALARM_THRESHOLD_CROSSING_HIGH, "Sensor value is above the high threshold."},
+                {ALARM_SENSOR_MISSING, "Sensor is missing."},
+                {ALARM_SENSOR_NONOPERATIONAL, "Sensor is flagged as nonoperational."},
+            });
 
         while (!m_quit) {
             m_log->trace("IetfHardware poll");
diff --git a/src/utils/alarms.cpp b/src/utils/alarms.cpp
index f079f8d..67d57b6 100644
--- a/src/utils/alarms.cpp
+++ b/src/utils/alarms.cpp
@@ -30,21 +30,23 @@
     session.sendRPC(inputNode);
 }
 
-void pushInventory(sysrepo::Session session, const std::string& alarmId, const std::string& description, const std::vector<std::string>& resources, const std::vector<std::string>& severities, WillClear willClear)
+void pushInventory(sysrepo::Session session, const std::vector<AlarmInventoryEntry>& entries)
 {
-    const auto prefix = alarmInventory + "/alarm-type[alarm-type-id='" + alarmId + "'][alarm-type-qualifier='']";
-
     utils::ScopedDatastoreSwitch s(session, sysrepo::Datastore::Operational);
 
-    session.setItem(prefix + "/will-clear", willClear == WillClear::Yes ? "true" : "false");
-    session.setItem(prefix + "/description", description);
+    for (const auto& entry: entries) {
+        const auto prefix = alarmInventory + "/alarm-type[alarm-type-id='" + entry.alarmType + "'][alarm-type-qualifier='']";
 
-    for (const auto& severity : severities) {
-        session.setItem(prefix + "/severity-level", severity);
-    }
+        session.setItem(prefix + "/will-clear", entry.willClear == WillClear::Yes ? "true" : "false");
+        session.setItem(prefix + "/description", entry.description);
 
-    for (const auto& resource : resources) {
-        session.setItem(prefix + "/resource", resource);
+        for (const auto& severity : entry.severities) {
+            session.setItem(prefix + "/severity-level", severity);
+        }
+
+        for (const auto& resource : entry.resources) {
+            session.setItem(prefix + "/resource", resource);
+        }
     }
 
     session.applyChanges();
@@ -63,4 +65,13 @@
     }
     session.applyChanges();
 }
+
+AlarmInventoryEntry::AlarmInventoryEntry(const std::string& alarmType, const std::string& description, const std::vector<std::string>& resources, const std::vector<std::string>& severities, WillClear willClear)
+    : alarmType(alarmType)
+    , description(description)
+    , resources(resources)
+    , severities(severities)
+    , willClear(willClear)
+{
+}
 }
diff --git a/src/utils/alarms.h b/src/utils/alarms.h
index 9f5cbbc..1531695 100644
--- a/src/utils/alarms.h
+++ b/src/utils/alarms.h
@@ -15,7 +15,17 @@
     Yes,
 };
 
+struct AlarmInventoryEntry {
+    std::string alarmType;
+    std::string description;
+    std::vector<std::string> resources;
+    std::vector<std::string> severities;
+    WillClear willClear;
+
+    AlarmInventoryEntry(const std::string& alarmType, const std::string& description, const std::vector<std::string>& resources = {}, const std::vector<std::string>& severities = {}, WillClear willClear = WillClear::Yes);
+};
+
 void push(sysrepo::Session session, const std::string& alarmId, const std::string& alarmResource, const std::string& severity, const std::string& alarmText);
-void pushInventory(sysrepo::Session session, const std::string& alarmId, const std::string& description, const std::vector<std::string>& resources, const std::vector<std::string>& severities = {}, WillClear willClear = WillClear::Yes);
+void pushInventory(sysrepo::Session session, const std::vector<AlarmInventoryEntry>& alarms);
 void addResourcesToInventory(sysrepo::Session session, const std::map<std::string, std::vector<std::string>>& resourcesPerAlarm);
 }
diff --git a/tests/health_systemd-units.cpp b/tests/health_systemd-units.cpp
index 5eaf354..da880df 100644
--- a/tests/health_systemd-units.cpp
+++ b/tests/health_systemd-units.cpp
@@ -21,6 +21,7 @@
 
 using namespace std::chrono_literals;
 
+#define VEC(...) (std::vector<std::string>{__VA_ARGS__})
 #define REQUIRE_NEW_ALARM_INVENTORY_UNIT(UNIT) \
     REQUIRE_NEW_ALARM_INVENTORY_RESOURCES(alarmsWatcher, (std::vector<std::string>{"velia-alarms:systemd-unit-failure"}), std::vector<std::string>{UNIT})
 
@@ -46,12 +47,13 @@
     client.switchDatastore(sysrepo::Datastore::Operational);
     AlarmWatcher alarmsWatcher(client);
 
-    REQUIRE_NEW_ALARM_INVENTORY_ENTRY(alarmsWatcher,
-                                      "velia-alarms:systemd-unit-failure",
-                                      (std::vector<std::string>{"unit1.service", "unit2.service", "unit3.service"}),
-                                      (std::vector<std::string>{"critical"}),
-                                      true,
-                                      "The systemd service is considered in failed state.");
+    REQUIRE_NEW_ALARM_INVENTORY_ENTRIES(alarmsWatcher,
+                                        (std::vector<velia::alarms::AlarmInventoryEntry>{{
+                                            "velia-alarms:systemd-unit-failure",
+                                            "The systemd service is considered in failed state.",
+                                            VEC("unit1.service", "unit2.service", "unit3.service"),
+                                            VEC("critical"),
+                                        }}));
 
     REQUIRE_ALARM_RPC("unit1.service", "cleared", "systemd unit state: (active, running)");
     server.createUnit(*serverConnection, "unit1.service", "/org/freedesktop/systemd1/unit/unit1", "active", "running");
diff --git a/tests/sysrepo-helpers/alarms.cpp b/tests/sysrepo-helpers/alarms.cpp
index 14fc323..6e0e00d 100644
--- a/tests/sysrepo-helpers/alarms.cpp
+++ b/tests/sysrepo-helpers/alarms.cpp
@@ -21,6 +21,12 @@
         alarm.severities.insert(severities.begin(), severities.end());
     }
 }
+void AlarmWatcher::AlarmInventory::add(const std::vector<velia::alarms::AlarmInventoryEntry>& entries)
+{
+    for (const auto& e : entries) {
+        add({e.alarmType}, e.resources, e.severities);
+    }
+}
 
 bool AlarmWatcher::AlarmInventory::contains(const std::string& alarmTypeId, const std::optional<std::string>& resource, const std::optional<std::string>& severity) const
 {
diff --git a/tests/sysrepo-helpers/alarms.h b/tests/sysrepo-helpers/alarms.h
index 87c17db..6c55b10 100644
--- a/tests/sysrepo-helpers/alarms.h
+++ b/tests/sysrepo-helpers/alarms.h
@@ -14,35 +14,30 @@
 #include "sysrepo-helpers/datastore.h"
 #include "sysrepo-helpers/rpc.h"
 #include "test_log_setup.h"
+#include "utils/alarms.h"
 
-inline ValueChanges constructAlarmInventoryChange(const std::string& alarmType,
-                                                  const std::vector<std::string>& resources,
-                                                  const std::vector<std::string>& severities,
-                                                  const std::optional<bool>& willClear,
-                                                  const std::optional<std::string>& description)
+inline ValueChanges constructAlarmInventoryChange(const std::vector<velia::alarms::AlarmInventoryEntry>& entries)
 {
-    const std::string prefix = "/ietf-alarms:alarms/alarm-inventory/alarm-type[alarm-type-id='" + alarmType + "'][alarm-type-qualifier='']";
-
     ValueChanges ret;
-    ret.emplace(prefix + "/alarm-type-id", alarmType);
-    ret.emplace(prefix + "/alarm-type-qualifier", "");
 
-    if (willClear) {
-        ret.emplace(prefix + "/will-clear", *willClear ? "true" : "false");
-    }
+    for (const auto& entry : entries) {
+        const std::string prefix = "/ietf-alarms:alarms/alarm-inventory/alarm-type[alarm-type-id='" + entry.alarmType + "'][alarm-type-qualifier='']";
 
-    if (description) {
-        ret.emplace(prefix + "/description", *description);
-    }
+        ret.emplace(prefix + "/alarm-type-id", entry.alarmType);
+        ret.emplace(prefix + "/alarm-type-qualifier", "");
 
-    size_t i = 1; /* YANG uses 1-based indexing */
-    for (const auto& severity : severities) {
-        ret.emplace(prefix + "/severity-level[" + std::to_string(i++) + "]", severity);
-    }
+        ret.emplace(prefix + "/will-clear", entry.willClear ? "true" : "false");
+        ret.emplace(prefix + "/description", entry.description);
 
-    i = 1;
-    for (const auto& resource : resources) {
-        ret.emplace(prefix + "/resource[" + std::to_string(i++) + "]", resource);
+        size_t i = 1; /* YANG uses 1-based indexing */
+        for (const auto& severity : entry.severities) {
+            ret.emplace(prefix + "/severity-level[" + std::to_string(i++) + "]", severity);
+        }
+
+        i = 1;
+        for (const auto& resource : entry.resources) {
+            ret.emplace(prefix + "/resource[" + std::to_string(i++) + "]", resource);
+        }
     }
 
     return ret;
@@ -77,6 +72,7 @@
         std::map<AlarmType, AllowedResourcesAndSeverities> inventory;
 
         void add(const std::vector<std::string>& alarmTypeIds, const std::vector<std::string>& resources, const std::vector<std::string>& severities);
+        void add(const std::vector<velia::alarms::AlarmInventoryEntry>& entries);
         bool contains(const std::string& alarmTypeId, const std::optional<std::string>& resource, const std::optional<std::string>& severity) const;
     };
 
@@ -94,10 +90,12 @@
 // inserts the alarm in AlarmInventory as a side effect
 #define INSERT_INTO_INVENTORY(INV, ALARM_TYPES, RESOURCES, SEVERITIES) \
     LR_SIDE_EFFECT(INV.add(ALARM_TYPES, RESOURCES, SEVERITIES))
+#define INSERT_INTO_INVENTORY_MANY(INV, ALARMS) \
+    LR_SIDE_EFFECT(INV.add(ALARMS))
 
-#define REQUIRE_NEW_ALARM_INVENTORY_ENTRY(WATCHER, ALARM_TYPE, RESOURCES, SEVERITIES, WILL_CLEAR, DESCRIPTION) \
-    REQUIRE_DATASTORE_CHANGE(WATCHER.datastoreWatcher, constructAlarmInventoryChange(ALARM_TYPE, RESOURCES, SEVERITIES, WILL_CLEAR, DESCRIPTION)) \
-        .INSERT_INTO_INVENTORY(WATCHER.alarmInventory, (std::vector<std::string>{ALARM_TYPE}), RESOURCES, SEVERITIES)
+#define REQUIRE_NEW_ALARM_INVENTORY_ENTRIES(WATCHER, ALARMS) \
+    REQUIRE_DATASTORE_CHANGE(WATCHER.datastoreWatcher, constructAlarmInventoryChange(ALARMS)) \
+        .INSERT_INTO_INVENTORY_MANY(WATCHER.alarmInventory, ALARMS)
 
 #define REQUIRE_NEW_ALARM_INVENTORY_RESOURCES(WATCHER, ALARM_TYPES, RESOURCES) \
     REQUIRE_DATASTORE_CHANGE(WATCHER.datastoreWatcher, constructAlarmInventoryResourceChange(ALARM_TYPES, RESOURCES)) \
diff --git a/tests/sysrepo_ietf-hardware.cpp b/tests/sysrepo_ietf-hardware.cpp
index c3689da..b03444a 100644
--- a/tests/sysrepo_ietf-hardware.cpp
+++ b/tests/sysrepo_ietf-hardware.cpp
@@ -15,14 +15,12 @@
 
 #define COMPONENT(RESOURCE) "/ietf-hardware:hardware/component[name='" RESOURCE "']"
 
-#define REQUIRE_ALARM_INVENTORY_ADD_ALARM(ALARM_TYPE, DESCRIPTION) \
-    REQUIRE_NEW_ALARM_INVENTORY_ENTRY(alarmWatcher, ALARM_TYPE, (std::vector<std::string>{}), \
-            (std::vector<std::string>{}), true, DESCRIPTION)
-
 #define VEC(...) (std::vector<std::string>{__VA_ARGS__})
 #define ALARMS(...) VEC(__VA_ARGS__)
 #define COMPONENTS(...) VEC(__VA_ARGS__)
 
+#define INTRODUCED_ALARM(ALARM_TYPE, DESCRIPTION) velia::alarms::AlarmInventoryEntry(ALARM_TYPE, DESCRIPTION)
+#define REQUIRE_ALARM_INVENTORY_ADD_ALARMS(...) REQUIRE_NEW_ALARM_INVENTORY_ENTRIES(alarmWatcher, (std::vector<velia::alarms::AlarmInventoryEntry>{__VA_ARGS__}))
 #define REQUIRE_ALARM_INVENTORY_ADD_RESOURCES(ALARMS, COMPONENTS) REQUIRE_NEW_ALARM_INVENTORY_RESOURCES(alarmWatcher, ALARMS, COMPONENTS)
 
 #define REQUIRE_ALARM_RPC(ALARM_TYPE, RESOURCE, SEVERITY, TEXT) \
@@ -141,10 +139,12 @@
         REQUIRE_CALL(*sysfsTempCpu, attribute("temp1_input")).LR_RETURN(cpuTempValue).TIMES(AT_LEAST(1));
         REQUIRE_CALL(*sysfsPower, attribute("power1_input")).LR_RETURN(powerValue).TIMES(AT_LEAST(1));
 
-        REQUIRE_ALARM_INVENTORY_ADD_ALARM("velia-alarms:sensor-low-value-alarm", "Sensor value is below the low threshold.").IN_SEQUENCE(seq1);
-        REQUIRE_ALARM_INVENTORY_ADD_ALARM("velia-alarms:sensor-high-value-alarm", "Sensor value is above the high threshold.").IN_SEQUENCE(seq1);
-        REQUIRE_ALARM_INVENTORY_ADD_ALARM("velia-alarms:sensor-missing-alarm", "Sensor is missing.").IN_SEQUENCE(seq1);
-        REQUIRE_ALARM_INVENTORY_ADD_ALARM("velia-alarms:sensor-nonoperational", "Sensor is flagged as nonoperational.").IN_SEQUENCE(seq1);
+        REQUIRE_ALARM_INVENTORY_ADD_ALARMS(
+            INTRODUCED_ALARM("velia-alarms:sensor-low-value-alarm", "Sensor value is below the low threshold."),
+            INTRODUCED_ALARM("velia-alarms:sensor-high-value-alarm", "Sensor value is above the high threshold."),
+            INTRODUCED_ALARM("velia-alarms:sensor-missing-alarm", "Sensor is missing."),
+            INTRODUCED_ALARM("velia-alarms:sensor-nonoperational", "Sensor is flagged as nonoperational."))
+            .IN_SEQUENCE(seq1);
 
         REQUIRE_ALARM_INVENTORY_ADD_RESOURCES(
             ALARMS("velia-alarms:sensor-low-value-alarm",
@@ -349,10 +349,12 @@
         psuSensorValue = 12000;
         REQUIRE_CALL(*sysfsTempCpu, attribute("temp1_input")).LR_RETURN(cpuTempValue).TIMES(AT_LEAST(1));
         REQUIRE_CALL(*sysfsPower, attribute("power1_input")).LR_RETURN(powerValue).TIMES(AT_LEAST(1));
-        REQUIRE_ALARM_INVENTORY_ADD_ALARM("velia-alarms:sensor-low-value-alarm", "Sensor value is below the low threshold.").IN_SEQUENCE(seq1);
-        REQUIRE_ALARM_INVENTORY_ADD_ALARM("velia-alarms:sensor-high-value-alarm", "Sensor value is above the high threshold.").IN_SEQUENCE(seq1);
-        REQUIRE_ALARM_INVENTORY_ADD_ALARM("velia-alarms:sensor-missing-alarm", "Sensor is missing.").IN_SEQUENCE(seq1);
-        REQUIRE_ALARM_INVENTORY_ADD_ALARM("velia-alarms:sensor-nonoperational", "Sensor is flagged as nonoperational.").IN_SEQUENCE(seq1);
+        REQUIRE_ALARM_INVENTORY_ADD_ALARMS(
+            INTRODUCED_ALARM("velia-alarms:sensor-low-value-alarm", "Sensor value is below the low threshold."),
+            INTRODUCED_ALARM("velia-alarms:sensor-high-value-alarm", "Sensor value is above the high threshold."),
+            INTRODUCED_ALARM("velia-alarms:sensor-missing-alarm", "Sensor is missing."),
+            INTRODUCED_ALARM("velia-alarms:sensor-nonoperational", "Sensor is flagged as nonoperational."))
+            .IN_SEQUENCE(seq1);
 
         REQUIRE_ALARM_INVENTORY_ADD_RESOURCES(
             ALARMS("velia-alarms:sensor-low-value-alarm",