|
- #include "domain/project_storage.h"
- #include "domain/register_repository.h"
- #include "domain/virtual_register_repository.h"
- #include "services/alarm_editor_service.h"
- #include "services/alarm_service.h"
- #include "services/project_service.h"
- #include "support/test_support.h"
-
- #include <algorithm>
- #include <exception>
- #include <iostream>
- #include <stdexcept>
- #include <string>
-
- namespace {
-
- using TestProjectStorage = TestSupport::InMemoryProjectStorage;
- using TestSupport::require;
-
- const AlarmRecord *findRecord(
- const AlarmService &service, const std::string &definition_id)
- {
- const auto &records = service.records();
- const auto record = std::find_if(
- records.cbegin(), records.cend(),
- [&definition_id](const AlarmRecord &candidate)
- {
- return candidate.definitionId == definition_id;
- });
- return record == records.cend() ? nullptr : &*record;
- }
-
- void testAlarmDefinitionsAndRuntimeLifecycle()
- {
- TestProjectStorage storage;
- ProjectService project_service(storage);
- AlarmEditorService editor_service(project_service);
- VirtualRegisterRepository repository;
- AlarmService alarm_service(project_service, repository);
-
- AlarmDefinition m_alarm;
- m_alarm.address = RegisterAddress{RegisterArea::M, 0};
- m_alarm.condition = AlarmCondition::MOn;
- m_alarm.message = "Emergency stop";
- const AlarmEditorResult m_result = editor_service.addDefinition(m_alarm);
-
- AlarmDefinition m_off_alarm;
- m_off_alarm.address = RegisterAddress{RegisterArea::M, 1};
- m_off_alarm.condition = AlarmCondition::MOff;
- m_off_alarm.message = "Safety circuit open";
- const AlarmEditorResult m_off_result = editor_service.addDefinition(
- m_off_alarm);
-
- AlarmDefinition high_alarm;
- high_alarm.address = RegisterAddress{RegisterArea::D, 0};
- high_alarm.condition = AlarmCondition::DHigh;
- high_alarm.threshold = 80;
- high_alarm.message = "Temperature high";
- const AlarmEditorResult high_result = editor_service.addDefinition(high_alarm);
-
- AlarmDefinition low_alarm;
- low_alarm.address = RegisterAddress{RegisterArea::D, 1};
- low_alarm.condition = AlarmCondition::DLow;
- low_alarm.threshold = 10;
- low_alarm.message = "Pressure low";
- const AlarmEditorResult low_result = editor_service.addDefinition(low_alarm);
- require(m_result.succeeded && m_off_result.succeeded
- && high_result.succeeded && low_result.succeeded,
- "M ON, M OFF, D high and D low alarms must be accepted");
-
- AlarmDefinition invalid = high_alarm;
- invalid.address = RegisterAddress{RegisterArea::M, 2};
- require(editor_service.addDefinition(invalid).error
- == AlarmEditorError::InvalidDefinition,
- "D alarm conditions must reject M addresses");
-
- invalid = m_off_alarm;
- invalid.address = RegisterAddress{RegisterArea::D, 2};
- require(editor_service.addDefinition(invalid).error
- == AlarmEditorError::InvalidDefinition,
- "M OFF alarm conditions must reject D addresses");
-
- repository.writeBit(RegisterAddress{RegisterArea::M, 1}, true);
- repository.writeWord(RegisterAddress{RegisterArea::D, 1}, 20);
- alarm_service.refresh();
- require(alarm_service.records().empty(),
- "inactive alarm conditions must not create records");
-
- repository.writeBit(RegisterAddress{RegisterArea::M, 0}, true);
- repository.writeBit(RegisterAddress{RegisterArea::M, 1}, false);
- repository.writeWord(RegisterAddress{RegisterArea::D, 0}, 80);
- repository.writeWord(RegisterAddress{RegisterArea::D, 1}, 10);
- alarm_service.refresh();
- require(findRecord(alarm_service, m_result.id) != nullptr
- && findRecord(alarm_service, m_off_result.id) != nullptr
- && findRecord(alarm_service, high_result.id) != nullptr
- && findRecord(alarm_service, low_result.id) != nullptr,
- "all four alarm conditions must create active records");
- require(alarm_service.acknowledge(m_result.id)
- && findRecord(alarm_service, m_result.id)->acknowledged,
- "an active alarm must be locally acknowledged");
-
- repository.writeBit(RegisterAddress{RegisterArea::M, 0}, false);
- repository.writeBit(RegisterAddress{RegisterArea::M, 1}, true);
- repository.writeWord(RegisterAddress{RegisterArea::D, 0}, 79);
- repository.writeWord(RegisterAddress{RegisterArea::D, 1}, 11);
- alarm_service.refresh();
- require(alarm_service.records().empty(),
- "cleared conditions must immediately remove active alarms");
-
- repository.writeBit(RegisterAddress{RegisterArea::M, 0}, true);
- alarm_service.refresh();
- require(findRecord(alarm_service, m_result.id) != nullptr
- && alarm_service.records().size() == 1
- && !alarm_service.records().front().acknowledged,
- "a repeated alarm must create a new unacknowledged occurrence");
-
- alarm_service.reset();
- require(alarm_service.records().empty(),
- "leaving runtime must clear session alarm records");
- }
-
- void testConfiguredAlarmLimit()
- {
- TestProjectStorage storage;
- ProjectLimitSettings limits;
- limits.maximumAlarmDefinitions = 0U;
- ProjectService project_service(storage, limits);
- AlarmEditorService editor_service(project_service);
-
- AlarmDefinition definition;
- definition.address = RegisterAddress{RegisterArea::M, 0};
- definition.condition = AlarmCondition::MOn;
- definition.message = "Disabled by configuration";
- require(!editor_service.addDefinition(definition).succeeded,
- "the alarm editor must use the configured definition limit");
- }
-
- } // namespace
-
- int main()
- {
- try
- {
- testAlarmDefinitionsAndRuntimeLifecycle();
- testConfiguredAlarmLimit();
- }
- catch (const std::exception &error)
- {
- std::cerr << "alarm service tests failed: " << error.what() << '\n';
- return 1;
- }
-
- std::cout << "alarm service tests passed\n";
- return 0;
- }
|