def setup_class(self) -> None: #pylint: disable=W0201 self.appInfo = AppInfo() settingsFilenameOrig = os.path.join(self.appInfo.path, "misc/setup/situationboard_default.conf") settingsFilename = os.path.join(self.appInfo.path, ".temp/situationboard.conf") databaseFilename = os.path.join(self.appInfo.path, ".temp/situationboard.sqlite") shutil.copy(settingsFilenameOrig, settingsFilename) self.database = Database(databaseFilename, reset = True) settings = Settings(settingsFilename, self.appInfo.path) settings.setFrontendHeader("header") settings.setFrontendNews("news") settings.setBoolean(Settings.SECTION_BACKEND, "web_api", True) displayPowerManager = DisplayPowerManager(settings) self.webSocket = WebSocket(self.appInfo, settings, self.database) pluginManager = PluginManager(settings, self.database, self.webSocket, displayPowerManager) self.webSocket.init(pluginManager) self.appClient = self.webSocket.app_test_client() self.socketClient = self.webSocket.socket_test_client(self.appClient)
def __init__(self, appInfo: AppInfo, settings: Settings, database: Database): super().__init__("situationboard", settings) self.appInfo = appInfo self.settings = settings self.database = database self.displayPowerManager = DisplayPowerManager(self.settings) self.webSocket = WebSocket(self.appInfo, self.settings, self.database) self.pluginManager = PluginManager(self.settings, self.database, self.webSocket, self.displayPowerManager) signal.signal(signal.SIGTERM, self.__shutdownHandler ) # a SIGTERM signal causes the backend to shutdown signal.signal(signal.SIGINT, self.__shutdownHandler ) # a SIGINT signal causes the backend to shutdown sys.stdin.close()
def test_handle_event(self) -> None: #pylint: disable=W0201 appInfo = AppInfo() settingsFilenameOrig = os.path.join( appInfo.path, "misc/setup/situationboard_default.conf") settingsFilename = os.path.join(appInfo.path, ".temp/situationboard.conf") databaseFilename = os.path.join(appInfo.path, ".temp/situationboard.sqlite") shutil.copy(settingsFilenameOrig, settingsFilename) settings = Settings(settingsFilename, appInfo.path) maxLastEvents = 10 displayPowerManager = DisplayPowerManager(settings) db = Database(databaseFilename, reset=True) webSocket = WebSocket(appInfo, settings, db) pluginManager = PluginManager(settings, db, webSocket, displayPowerManager) webSocket.init(pluginManager) # check that creating a database works and yields an empty database assert (db.getEventCount(textOnly=False) == 0) assert (db.getEventCount(textOnly=True) == 0) assert (db.getEvent(1302) is None) assert (len(db.getEvents(textOnly=False)) == 0) assert (len(db.getEvents(textOnly=True)) == 0) assert (len(db.getLastEvents(maxLastEvents, textOnly=False)) == 0) assert (len(db.getLastEvents(maxLastEvents, textOnly=True)) == 0) # check for valid stats assert (db.getEventStats(DatabaseTimespan.TOTAL, textOnly=False) == 0) assert (db.getEventStats(DatabaseTimespan.YEAR, textOnly=False) == 0) assert (db.getEventStats(DatabaseTimespan.MONTH, textOnly=False) == 0) assert (db.getEventStats(DatabaseTimespan.TODAY, textOnly=False) == 0) assert (db.getEventStats(DatabaseTimespan.TOTAL, textOnly=True) == 0) assert (db.getEventStats(DatabaseTimespan.YEAR, textOnly=True) == 0) assert (db.getEventStats(DatabaseTimespan.MONTH, textOnly=True) == 0) assert (db.getEventStats(DatabaseTimespan.TODAY, textOnly=True) == 0) action = ActionUpdateDatabase("", settings, db, webSocket) # check that inserting an alarm event succeeds newEvent = self.__createEvent() action.handleEvent(newEvent) assert (not newEvent.noID) assert (db.getEventCount(textOnly=False) == 1) assert (db.getEventCount(textOnly=True) == 1) assert (len(db.getEvents(textOnly=False)) == 1) assert (len(db.getEvents(textOnly=True)) == 1) assert (len(db.getLastEvents(maxLastEvents, textOnly=False)) == 1) assert (len(db.getLastEvents(maxLastEvents, textOnly=True)) == 1) newEventID = newEvent.eventID assert (db.getEvent(newEventID) is not None) # check for valid stats assert (db.getEventStats(DatabaseTimespan.TOTAL, textOnly=False) == 1) assert (db.getEventStats(DatabaseTimespan.YEAR, textOnly=False) == 1) assert (db.getEventStats(DatabaseTimespan.MONTH, textOnly=False) == 1) assert (db.getEventStats(DatabaseTimespan.TODAY, textOnly=False) == 1) assert (db.getEventStats(DatabaseTimespan.TOTAL, textOnly=True) == 1) assert (db.getEventStats(DatabaseTimespan.YEAR, textOnly=True) == 1) assert (db.getEventStats(DatabaseTimespan.MONTH, textOnly=True) == 1) assert (db.getEventStats(DatabaseTimespan.TODAY, textOnly=True) == 1) # check that updating an alarm event succeeds self.__updateEvent(newEvent) action.handleEvent(newEvent) assert (newEvent.eventID == newEventID) assert (db.getEventCount(textOnly=False) == 1) assert (db.getEventCount(textOnly=True) == 1) assert (len(db.getEvents(textOnly=False)) == 1) assert (len(db.getEvents(textOnly=True)) == 1) assert (len(db.getLastEvents(maxLastEvents, textOnly=False)) == 1) assert (len(db.getLastEvents(maxLastEvents, textOnly=True)) == 1) retrievedEvent = db.getEvent(newEventID) assert (retrievedEvent is not None) assert (retrievedEvent.comment == Test_ActionUpdateDatabase.UPDATED_COMMENT) # check for valid stats assert (db.getEventStats(DatabaseTimespan.TOTAL, textOnly=False) == 1) assert (db.getEventStats(DatabaseTimespan.YEAR, textOnly=False) == 1) assert (db.getEventStats(DatabaseTimespan.MONTH, textOnly=False) == 1) assert (db.getEventStats(DatabaseTimespan.TODAY, textOnly=False) == 1) assert (db.getEventStats(DatabaseTimespan.TOTAL, textOnly=True) == 1) assert (db.getEventStats(DatabaseTimespan.YEAR, textOnly=True) == 1) assert (db.getEventStats(DatabaseTimespan.MONTH, textOnly=True) == 1) assert (db.getEventStats(DatabaseTimespan.TODAY, textOnly=True) == 1) db.commitAndClose()
class SituationBoardBackend(Module): """The SituationBoardBackend class represents the core of the backend service. It initializes the different subsystems and implements core application logic like the event loop that is responsible for retrieving and handling of alarm events.""" def __init__(self, appInfo: AppInfo, settings: Settings, database: Database): super().__init__("situationboard", settings) self.appInfo = appInfo self.settings = settings self.database = database self.displayPowerManager = DisplayPowerManager(self.settings) self.webSocket = WebSocket(self.appInfo, self.settings, self.database) self.pluginManager = PluginManager(self.settings, self.database, self.webSocket, self.displayPowerManager) signal.signal(signal.SIGTERM, self.__shutdownHandler ) # a SIGTERM signal causes the backend to shutdown signal.signal(signal.SIGINT, self.__shutdownHandler ) # a SIGINT signal causes the backend to shutdown sys.stdin.close() def __shutdownHandler( self, signum: signal.Signals, frame: Optional[Any]) -> None: #pylint: disable=no-member """The __shutdownHandler handles the SIGTERM/SIGINT signal and terminates the backend.""" self.clrPrint("Terminating backend") # database is committed and closed automatically (via atexit) sys.exit(0) def backgroundTask(self) -> None: """Background thread / event loop that retrieves SourceEvents from configured SourceDrivers and handles them by passing them to all the configured Action plugins. Those plugins in turn may perform various actions (like updating the frontend of web clients). When no SourceEvents are retrieved, Action plugins are allowed to perform housekeeping operations instead.""" sleepDuration = self.settings.getBackendLoopSleepDuration() while True: # wait for alarms or setting event messages, parse them and handle them message = self.pluginManager.retrieveEvent() if message is not None: # handle source event by triggering all required actions / event handlers ... # new alarm -> add to database and send an async alarm event to frontend # new setting -> update setting and send an async update event to frontend self.pluginManager.handleEvent(message) else: # perform housekeeping tasks ... housekeepingStart = time.time() self.pluginManager.handleCyclic() housekeepingEnd = time.time() # maybe sleep some time ... housekeepingDuration = housekeepingEnd - housekeepingStart if housekeepingDuration < sleepDuration: remainingDuration = sleepDuration - housekeepingDuration self.webSocket.sleep(round(remainingDuration)) def run(self) -> None: # Read all the configured plugins from the configuration file and initialize them self.print("Initializing all configured plugins") self.pluginManager.initPlugins() # Initialize websocket interface and register all endpoints self.print("Initializing websocket interface") self.webSocket.init(self.pluginManager) # Start the background task with the event loop that retrieves events and handles them self.print("Starting background task") self.webSocket.start_background_task(self.backgroundTask) # Start the webserver and the websocket API for the frontend (does not return) self.print("Starting websocket interface") self.webSocket.run()
class Test_WebSocket: def setup_class(self) -> None: #pylint: disable=W0201 self.appInfo = AppInfo() settingsFilenameOrig = os.path.join(self.appInfo.path, "misc/setup/situationboard_default.conf") settingsFilename = os.path.join(self.appInfo.path, ".temp/situationboard.conf") databaseFilename = os.path.join(self.appInfo.path, ".temp/situationboard.sqlite") shutil.copy(settingsFilenameOrig, settingsFilename) self.database = Database(databaseFilename, reset = True) settings = Settings(settingsFilename, self.appInfo.path) settings.setFrontendHeader("header") settings.setFrontendNews("news") settings.setBoolean(Settings.SECTION_BACKEND, "web_api", True) displayPowerManager = DisplayPowerManager(settings) self.webSocket = WebSocket(self.appInfo, settings, self.database) pluginManager = PluginManager(settings, self.database, self.webSocket, displayPowerManager) self.webSocket.init(pluginManager) self.appClient = self.webSocket.app_test_client() self.socketClient = self.webSocket.socket_test_client(self.appClient) def teardown_class(self) -> None: self.database.commitAndClose() def test_get_last_alarm_events(self) -> None: self.__emit("get_last_alarm_events", {'count': 10}) (event, args) = self.__getReceived() assert(event == "last_alarm_events") assert(args['total_events'] == 0) assert(args['alarm_events'] == "[]") def test_get_stats(self) -> None: self.__emit("get_stats") (event, args) = self.__getReceived() assert(event == "stats") assert(args is not None) assert(args['total'] == 0) assert(args['year'] == 0) assert(args['month'] == 0) assert(args['today'] == 0) def test_get_header(self) -> None: self.__emit("get_header") (event, args) = self.__getReceived() assert(event == "header") assert(args is not None) assert(args['header'] == "header") def test_get_news(self) -> None: self.__emit("get_news") (event, args) = self.__getReceived() assert(event == "news") assert(args is not None) assert(args['news'] == "news") def test_get_state(self) -> None: self.__emit("get_state") (event, args) = self.__getReceived() assert(event == "state") assert(args is not None) assert(args['version'] == self.appInfo.version) assert(args['start_timestamp'] > 0) assert(args['source_state'] == SourceState.OK) def test_broadcast_alarm_event(self) -> None: alarmEvent = AlarmEvent(1302) self.webSocket.broadcastAlarmEvent(alarmEvent) (event, args) = self.__getReceived() assert(event == "alarm_event") assert(args is not None) assert(args['eventID'] == 1302) def test_broadcast_header(self) -> None: self.webSocket.broadcastHeader("TEST\nHEADER") (event, args) = self.__getReceived() assert(event == "header") assert(args is not None) assert(args['header'] == "TEST\nHEADER") def test_broadcast_news(self) -> None: self.webSocket.broadcastNews("TEST\nNEWS") (event, args) = self.__getReceived() assert(event == "news") assert(args is not None) assert(args['news'] == "TEST\nNEWS") def test_broadcast_database_changed(self) -> None: self.webSocket.broadcastDatabaseChanged() (event, _) = self.__getReceived() assert(event == "database_changed") def test_broadcast_calendar_changed(self) -> None: self.webSocket.broadcastCalendarChanged() (event, _) = self.__getReceived() assert (event == "calendar_changed") def test_static(self) -> None: response = self.appClient.get('/css/situationboard.css') assert(response.status_code == 200) def test_index(self) -> None: response = self.appClient.get('/') assert(response.status_code == 200) def test_javascript_frontend(self) -> None: response = self.appClient.get('/js/situationboard.js') assert(response.status_code == 200) def test_javascript_settings(self) -> None: response = self.appClient.get('/js/frontend/util/settings.js') assert(response.status_code == 200) def test_api_stats(self) -> None: response = self.appClient.get('/api/v1/stats') assert(response.status_code == 200) assert(response.json['result'] == "ok") stats = response.json['stats'] assert(stats['total'] == 0) assert(stats['year'] == 0) assert(stats['month'] == 0) assert(stats['today'] == 0) def test_api_state(self) -> None: response = self.appClient.get('/api/v1/state') assert(response.status_code == 200) assert(response.json['result'] == "ok") state = response.json['state'] assert(state['version'] == self.appInfo.version) assert(state['start_timestamp'] > 0) assert(state['source_state'] == SourceState.OK) def __emit(self, event: str, args: Optional[Dict[str, Any]] = None) -> None: if args is not None: self.socketClient.emit(event, args, namespace=WebSocket.NS) else: self.socketClient.emit(event, namespace=WebSocket.NS) def __getReceived(self) -> Tuple[Any, Any]: response = self.socketClient.get_received(WebSocket.NS) assert(len(response) == 1) result = response[0] event = result['name'] argsList = result['args'] asgsListLen = len(argsList) assert(asgsListLen <= 1) if asgsListLen == 1: args = argsList[0] return (event, args) return (event, None)
class Test_ActionUpdateFrontend: def setup_class(self) -> None: #pylint: disable=W0201 appInfo = AppInfo() settingsFilenameOrig = os.path.join( appInfo.path, "misc/setup/situationboard_default.conf") settingsFilename = os.path.join(appInfo.path, ".temp/situationboard.conf") databaseFilename = os.path.join(appInfo.path, ".temp/situationboard.sqlite") shutil.copy(settingsFilenameOrig, settingsFilename) self.database = Database(databaseFilename, reset=True) settings = Settings(settingsFilename, appInfo.path) settings.setFrontendHeader("header") settings.setFrontendNews("news") displayPowerManager = DisplayPowerManager(settings) self.webSocket = WebSocket(appInfo, settings, self.database) pluginManager = PluginManager(settings, self.database, self.webSocket, displayPowerManager) self.webSocket.init(pluginManager) appClient = self.webSocket.app_test_client() self.socketClient = self.webSocket.socket_test_client(appClient) self.action = ActionUpdateFrontend("", settings, self.webSocket) def teardown_class(self) -> None: self.database.commitAndClose() def test_handle_event_alarm(self) -> None: alarmEvent = AlarmEvent(1302) self.action.handleEvent(alarmEvent) (event, args) = self.__getReceived() assert (event == "alarm_event") assert (args is not None) assert (args['eventID'] == 1302) def test_handle_event_setting_header(self) -> None: settingEvent = SettingEvent() settingEvent.key = "header" settingEvent.value = "new header" self.action.handleEvent(settingEvent) (event, args) = self.__getReceived() assert (event == "header") assert (args is not None) assert (args['header'] == "new header") def test_handle_event_setting_news(self) -> None: settingEvent = SettingEvent() settingEvent.key = "news" settingEvent.value = "new news" self.action.handleEvent(settingEvent) (event, args) = self.__getReceived() assert (event == "news") assert (args is not None) assert (args['news'] == "new news") def __getReceived(self) -> Tuple[Any, Any]: response = self.socketClient.get_received(WebSocket.NS) assert (len(response) == 1) result = response[0] event = result['name'] argsList = result['args'] asgsListLen = len(argsList) assert (asgsListLen <= 1) if asgsListLen == 1: args = argsList[0] return (event, args) return (event, None)