#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 #include #include #include #include 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 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 && high_result.succeeded && low_result.succeeded, "M, D high and D low alarm definitions 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"); 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.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, high_result.id) != nullptr && findRecord(alarm_service, low_result.id) != nullptr, "all three 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.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"); } } // namespace int main() { try { testAlarmDefinitionsAndRuntimeLifecycle(); } 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; }