From 5e9a7e6d5c70ae3f88d96d698cff16198ee335fe Mon Sep 17 00:00:00 2001 From: Alan Gibson Date: Wed, 13 Nov 2024 16:14:02 -0500 Subject: [PATCH] Fixes and docstrings Using numpydoc format updated module docstring added class docstring ensured all functions have docstring ensured all docstrings are numpydoc Pylint fixes in anticipation of incorporating Pylint into use currentData -> current_data json > redis because json is a python package catch exact exceptions in `connect` updated the error handling provided exception information in output Black fixes in anticipation of incorporating Black into use removed unnecessary whitespaces favor double quote, ", over single quote, ', for string delineation line size fixes based on default settings Unit Tests Exception change required 1 test to change to 3 tested both types of expected exceptions (2) tested neither expected exceptions (1) minor clarity updates currentData -> current_data --- onair/data_handling/redis_adapter.py | 342 ++++++++-- .../onair/data_handling/test_redis_adapter.py | 602 ++++++++++++------ 2 files changed, 688 insertions(+), 256 deletions(-) diff --git a/onair/data_handling/redis_adapter.py b/onair/data_handling/redis_adapter.py index 62514ff8..0057bbeb 100644 --- a/onair/data_handling/redis_adapter.py +++ b/onair/data_handling/redis_adapter.py @@ -8,130 +8,274 @@ # Licensed under the NASA Open Source Agreement version 1.3 # See "NOSA GSC-19165-1 OnAIR.pdf" -""" -redis_adapter AdapterDataSource class +"""redis_adapter module + +This module contains a DataSource class, which serves as a data source +for sim.py by receiving messages from one or more REDIS servers. It +implements the OnAirDataSource interface and provides functionality for +connecting to REDIS servers, subscribing to channels, and processing +incoming data. -Receives messages from REDIS server, serves as a data source for sim.py +The module utilizes Redis, threading, and JSON libraries to handle +server connections and data parsing. """ import threading import time -import redis import json +import redis from onair.data_handling.on_air_data_source import OnAirDataSource from onair.data_handling.on_air_data_source import ConfigKeyError from onair.data_handling.tlm_json_parser import parseJson -from onair.src.util.print_io import * -from onair.data_handling.parser_util import * +from onair.src.util.print_io import print_msg +from onair.data_handling.parser_util import extract_meta_data_handle_ss_breakdown + class DataSource(OnAirDataSource): + """Implements OnAirDataSource interface for receiving data from REDIS servers. + + This class provides the following functionality: + - Establishes connections to one or more REDIS servers + - Subscribes to specified channels on each server + - Listens for incoming messages on subscribed channels + - Parses and processes received data + - Provides methods to access the latest data received + + The class uses double-buffering for thread-safe access to recent data. + """ def __init__(self, data_file, meta_file, ss_breakdown=False): + """Initialize the DataSource object. + + Parameters + ---------- + data_file : str + Path to the data file (not used in Redis adapter). + meta_file : str + Path to the metadata file containing Redis server configurations. + ss_breakdown : bool, optional + Flag to indicate whether to handle subsystem breakdown, by default False. + Flag to indicate whether to handle subsystem breakdown, by default + False. + + Notes + ----- + This method performs the following tasks: + 1. Initializes the parent class. + 2. Sets up threading lock and new data flag. + 3. Initializes lists for servers and current data. + 4. Creates a double buffer for current data storage. + 5. Connects to Redis servers specified in the metadata file. + """ super().__init__(data_file, meta_file, ss_breakdown) self.new_data_lock = threading.Lock() self.new_data = False self.servers = [] - self.currentData = [] - self.currentData.append( + self.current_data = [] + self.current_data.append( {"headers": self.order, "data": list("-" * len(self.order))} ) - self.currentData.append( + self.current_data.append( {"headers": self.order, "data": list("-" * len(self.order))} ) self.double_buffer_read_index = 0 self.connect() def connect(self): - """Establish connection to REDIS server.""" - print_msg('Redis adapter connecting to server...') + """Connect to Redis servers and set up subscriptions. + + This method iterates through the server configurations, attempts to + connect to each Redis server, and sets up subscriptions for the + specified channels. + + For each server configuration: + 1. Extracts connection details (address, port, db, password). + 2. Creates a Redis connection if subscriptions are specified. + 3. Pings the server to ensure connectivity. + 4. Sets up a pubsub object and subscribes to specified channels. + 5. Starts a listener thread for each server connection. + + When a connection fails, an error message is output, and the method + continues to the next server configuration. + """ + print_msg("Redis adapter connecting to server...") for idx, server_config in enumerate(self.server_configs): - server_config_keys = server_config.keys() - if 'address' in server_config_keys: - address = server_config['address'] - else: - address = 'localhost' - - if 'port' in server_config_keys: - port = server_config['port'] - else: - port = 6379 - - if 'db' in server_config_keys: - db = server_config['db'] - else: - db = 0 - - if 'password' in server_config_keys: - password = server_config['password'] - else: - password = '' - - #if there are subscriptions in this Redis server configuration's subscription key - if len(server_config['subscriptions']) != 0: - #Create the servers and append them to self.servers list + address = server_config.get("address", "localhost") + port = server_config.get("port", 6379) + db = server_config.get("db", 0) + password = server_config.get("password", "") + + # if there are subscriptions in this Redis server configuration's subscription key + if len(server_config["subscriptions"]) != 0: + # Create the servers and append them to self.servers list self.servers.append(redis.Redis(address, port, db, password)) try: - #Ping server to make sure we can connect + # Ping server to make sure we can connect self.servers[-1].ping() - print_msg(f'... connected to server # {idx}!') + print_msg(f"... connected to server # {idx}!") - #Set up Redis pubsub function for the current server + # Set up Redis pubsub function for the current server pubsub = self.servers[-1].pubsub() - for s in server_config['subscriptions']: + for s in server_config["subscriptions"]: pubsub.subscribe(s) print_msg(f"Subscribing to channel: {s} on server # {idx}") - listen_thread = threading.Thread(target=self.message_listener, args=(pubsub,)) + listen_thread = threading.Thread( + target=self.message_listener, args=(pubsub,) + ) listen_thread.start() - #This except will be hit if self.servers[-1].ping() threw an exception (could not properly ping server) - except: - print_msg(f'Did not connect to server # {idx}. Not setting up subscriptions.', ['FAIL']) + # This except will be hit if self.servers[-1].ping() + # threw an exception (could not properly ping server) + except ( + redis.exceptions.ConnectionError, + redis.exceptions.TimeoutError, + ) as e: + error_type = type(e).__name__ + error_message = str(e) + print_msg( + f"Did not connect to server # {idx} due to {error_type}: {error_message}" + f"\nNot setting up subscriptions.", + ["FAIL"], + ) else: print_msg("No subscriptions given! Redis server not created") def parse_meta_data_file(self, meta_data_file, ss_breakdown): + """ + Parse the metadata file and extract configuration information. + + Parameters + ---------- + meta_data_file : str + Path to the metadata file. + ss_breakdown : bool + Flag to indicate whether to handle subsystem breakdown. + + Returns + ------- + dict + Extracted configuration information. + + Raises + ------ + ConfigKeyError + If required keys are missing in the metadata file. + + Notes + ----- + This method performs the following tasks: + 1. Extracts metadata and handles subsystem breakdown. + 2. Parses the JSON content of the metadata file. + 3. Validates and extracts Redis server configurations. + 4. Extracts the 'order' key from the metadata. + + The extracted Redis server configurations are stored in + `self.server_configs`. + The 'order' key is stored in `self.order`. + """ self.server_configs = [] - configs = extract_meta_data_handle_ss_breakdown( - meta_data_file, ss_breakdown) + configs = extract_meta_data_handle_ss_breakdown(meta_data_file, ss_breakdown) meta = parseJson(meta_data_file) keys = meta.keys() # Setup redis server configuration - #Checking if 'redis' exists - if 'redis' in keys: + # Checking if 'redis' exists + if "redis" in keys: count_server_config = 0 - #Checking if dictionaries within 'redis' key each have a 'subscription' key. Error will be thrown if not. - for server_config in meta['redis']: + # Checking if dictionaries within 'redis' key each have a 'subscription' key. + for server_config in meta["redis"]: redis_config_keys = server_config.keys() - if ('subscriptions' in redis_config_keys) == False: - raise ConfigKeyError(f'Config file: \'{meta_data_file}\' ' \ - f'missing required key \'subscriptions\' from {count_server_config} in key \'redis\'') - count_server_config +=1 + if not "subscriptions" in redis_config_keys: + raise ConfigKeyError( + f"Config file: '{meta_data_file}' " + f"missing required key 'subscriptions' from {count_server_config}" + + " in key 'redis'" + ) + count_server_config += 1 - #Saving all of Redis dictionaries from JSON file to self.server_configs - self.server_configs = meta['redis'] + # Saving all of Redis dictionaries from JSON file to self.server_configs + self.server_configs = meta["redis"] - if 'order' in keys: - self.order = meta['order'] + if "order" in keys: + self.order = meta["order"] else: - raise ConfigKeyError(f'Config file: \'{meta_data_file}\' ' \ - 'missing required key \'order\'') + raise ConfigKeyError( + f"Config file: '{meta_data_file}' " "missing required key 'order'" + ) return configs def process_data_file(self, data_file): + """Process the data file (not used in Redis Adapter). + + This method is not used in the Redis Adapter and simply prints a + message indicating that the file is being ignored. + + Parameters + ---------- + data_file : str + Path to the data file (not used). + + Notes + ----- + Data is received through Redis subscriptions rather than from a file, + so this method does not perform any actual processing. + """ print("Redis Adapter ignoring file") def get_vehicle_metadata(self): + """Get the vehicle metadata for headers and test assignments. + + Returns + ------- + tuple + A tuple containing two elements: + - all_headers : list + A list of all headers for the vehicle data. + - test_assignments : dict + A dictionary containing the test assignments from the binning + configurations. + + Notes + ----- + This method returns the metadata necessary for processing and organizing + vehicle data. The all_headers list provides information about the + structure of the data, while the test_assignments dictionary contains + information about how tests are assigned or grouped. + """ return self.all_headers, self.binning_configs["test_assignments"] def get_next(self): - """Provides the latest data from REDIS channel""" + """Retrieve the next available data from the buffer. + + This method waits for new data to become available, then returns it. + It uses a double-buffering technique to ensure thread-safe access to + the data. + + Returns + ------- + list + The latest data retrieved from the buffer. + + Notes + ----- + This method blocks until new data is available. It uses a short sleep + (10 milliseconds) between checks to reduce CPU usage while waiting. + + The double-buffering technique involves: + 1. Waiting for the `new_data` flag to become True. + 2. Resetting the `new_data` flag to False. + 3. Updating the read index for the double buffer. + 4. Returning the data from the current read buffer. + + This approach allows one buffer to be read while the other is being + written to, ensuring data consistency and thread safety. + """ data_available = False while not data_available: @@ -147,16 +291,65 @@ def get_next(self): self.double_buffer_read_index = (self.double_buffer_read_index + 1) % 2 read_index = self.double_buffer_read_index - return self.currentData[read_index]["data"] + return self.current_data[read_index]["data"] def has_more(self): - """Live connection should always return True""" + """Check if more data is available. + + This method always returns True for the Redis adapter, as it + continuously listens for new messages. + + Returns + ------- + bool + Always True, indicating that more data can potentially be received. + + Notes + ----- + This method is part of the OnAirDataSource interface. For the Redis + adapter, it always returns True because the adapter continuously + listens for new messages from the subscribed Redis channels. + """ return True def message_listener(self, pubsub): - """Loop for listening for messages on channels""" + """Listen for messages from Redis pubsub and process them. + + This method continuously listens for messages from the Redis pubsub + connection, processes JSON messages, updates the current data buffer, + handling various error conditions. + + Parameters + ---------- + pubsub : redis.client.PubSub + A Redis pubsub object for subscribing to channels and receiving + messages. + + Notes + ----- + The method performs the following tasks: + 1. Listens for messages from the pubsub connection. + 2. Processes messages of type "message". + 3. Attempts to parse the message data as JSON. + 4. Updates the current data buffer with new values. + 5. Handles missing or unexpected keys in the message data. + 6. Sets a flag to indicate new data is available. + 7. Warns about non-message type receipts. + + The method continues to run until the pubsub connection is closed or + an error occurs. If the listener loop exits, a warning is issued. + + Warnings + -------- + Warnings are issued for the following conditions: + - Non-JSON conforming message data + - Unexpected keys in the message data + - Expected keys missing from the message data + - Non-message type receipts + - Listener loop exit + """ for message in pubsub.listen(): - if message['type'] == 'message': + if message["type"] == "message": channel_name = f"{message['channel'].decode()}" # Attempt to load message as json try: @@ -171,12 +364,14 @@ def message_listener(self, pubsub): print_msg(non_json_msg, ["WARNING"]) continue # Select the current data - currentData = self.currentData[(self.double_buffer_read_index + 1) % 2] + current_data = self.current_data[ + (self.double_buffer_read_index + 1) % 2 + ] # turn all data points to unknown - currentData["data"] = ["-" for _ in currentData["data"]] + current_data["data"] = ["-" for _ in current_data["data"]] # Find expected keys for received channel expected_message_keys = [ - k for k in currentData["headers"] if channel_name in k + k for k in current_data["headers"] if channel_name in k ] # Time is an expected key for all channels expected_message_keys.append("time") @@ -188,8 +383,8 @@ def message_listener(self, pubsub): header_string = f"{channel_name}.{key}" # Look for channel specific values try: - index = currentData["headers"].index(header_string) - currentData["data"][index] = data[key] + index = current_data["headers"].index(header_string) + current_data["data"][index] = data[key] expected_message_keys.remove(header_string) # Unexpected key in data except ValueError: @@ -221,4 +416,13 @@ def message_listener(self, pubsub): print_msg("Redis subscription listener exited.", ["WARNING"]) def has_data(self): + """ + Check if new data is available. + + Returns + ------- + bool + The value of the new_data flag, which should be True if new data is + available, False otherwise. + """ return self.new_data diff --git a/test/onair/data_handling/test_redis_adapter.py b/test/onair/data_handling/test_redis_adapter.py index 218ae56b..c014d2e2 100644 --- a/test/onair/data_handling/test_redis_adapter.py +++ b/test/onair/data_handling/test_redis_adapter.py @@ -37,21 +37,25 @@ def test_redis_adapter_DataSource__init__sets_redis_values_then_connects(mocker) ) # from 1 to 10 arbitrary cut.order = fake_order - mocker.patch.object(OnAirDataSource, '__init__', new=MagicMock()) - mocker.patch('threading.Lock', return_value=fake_new_data_lock) - mocker.patch.object(cut, 'connect') + mocker.patch.object(OnAirDataSource, "__init__", new=MagicMock()) + mocker.patch("threading.Lock", return_value=fake_new_data_lock) + mocker.patch.object(cut, "connect") # Act cut.__init__(arg_data_file, arg_meta_file, arg_ss_breakdown) # Assert assert OnAirDataSource.__init__.call_count == 1 - assert OnAirDataSource.__init__.call_args_list[0].args == (arg_data_file, arg_meta_file, arg_ss_breakdown) + assert OnAirDataSource.__init__.call_args_list[0].args == ( + arg_data_file, + arg_meta_file, + arg_ss_breakdown, + ) assert cut.servers == expected_server assert cut.new_data_lock == fake_new_data_lock assert threading.Lock.call_count == 1 assert cut.new_data == False - assert cut.currentData == [ + assert cut.current_data == [ {"headers": fake_order, "data": list("-" * len(fake_order))}, {"headers": fake_order, "data": list("-" * len(fake_order))}, ] @@ -59,12 +63,28 @@ def test_redis_adapter_DataSource__init__sets_redis_values_then_connects(mocker) assert cut.connect.call_count == 1 assert cut.connect.call_args_list[0].args == () + # connect tests def test_redis_adapter_DataSource_connect_establishes_server_with_initialized_attributes( mocker, ): # Arrange - fake_server_configs = [{"address": MagicMock(), "port": 1234,"db": 1, "password": 'test', "subscriptions": ["state_0", "state_1"]}, {"address": '000.000.000.222', "port": 5678, "db": 2, "password": 'test2', "subscriptions" : ["state_2", "state_3"]}] + fake_server_configs = [ + { + "address": MagicMock(), + "port": 1234, + "db": 1, + "password": "test", + "subscriptions": ["state_0", "state_1"], + }, + { + "address": "000.000.000.222", + "port": 5678, + "db": 2, + "password": "test2", + "subscriptions": ["state_2", "state_3"], + }, + ] fake_server = MagicMock() @@ -76,42 +96,71 @@ def test_redis_adapter_DataSource_connect_establishes_server_with_initialized_at cut.servers = [] cut.message_listener = fake_message_listener - - mocker.patch(redis_adapter.__name__ + '.print_msg') - mocker.patch('redis.Redis', return_value=fake_server) - mocker.patch.object(fake_server, 'ping') - mocker.patch('threading.Thread', return_value=fake_listen_thread) - mocker.patch.object(fake_listen_thread, 'start') + mocker.patch(redis_adapter.__name__ + ".print_msg") + mocker.patch("redis.Redis", return_value=fake_server) + mocker.patch.object(fake_server, "ping") + mocker.patch("threading.Thread", return_value=fake_listen_thread) + mocker.patch.object(fake_listen_thread, "start") # Act cut.connect() # Assert assert redis_adapter.print_msg.call_count == 7 - assert redis_adapter.print_msg.call_args_list[0].args == ('Redis adapter connecting to server...',) - assert redis_adapter.print_msg.call_args_list[1].args == ('... connected to server # 0!',) - assert redis_adapter.print_msg.call_args_list[2].args == ('Subscribing to channel: state_0 on server # 0',) - assert redis_adapter.print_msg.call_args_list[3].args == ('Subscribing to channel: state_1 on server # 0',) - assert redis_adapter.print_msg.call_args_list[4].args == ('... connected to server # 1!',) - assert redis_adapter.print_msg.call_args_list[5].args == ('Subscribing to channel: state_2 on server # 1',) - assert redis_adapter.print_msg.call_args_list[6].args == ('Subscribing to channel: state_3 on server # 1',) + assert redis_adapter.print_msg.call_args_list[0].args == ( + "Redis adapter connecting to server...", + ) + assert redis_adapter.print_msg.call_args_list[1].args == ( + "... connected to server # 0!", + ) + assert redis_adapter.print_msg.call_args_list[2].args == ( + "Subscribing to channel: state_0 on server # 0", + ) + assert redis_adapter.print_msg.call_args_list[3].args == ( + "Subscribing to channel: state_1 on server # 0", + ) + assert redis_adapter.print_msg.call_args_list[4].args == ( + "... connected to server # 1!", + ) + assert redis_adapter.print_msg.call_args_list[5].args == ( + "Subscribing to channel: state_2 on server # 1", + ) + assert redis_adapter.print_msg.call_args_list[6].args == ( + "Subscribing to channel: state_3 on server # 1", + ) assert redis.Redis.call_count == 2 - assert redis.Redis.call_args_list[0].args == (fake_server_configs[0]["address"], fake_server_configs[0]["port"], fake_server_configs[0]["db"], fake_server_configs[0]["password"] ) - assert redis.Redis.call_args_list[1].args == (fake_server_configs[1]["address"], fake_server_configs[1]["port"], fake_server_configs[1]["db"], fake_server_configs[1]["password"] ) + assert redis.Redis.call_args_list[0].args == ( + fake_server_configs[0]["address"], + fake_server_configs[0]["port"], + fake_server_configs[0]["db"], + fake_server_configs[0]["password"], + ) + assert redis.Redis.call_args_list[1].args == ( + fake_server_configs[1]["address"], + fake_server_configs[1]["port"], + fake_server_configs[1]["db"], + fake_server_configs[1]["password"], + ) assert fake_server.ping.call_count == 2 assert cut.servers == [fake_server, fake_server] + # connect tests -def test_redis_adapter_DataSource_connect_establishes_server_with_default_attributes(mocker): +def test_redis_adapter_DataSource_connect_establishes_server_with_default_attributes( + mocker, +): # Arrange - expected_address = 'localhost' + expected_address = "localhost" expected_port = 6379 expected_db = 0 - expected_password = '' + expected_password = "" - fake_server_configs = [{"subscriptions": ["state_0", "state_1"]}, {"subscriptions" : ["state_2", "state_3"]}] + fake_server_configs = [ + {"subscriptions": ["state_0", "state_1"]}, + {"subscriptions": ["state_2", "state_3"]}, + ] fake_server = MagicMock() @@ -123,51 +172,156 @@ def test_redis_adapter_DataSource_connect_establishes_server_with_default_attrib cut.servers = [] cut.message_listener = fake_message_listener + mocker.patch(redis_adapter.__name__ + ".print_msg") + mocker.patch("redis.Redis", return_value=fake_server) + mocker.patch.object(fake_server, "ping") + mocker.patch("threading.Thread", return_value=fake_listen_thread) + mocker.patch.object(fake_listen_thread, "start") - mocker.patch(redis_adapter.__name__ + '.print_msg') - mocker.patch('redis.Redis', return_value=fake_server) - mocker.patch.object(fake_server, 'ping') - mocker.patch('threading.Thread', return_value=fake_listen_thread) - mocker.patch.object(fake_listen_thread, 'start') + # Act + cut.connect() + + # Assert + assert redis.Redis.call_count == 2 + assert redis.Redis.call_args_list[0].args == ( + expected_address, + expected_port, + expected_db, + expected_password, + ) + assert redis.Redis.call_args_list[1].args == ( + expected_address, + expected_port, + expected_db, + expected_password, + ) + + +def test_redis_adapter_DataSource_fails_ping_to_server_with_ConnectionError_and_prints_info( + mocker, +): + fake_server_configs = [ + {"subscriptions": ["state_0", "state_1"]}, + {"subscriptions": ["state_2", "state_3"]}, + ] + fake_server = MagicMock() + + cut = DataSource.__new__(DataSource) + cut.server_configs = fake_server_configs + cut.servers = [] + + mocker.patch(redis_adapter.__name__ + ".print_msg") + mocker.patch("redis.Redis", return_value=fake_server) + mocker.patch.object( + fake_server, + "ping", + side_effect=redis.exceptions.ConnectionError("Connection refused"), + ) # Act cut.connect() # Assert + assert redis_adapter.print_msg.call_count == 3 + assert redis_adapter.print_msg.call_args_list[0].args == ( + "Redis adapter connecting to server...", + ) assert redis.Redis.call_count == 2 - assert redis.Redis.call_args_list[0].args == (expected_address, expected_port, expected_db, expected_password ) - assert redis.Redis.call_args_list[1].args == (expected_address, expected_port, expected_db, expected_password ) + assert fake_server.ping.call_count == 2 + assert cut.servers == [fake_server, fake_server] + + assert redis_adapter.print_msg.call_args_list[1].args == ( + "Did not connect to server # 0 due to ConnectionError: " + + "Connection refused\nNot setting up subscriptions.", + ["FAIL"], + ) + assert redis_adapter.print_msg.call_args_list[2].args == ( + "Did not connect to server # 1 due to ConnectionError: " + + "Connection refused\nNot setting up subscriptions.", + ["FAIL"], + ) -def test_redis_adapter_DataSource_fails_to_connect_to_server_with_ping_and_states_no_subscriptions_(mocker): - fake_server_configs = [{"subscriptions": ["state_0", "state_1"]}, {"subscriptions": ["state_2", "state_3"]}] +def test_redis_adapter_DataSource_fails_ping_to_server_with_TimeoutError_and_prints_info( + mocker, +): + fake_server_configs = [ + {"subscriptions": ["state_0", "state_1"]}, + {"subscriptions": ["state_2", "state_3"]}, + ] fake_server = MagicMock() cut = DataSource.__new__(DataSource) cut.server_configs = fake_server_configs cut.servers = [] - mocker.patch(redis_adapter.__name__ + '.print_msg') - mocker.patch('redis.Redis', return_value=fake_server) - mocker.patch.object(fake_server, 'ping', side_effect=ConnectionError) + mocker.patch(redis_adapter.__name__ + ".print_msg") + mocker.patch("redis.Redis", return_value=fake_server) + mocker.patch.object( + fake_server, + "ping", + side_effect=redis.exceptions.TimeoutError("Connection timed out"), + ) # Act cut.connect() # Assert assert redis_adapter.print_msg.call_count == 3 - assert redis_adapter.print_msg.call_args_list[0].args == ("Redis adapter connecting to server...",) + assert redis_adapter.print_msg.call_args_list[0].args == ( + "Redis adapter connecting to server...", + ) assert redis.Redis.call_count == 2 assert fake_server.ping.call_count == 2 - assert cut.servers == [fake_server, fake_server] + assert cut.servers == [fake_server, fake_server] - assert redis_adapter.print_msg.call_args_list[0].args == ('Redis adapter connecting to server...',) - assert redis_adapter.print_msg.call_args_list[1].args == ('Did not connect to server # 0. Not setting up subscriptions.', ['FAIL']) - assert redis_adapter.print_msg.call_args_list[2].args == ('Did not connect to server # 1. Not setting up subscriptions.', ['FAIL']) + assert redis_adapter.print_msg.call_args_list[1].args == ( + "Did not connect to server # 0 due to TimeoutError: " + + "Connection timed out\nNot setting up subscriptions.", + ["FAIL"], + ) + assert redis_adapter.print_msg.call_args_list[2].args == ( + "Did not connect to server # 1 due to TimeoutError: " + + "Connection timed out\nNot setting up subscriptions.", + ["FAIL"], + ) + + +def test_redis_adapter_DataSource_fails_to_connect_due_to_unhandled_exception_and_raises( + mocker, +): + fake_server_configs = [ + {"subscriptions": ["state_0", "state_1"]}, + {"subscriptions": ["state_2", "state_3"]}, + ] + fake_server = MagicMock() + + cut = DataSource.__new__(DataSource) + cut.server_configs = fake_server_configs + cut.servers = [] + + mocker.patch(redis_adapter.__name__ + ".print_msg") + mocker.patch("redis.Redis", return_value=fake_server) + mocker.patch.object(fake_server, "ping", side_effect=Exception("Unexpected error")) + + # Act and Assert + with pytest.raises(Exception) as excinfo: + cut.connect() + + assert str(excinfo.value) == "Unexpected error" + assert redis_adapter.print_msg.call_count == 1 + assert redis_adapter.print_msg.call_args_list[0].args == ( + "Redis adapter connecting to server...", + ) + assert redis.Redis.call_count == 1 + assert fake_server.ping.call_count == 1 + assert cut.servers == [fake_server] # subscribe_message tests -def test_redis_adapter_DataSource_subscribe_subscribes_to_each_given_subscription_and_starts_listening_when_server_available(mocker): +def test_redis_adapter_DataSource_subscribe_subscribes_to_each_given_subscription_and_starts_listening_when_server_available( + mocker, +): # Arrange fake_server = MagicMock() @@ -175,20 +329,26 @@ def test_redis_adapter_DataSource_subscribe_subscribes_to_each_given_subscriptio fake_message_listener = MagicMock() fake_listen_thread = MagicMock() - fake_server_configs = [{"subscriptions": ["state_0", "state_1"]}, {"subscriptions": ["state_2", "state_3"]}] + fake_server_configs = [ + {"subscriptions": ["state_0", "state_1"]}, + {"subscriptions": ["state_2", "state_3"]}, + ] cut = DataSource.__new__(DataSource) cut.server_configs = fake_server_configs cut.message_listener = fake_message_listener - cut.servers = [{"subscriptions": ["state_0", "state_1"]}, {"subscriptions": ["state_2", "state_3"]}] + cut.servers = [ + {"subscriptions": ["state_0", "state_1"]}, + {"subscriptions": ["state_2", "state_3"]}, + ] - mocker.patch('redis.Redis', return_value=fake_server) - mocker.patch.object(fake_server, 'ping', return_value=True) - mocker.patch.object(fake_server, 'pubsub', return_value=fake_pubsub) - mocker.patch.object(fake_pubsub, 'subscribe') - mocker.patch(redis_adapter.__name__ + '.print_msg') - mocker.patch('threading.Thread', return_value=fake_listen_thread) - mocker.patch.object(fake_listen_thread, 'start') + mocker.patch("redis.Redis", return_value=fake_server) + mocker.patch.object(fake_server, "ping", return_value=True) + mocker.patch.object(fake_server, "pubsub", return_value=fake_pubsub) + mocker.patch.object(fake_pubsub, "subscribe") + mocker.patch(redis_adapter.__name__ + ".print_msg") + mocker.patch("threading.Thread", return_value=fake_listen_thread) + mocker.patch.object(fake_listen_thread, "start") # Act cut.connect() @@ -197,20 +357,38 @@ def test_redis_adapter_DataSource_subscribe_subscribes_to_each_given_subscriptio assert fake_server.ping.call_count == 2 assert fake_server.pubsub.call_count == 2 - #This is already checked in the first test. Should it be removed? Should the first test not check the subscription messages? + # Print message call count is already checked in the first test. + # Should it be removed? + # Should the first test not check the subscription messages? assert redis_adapter.print_msg.call_count == 7 - assert redis_adapter.print_msg.call_args_list[0].args == ('Redis adapter connecting to server...',) - assert redis_adapter.print_msg.call_args_list[1].args == ('... connected to server # 0!',) - assert redis_adapter.print_msg.call_args_list[2].args == ('Subscribing to channel: state_0 on server # 0',) - assert redis_adapter.print_msg.call_args_list[3].args == ('Subscribing to channel: state_1 on server # 0',) - assert redis_adapter.print_msg.call_args_list[4].args == ('... connected to server # 1!',) - assert redis_adapter.print_msg.call_args_list[5].args == ('Subscribing to channel: state_2 on server # 1',) - assert redis_adapter.print_msg.call_args_list[6].args == ('Subscribing to channel: state_3 on server # 1',) + assert redis_adapter.print_msg.call_args_list[0].args == ( + "Redis adapter connecting to server...", + ) + assert redis_adapter.print_msg.call_args_list[1].args == ( + "... connected to server # 0!", + ) + assert redis_adapter.print_msg.call_args_list[2].args == ( + "Subscribing to channel: state_0 on server # 0", + ) + assert redis_adapter.print_msg.call_args_list[3].args == ( + "Subscribing to channel: state_1 on server # 0", + ) + assert redis_adapter.print_msg.call_args_list[4].args == ( + "... connected to server # 1!", + ) + assert redis_adapter.print_msg.call_args_list[5].args == ( + "Subscribing to channel: state_2 on server # 1", + ) + assert redis_adapter.print_msg.call_args_list[6].args == ( + "Subscribing to channel: state_3 on server # 1", + ) assert fake_pubsub.subscribe.call_count == 4 assert threading.Thread.call_count == 2 - assert threading.Thread.call_args_list[0].kwargs == ({'target': cut.message_listener, 'args': (fake_pubsub,)}) + assert threading.Thread.call_args_list[0].kwargs == ( + {"target": cut.message_listener, "args": (fake_pubsub,)} + ) assert fake_listen_thread.start.call_count == 2 @@ -223,21 +401,20 @@ def test_redis_adapter_DataSource_subscribe_states_no_subscriptions_given_when_e fake_message_listener = MagicMock() fake_listen_thread = MagicMock() - fake_server_configs = [{'subscriptions': {}}] + fake_server_configs = [{"subscriptions": {}}] cut = DataSource.__new__(DataSource) cut.server_configs = fake_server_configs cut.message_listener = fake_message_listener cut.servers = [] - - mocker.patch('redis.Redis', return_value=fake_servers) - mocker.patch.object(fake_servers, 'ping', return_value=True) - mocker.patch.object(fake_servers, 'pubsub', return_value=initial_pubsub) - mocker.patch.object(initial_pubsub, 'subscribe') - mocker.patch(redis_adapter.__name__ + '.print_msg') - mocker.patch('threading.Thread', return_value=fake_listen_thread) - mocker.patch.object(fake_listen_thread, 'start') + mocker.patch("redis.Redis", return_value=fake_servers) + mocker.patch.object(fake_servers, "ping", return_value=True) + mocker.patch.object(fake_servers, "pubsub", return_value=initial_pubsub) + mocker.patch.object(initial_pubsub, "subscribe") + mocker.patch(redis_adapter.__name__ + ".print_msg") + mocker.patch("threading.Thread", return_value=fake_listen_thread) + mocker.patch.object(fake_listen_thread, "start") # Act cut.connect() @@ -248,20 +425,22 @@ def test_redis_adapter_DataSource_subscribe_states_no_subscriptions_given_when_e assert fake_servers.pubsub.subscribe.call_count == 0 assert threading.Thread.call_count == 0 assert fake_listen_thread.start.call_count == 0 - assert redis_adapter.print_msg.call_args_list[1].args == ("No subscriptions given! Redis server not created",) + assert redis_adapter.print_msg.call_args_list[1].args == ( + "No subscriptions given! Redis server not created", + ) + def test_redis_adapter_DataSource_get_next_returns_expected_data_when_new_data_is_true_and_double_buffer_read_index_is_1(): # Arrange - # Renew DataSource to ensure test independence cut = DataSource.__new__(DataSource) cut.new_data = True cut.new_data_lock = MagicMock() cut.double_buffer_read_index = 1 pre_call_index = cut.double_buffer_read_index expected_result = MagicMock() - cut.currentData = [] - cut.currentData.append({"data": expected_result}) - cut.currentData.append({"data": MagicMock()}) + cut.current_data = [] + cut.current_data.append({"data": expected_result}) + cut.current_data.append({"data": MagicMock()}) # Act result = cut.get_next() @@ -274,11 +453,10 @@ def test_redis_adapter_DataSource_get_next_returns_expected_data_when_new_data_i def test_redis_adapter_DataSource_get_next_when_called_multiple_times_when_new_data_is_true(): # Arrange - # Renew DataSource to ensure test independence cut = DataSource.__new__(DataSource) cut.double_buffer_read_index = pytest.gen.randint(0, 1) cut.new_data_lock = MagicMock() - cut.currentData = [MagicMock(), MagicMock()] + cut.current_data = [MagicMock(), MagicMock()] pre_call_index = cut.double_buffer_read_index expected_data = [] @@ -289,9 +467,9 @@ def test_redis_adapter_DataSource_get_next_when_called_multiple_times_when_new_d cut.new_data = True fake_new_data = MagicMock() if cut.double_buffer_read_index == 0: - cut.currentData[1] = {"data": fake_new_data} + cut.current_data[1] = {"data": fake_new_data} else: - cut.currentData[0] = {"data": fake_new_data} + cut.current_data[0] = {"data": fake_new_data} expected_data.append(fake_new_data) results.append(cut.get_next()) @@ -304,20 +482,19 @@ def test_redis_adapter_DataSource_get_next_when_called_multiple_times_when_new_d def test_redis_adapter_DataSource_get_next_waits_until_data_is_available(mocker): # Arrange - # Renew DataSource to ensure test independence cut = DataSource.__new__(DataSource) cut.new_data_lock = MagicMock() cut.double_buffer_read_index = pytest.gen.randint(0, 1) pre_call_index = cut.double_buffer_read_index expected_result = MagicMock() cut.new_data = None - cut.currentData = [] + cut.current_data = [] if pre_call_index == 0: - cut.currentData.append({"data": MagicMock()}) - cut.currentData.append({"data": expected_result}) + cut.current_data.append({"data": MagicMock()}) + cut.current_data.append({"data": expected_result}) else: - cut.currentData.append({"data": expected_result}) - cut.currentData.append({"data": MagicMock()}) + cut.current_data.append({"data": expected_result}) + cut.current_data.append({"data": MagicMock()}) num_falses = pytest.gen.randint(1, 10) side_effect_list = [False] * num_falses @@ -358,11 +535,11 @@ def test_redis_adapter_DataSource_message_listener_warns_of_exit_and_does_not_ru fake_server = MagicMock() fake_server.fake_pubsub = MagicMock() - fake_listener = MagicMock(name='fake_listener') + fake_listener = MagicMock(name="fake_listener") fake_listener.__next__.side_effect = StopIteration - mocker.patch.object(fake_server.fake_pubsub, 'listen', side_effect=[fake_listener]) - mocker.patch(redis_adapter.__name__ + '.json.loads') - mocker.patch(redis_adapter.__name__ + '.print_msg') + mocker.patch.object(fake_server.fake_pubsub, "listen", side_effect=[fake_listener]) + mocker.patch(redis_adapter.__name__ + ".json.loads") + mocker.patch(redis_adapter.__name__ + ".print_msg") # Act cut.message_listener(fake_server.fake_pubsub) @@ -382,16 +559,22 @@ def test_redis_adapter_DataSource_message_listener_prints_warning_when_receiving # Arrange cut = DataSource.__new__(DataSource) - #cut.pubsub = MagicMock() + # cut.pubsub = MagicMock() fake_server = MagicMock() fake_server.fake_pubsub = MagicMock() - ignored_message_types = ['subscribe', 'unsubscribe', 'psubscribe', 'punsubscribe', 'pmessage'] + ignored_message_types = [ + "subscribe", + "unsubscribe", + "psubscribe", + "punsubscribe", + "pmessage", + ] fake_message = {} - fake_message['type'] = pytest.gen.choice(ignored_message_types) - fake_message['channel'] = str(MagicMock(name='fake_message')).encode('utf-8') - mocker.patch.object(fake_server.fake_pubsub, 'listen', return_value=[fake_message]) - mocker.patch(redis_adapter.__name__ + '.json.loads') - mocker.patch(redis_adapter.__name__ + '.print_msg') + fake_message["type"] = pytest.gen.choice(ignored_message_types) + fake_message["channel"] = str(MagicMock(name="fake_message")).encode("utf-8") + mocker.patch.object(fake_server.fake_pubsub, "listen", return_value=[fake_message]) + mocker.patch(redis_adapter.__name__ + ".json.loads") + mocker.patch(redis_adapter.__name__ + ".print_msg") # Act cut.message_listener(fake_server.fake_pubsub) @@ -418,13 +601,14 @@ def test_redis_adapter_DataSource_message_listener_prints_warning_when_data_not_ fake_server = MagicMock() fake_server.fake_pubsub = MagicMock() fake_message = {} - fake_message['type'] = 'message' - fake_message['channel'] = str( - MagicMock(name='fake_message_channel')).encode('utf-8') - fake_message['data'] = str(MagicMock(name='fake_message_data')) - mocker.patch.object(fake_server.fake_pubsub, 'listen', return_value=[fake_message]) - mocker.patch(redis_adapter.__name__ + '.json.loads', side_effect=ValueError) - mocker.patch(redis_adapter.__name__ + '.print_msg') + fake_message["type"] = "message" + fake_message["channel"] = str(MagicMock(name="fake_message_channel")).encode( + "utf-8" + ) + fake_message["data"] = str(MagicMock(name="fake_message_data")) + mocker.patch.object(fake_server.fake_pubsub, "listen", return_value=[fake_message]) + mocker.patch(redis_adapter.__name__ + ".json.loads", side_effect=ValueError) + mocker.patch(redis_adapter.__name__ + ".print_msg") # Act cut.message_listener(fake_server.fake_pubsub) @@ -450,22 +634,22 @@ def test_redis_adapter_DataSource_message_listener_warns_user_when_processed_dat ): # Arrange cut = DataSource.__new__(DataSource) - cut.double_buffer_read_index = pytest.gen.choice([0 , 1]) - cut.currentData = {0: {'headers': [], 'data': []}, - 1: {'headers': [], 'data': []}} + cut.double_buffer_read_index = pytest.gen.choice([0, 1]) + cut.current_data = {0: {"headers": [], "data": []}, 1: {"headers": [], "data": []}} fake_server = MagicMock() fake_server.fake_pubsub = MagicMock() cut.new_data_lock = MagicMock() cut.new_data = False fake_message = {} - fake_message['type'] = 'message' - fake_message['channel'] = str( - MagicMock(name='fake_message_channel')).encode('utf-8') - fake_message['data'] = '{}' # empty_message - mocker.patch.object(fake_server.fake_pubsub, 'listen', return_value=[fake_message]) - mocker.patch(redis_adapter.__name__ + '.json.loads', return_value={}) - mocker.patch(redis_adapter.__name__ + '.print_msg') + fake_message["type"] = "message" + fake_message["channel"] = str(MagicMock(name="fake_message_channel")).encode( + "utf-8" + ) + fake_message["data"] = "{}" # empty_message + mocker.patch.object(fake_server.fake_pubsub, "listen", return_value=[fake_message]) + mocker.patch(redis_adapter.__name__ + ".json.loads", return_value={}) + mocker.patch(redis_adapter.__name__ + ".print_msg") # Act cut.message_listener(fake_server.fake_pubsub) @@ -490,24 +674,28 @@ def test_redis_adapter_DataSource_message_listener_warns_of_received_key_that_do ): # Arrange cut = DataSource.__new__(DataSource) - cut.double_buffer_read_index = pytest.gen.choice([0 , 1]) - cut.currentData = {0: {'headers': ['time'], - 'data': ['-']}, - 1: {'headers': ['time'], - 'data': ['-']}} + cut.double_buffer_read_index = pytest.gen.choice([0, 1]) + cut.current_data = { + 0: {"headers": ["time"], "data": ["-"]}, + 1: {"headers": ["time"], "data": ["-"]}, + } fake_server = MagicMock() fake_server.fake_pubsub = MagicMock() cut.new_data_lock = MagicMock() cut.new_data = False fake_message = {} - fake_message['type'] = 'message' - fake_message['channel'] = str( - MagicMock(name='fake_message_channel')).encode('utf-8') - fake_message['data'] = '{"time":0, "unknown_key":0}' - mocker.patch.object(fake_server.fake_pubsub, 'listen', return_value=[fake_message]) - mocker.patch(redis_adapter.__name__ + '.json.loads', return_value={"time":0, "unknown_key":0}) - mocker.patch(redis_adapter.__name__ + '.print_msg') + fake_message["type"] = "message" + fake_message["channel"] = str(MagicMock(name="fake_message_channel")).encode( + "utf-8" + ) + fake_message["data"] = '{"time":0, "unknown_key":0}' + mocker.patch.object(fake_server.fake_pubsub, "listen", return_value=[fake_message]) + mocker.patch( + redis_adapter.__name__ + ".json.loads", + return_value={"time": 0, "unknown_key": 0}, + ) + mocker.patch(redis_adapter.__name__ + ".print_msg") # Act cut.message_listener(fake_server.fake_pubsub) @@ -532,28 +720,31 @@ def test_redis_adapter_DataSource_message_listener_warns_of_expected_keys_that_d ): # Arrange cut = DataSource.__new__(DataSource) - cut.double_buffer_read_index = pytest.gen.choice([0 , 1]) + cut.double_buffer_read_index = pytest.gen.choice([0, 1]) fake_server = MagicMock() fake_server.fake_pubsub = MagicMock() cut.new_data_lock = MagicMock() cut.new_data = False fake_message = {} - fake_message['type'] = 'message' - fake_message['channel'] = str( - MagicMock(name='fake_message_channel')).encode('utf-8') - cut.currentData = {0: {'headers': ['time', - f'{fake_message["channel"].decode()}' \ - '.missing_key'], - 'data': ['-', '-']}, - 1: {'headers': ['time', - f'{fake_message["channel"].decode()}' \ - '.missing_key'], - 'data': ['-', '-']}} - fake_message['data'] = '{}' - mocker.patch.object(fake_server.fake_pubsub, 'listen', return_value=[fake_message]) - mocker.patch(redis_adapter.__name__ + '.json.loads', return_value={}) - mocker.patch(redis_adapter.__name__ + '.print_msg') + fake_message["type"] = "message" + fake_message["channel"] = str(MagicMock(name="fake_message_channel")).encode( + "utf-8" + ) + cut.current_data = { + 0: { + "headers": ["time", f'{fake_message["channel"].decode()}' ".missing_key"], + "data": ["-", "-"], + }, + 1: { + "headers": ["time", f'{fake_message["channel"].decode()}' ".missing_key"], + "data": ["-", "-"], + }, + } + fake_message["data"] = "{}" + mocker.patch.object(fake_server.fake_pubsub, "listen", return_value=[fake_message]) + mocker.patch(redis_adapter.__name__ + ".json.loads", return_value={}) + mocker.patch(redis_adapter.__name__ + ".print_msg") # Act cut.message_listener(fake_server.fake_pubsub) @@ -584,32 +775,37 @@ def test_redis_adapter_DataSource_message_listener_updates_new_data_with_receive ): # Arrange cut = DataSource.__new__(DataSource) - cut.double_buffer_read_index = pytest.gen.choice([0 , 1]) + cut.double_buffer_read_index = pytest.gen.choice([0, 1]) fake_server = MagicMock() fake_server.fake_pubsub = MagicMock() cut.new_data_lock = MagicMock() cut.new_data = False fake_message = {} - fake_message['type'] = 'message' - fake_message['channel'] = str( - MagicMock(name='fake_message_channel')).encode('utf-8') - cut.currentData = {0: {'headers': ['time', - f'{fake_message["channel"].decode()}' \ - '.correct_key', 'fakeotherchannel.x'], - 'data': ['-', '-', '0']}, - 1: {'headers': ['time', - f'{fake_message["channel"].decode()}' \ - '.correct_key', 'fakeotherchannel.x'], - 'data': ['-', '-', '0']}} - fake_message['data'] = '{}' - mocker.patch.object(fake_server.fake_pubsub, 'listen', return_value=[fake_message]) - fake_data = { - 'time': pytest.gen.randint(1, 100), # from 1 to 100 arbitrary - 'correct_key': pytest.gen.randint(1, 100), # from 1 to 100 arbitrary + fake_message["type"] = "message" + fake_message["channel"] = str(MagicMock(name="fake_message_channel")).encode( + "utf-8" + ) + cut.current_data = { + 0: { + "headers": [ + "time", + f'{fake_message["channel"].decode()}' ".correct_key", + "fakeotherchannel.x", + ], + "data": ["-", "-", "0"], + }, + 1: { + "headers": [ + "time", + f'{fake_message["channel"].decode()}' ".correct_key", + "fakeotherchannel.x", + ], + "data": ["-", "-", "0"], + }, } fake_message["data"] = "{}" - mocker.patch.object(cut.pubsub, "listen", return_value=[fake_message]) + mocker.patch.object(fake_server.fake_pubsub, "listen", return_value=[fake_message]) fake_data = { "time": pytest.gen.randint(1, 100), # from 1 to 100 arbitrary "correct_key": pytest.gen.randint(1, 100), # from 1 to 100 arbitrary @@ -624,8 +820,8 @@ def test_redis_adapter_DataSource_message_listener_updates_new_data_with_receive assert redis_adapter.json.loads.call_count == 1 assert redis_adapter.json.loads.call_args_list[0].args == (fake_message["data"],) assert cut.new_data == True - print(cut.currentData[cut.double_buffer_read_index]) - assert cut.currentData[(cut.double_buffer_read_index + 1) % 2]["data"] == [ + print(cut.current_data[cut.double_buffer_read_index]) + assert cut.current_data[(cut.double_buffer_read_index + 1) % 2]["data"] == [ fake_data["time"], fake_data["correct_key"], "-", @@ -692,15 +888,17 @@ def test_redis_adapter_DataSource_parse_meta_data_file_raises_ConfigKeyError_whe assert redis_adapter.parseJson.call_args_list[0].args == (arg_configFile,) assert e_info.match(exception_message) -def test_redis_adapter_DataSource_parse_meta_data_file_returns_call_to_extract_meta_data_handle_ss_breakdown(mocker): + +def test_redis_adapter_DataSource_parse_meta_data_file_returns_call_to_extract_meta_data_handle_ss_breakdown( + mocker, +): # Arrange cut = DataSource.__new__(DataSource) arg_configFile = MagicMock() arg_ss_breakdown = MagicMock() expected_extracted_configs = MagicMock() - fake_meta = {'fake_other_stuff': MagicMock(), - 'order': MagicMock()} + fake_meta = {"fake_other_stuff": MagicMock(), "order": MagicMock()} mocker.patch( redis_adapter.__name__ + ".extract_meta_data_handle_ss_breakdown", @@ -720,9 +918,10 @@ def test_redis_adapter_DataSource_parse_meta_data_file_returns_call_to_extract_m 0 ].args == (arg_configFile, arg_ss_breakdown) assert redis_adapter.parseJson.call_count == 1 - assert redis_adapter.parseJson.call_args_list[0].args == (arg_configFile, ) + assert redis_adapter.parseJson.call_args_list[0].args == (arg_configFile,) assert result == expected_extracted_configs + # redis_adapter get_vehicle_metadata tests def test_redis_adapter_DataSource_get_vehicle_metadata_returns_list_of_headers_and_list_of_test_assignments(): # Arrange @@ -759,35 +958,53 @@ def test_redis_adapter_DataSource_process_data_file_does_nothing(): assert result == expected_result -def test_redis_adapter_DataSource_parse_meta_data_file_redis_in_keys_subscriptions_exist_and_adds_redis_to_server_configs(mocker): +def test_redis_adapter_DataSource_parse_meta_data_file_redis_in_keys_subscriptions_exist_and_adds_redis_to_server_configs( + mocker, +): # Arrange cut = DataSource.__new__(DataSource) arg_configFile = MagicMock() arg_ss_breakdown = MagicMock() - fake_server_configs = [{"subscriptions": ["state_0", "state_1"]}, {"subscriptions": ["state_2", "state_3"]}] + fake_server_configs = [ + {"subscriptions": ["state_0", "state_1"]}, + {"subscriptions": ["state_2", "state_3"]}, + ] cut.server_configs = MagicMock() expected_extracted_configs = MagicMock() - fake_meta = {'fake_other_stuff': MagicMock(), - 'redis': fake_server_configs, - 'order': MagicMock()} + fake_meta = { + "fake_other_stuff": MagicMock(), + "redis": fake_server_configs, + "order": MagicMock(), + } - mocker.patch(redis_adapter.__name__ + '.extract_meta_data_handle_ss_breakdown', return_value=expected_extracted_configs) - mocker.patch(redis_adapter.__name__ + '.parseJson', return_value=fake_meta) + mocker.patch( + redis_adapter.__name__ + ".extract_meta_data_handle_ss_breakdown", + return_value=expected_extracted_configs, + ) + mocker.patch(redis_adapter.__name__ + ".parseJson", return_value=fake_meta) # Act - result = cut.parse_meta_data_file(arg_configFile, arg_ss_breakdown, ) + result = cut.parse_meta_data_file( + arg_configFile, + arg_ss_breakdown, + ) # Assert assert redis_adapter.extract_meta_data_handle_ss_breakdown.call_count == 1 - assert redis_adapter.extract_meta_data_handle_ss_breakdown.call_args_list[0].args == (arg_configFile, arg_ss_breakdown) + assert redis_adapter.extract_meta_data_handle_ss_breakdown.call_args_list[ + 0 + ].args == (arg_configFile, arg_ss_breakdown) assert redis_adapter.parseJson.call_count == 1 - assert redis_adapter.parseJson.call_args_list[0].args == (arg_configFile, ) + assert redis_adapter.parseJson.call_args_list[0].args == (arg_configFile,) assert result == expected_extracted_configs - assert cut.server_configs == fake_meta['redis'] + assert cut.server_configs == fake_meta["redis"] + -def test_redis_adapter_DataSource_parse_meta_data_file_redis_in_keys_subscriptions_do_not_exist(mocker): +def test_redis_adapter_DataSource_parse_meta_data_file_redis_in_keys_subscriptions_do_not_exist( + mocker, +): # Arrange cut = DataSource.__new__(DataSource) arg_configFile = MagicMock() @@ -797,22 +1014,33 @@ def test_redis_adapter_DataSource_parse_meta_data_file_redis_in_keys_subscriptio cut.server_configs = MagicMock() expected_extracted_configs = MagicMock() - fake_meta = {'fake_other_stuff': MagicMock(), - 'redis': fake_server_configs, - 'order': MagicMock()} + fake_meta = { + "fake_other_stuff": MagicMock(), + "redis": fake_server_configs, + "order": MagicMock(), + } - mocker.patch(redis_adapter.__name__ + '.extract_meta_data_handle_ss_breakdown', return_value=expected_extracted_configs) - mocker.patch(redis_adapter.__name__ + '.parseJson', return_value=fake_meta) + mocker.patch( + redis_adapter.__name__ + ".extract_meta_data_handle_ss_breakdown", + return_value=expected_extracted_configs, + ) + mocker.patch(redis_adapter.__name__ + ".parseJson", return_value=fake_meta) # Act with pytest.raises(ConfigKeyError) as e_info: - cut.parse_meta_data_file(arg_configFile, arg_ss_breakdown, ) - + cut.parse_meta_data_file( + arg_configFile, + arg_ss_breakdown, + ) # Assert assert redis_adapter.extract_meta_data_handle_ss_breakdown.call_count == 1 - assert redis_adapter.extract_meta_data_handle_ss_breakdown.call_args_list[0].args == (arg_configFile, arg_ss_breakdown) + assert redis_adapter.extract_meta_data_handle_ss_breakdown.call_args_list[ + 0 + ].args == (arg_configFile, arg_ss_breakdown) assert redis_adapter.parseJson.call_count == 1 - assert redis_adapter.parseJson.call_args_list[0].args == (arg_configFile, ) + assert redis_adapter.parseJson.call_args_list[0].args == (arg_configFile,) assert cut.server_configs == [] - assert e_info.match(f'Config file: \'{arg_configFile}\' missing required key \'subscriptions\' from 0 in key \'redis\'') + assert e_info.match( + f"Config file: '{arg_configFile}' missing required key 'subscriptions' from 0 in key 'redis'" + )