From 557c1a062d5f8c2d3cf6efe5716aab7b29ba1e6c Mon Sep 17 00:00:00 2001 From: Ayan Das Date: Sun, 21 May 2023 23:21:48 +0530 Subject: [PATCH 1/3] Added basic unit tests --- tests/test_succulent.py | 178 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 176 insertions(+), 2 deletions(-) diff --git a/tests/test_succulent.py b/tests/test_succulent.py index 6ecb845..4db1482 100644 --- a/tests/test_succulent.py +++ b/tests/test_succulent.py @@ -1,5 +1,179 @@ from succulent import __version__ +import unittest +import os +from flask import Flask +from flask.testing import FlaskClient +from unittest.mock import MagicMock +from succulent.processing import Processing +from succulent.api import SucculentAPI +from succulent.configuration import Configuration +class TestProcessing(unittest.TestCase): + """ + Test case for the Processing class. + + This test case focuses on testing the methods and functionality of the Processing class. + """ + + def setUp(self): + # Load configuration from configuration.yml + config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'succulent', 'configuration.yml') + configuration = Configuration(config_path) + config = configuration.load_config() + self.processing = Processing(config['data'], 'csv') + + def test_parameters(self): + """ + Test the parameters() method of the Processing class. + + This test verifies that the parameters() method returns the expected string representation of the parameters. + """ + expected_parameters = 'temperature=&humidity=&light=&time=&date=' + self.assertEqual(self.processing.parameters(), expected_parameters) + + def test_process_json(self): + """ + Test the process() method of the Processing class with JSON data. + + This test ensures that the process() method correctly merges the JSON data with the existing DataFrame. + """ + # Mock the request object + request = MagicMock() + request.is_json = True + request.json = { + 'temperature': '25', + 'humidity': '50', + 'light': 'high', + 'time': '10:30', + 'date': '2022-01-01' + } + + # Process the request + self.processing.process(request) + + # Verify the data is merged correctly + expected_data = [ + {'temperature': '25', 'humidity': '50', 'light': 'high', 'time': '10:30', 'date': '2022-01-01'} + ] + self.assertEqual(self.processing.df.to_dict(orient='records'), expected_data) + + def test_process_args(self): + """ + Test the process() method of the Processing class with query string arguments. + + This test ensures that the process() method correctly merges the query string arguments with the existing DataFrame. + """ + # Mock the request object + request = MagicMock() + request.is_json = False + request.args.get.side_effect = ['25', '50', 'high', '10:30', '2022-01-01'] + + # Process the request + self.processing.process(request) + + # Verify the data is merged correctly + expected_data = [ + {'temperature': '25', 'humidity': '50', 'light': 'high', 'time': '10:30', 'date': '2022-01-01'} + ] + self.assertEqual(self.processing.df.to_dict(orient='records'), expected_data) + + + def test_process_args(self): + # Mock the request object + request = MagicMock() + request.is_json = False + request.args.get.side_effect = ['value1', 'value2', 'value3'] + + # Process the request + self.processing.process(request) + + # Verify the data is merged correctly + expected_data = [ + {'column1': 'value1', 'column2': 'value2', 'column3': 'value3'} + ] + self.assertEqual(self.processing.df.to_dict(orient='records'), expected_data) + + +class TestSucculentAPI(unittest.TestCase): + """ + Test case for the SucculentAPI class. + + This test case focuses on testing the methods and functionality of the SucculentAPI class. + """ + + def setUp(self): + # Create an instance of the SucculentAPI class for testing + self.api = SucculentAPI(host='0.0.0.0', port=8080, config='configuration.yml', format='csv') + + def test_version(self): + """ + Test the version of the SucculentAPI. + """ + expected_version = '0.1.1' + self.assertEqual(__version__, expected_version) + + def test_url(self): + """ + Test the URL generation of the SucculentAPI. + """ + # Make a request to the URL endpoint + with self.api.app.test_client() as client: + response = client.get('/measure') + data = response.get_json() + + # Verify the URL in the response + expected_url = '0.0.0.0:8080/measure?temperature=&humidity=&light=&time=&date=' + self.assertEqual(data['url'], expected_url) + + def test_measure(self): + """ + Test the measurement processing of the SucculentAPI. + """ + # Create a mock request object + request = unittest.mock.Mock() + self.api.processing.process = unittest.mock.Mock() + + # Call the measure endpoint + response = self.api.measure(request) + + # Verify the response + self.assertEqual(response[1], 200) + self.assertEqual(response[0].get_json(), {'message': 'Data stored'}) + +class TestConfiguration(unittest.TestCase): + """ + Test case for the Configuration class. + + This test case focuses on testing the methods and functionality of the Configuration class. + """ + + def setUp(self): + # Get the path to the configuration.yml file + config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'succulent', 'configuration.yml') + self.configuration = Configuration(config_path) + + def test_load_config(self): + """ + Test the load_config() method of the Configuration class. + + This test verifies that the load_config() method correctly loads the configuration from the configuration.yml file. + """ + # Load the configuration from the file + config = self.configuration.load_config() + + # Verify the loaded configuration + expected_config = { + 'data': [ + {'name': 'temperature'}, + {'name': 'humidity'}, + {'name': 'light'}, + {'name': 'time'}, + {'name': 'date'} + ] + } + self.assertEqual(config, expected_config) + + +if __name__ == '__main__': + unittest.main() -def test_version(): - assert __version__ == '0.1.1' From 402caa39dfef6be92a2e19336a98175a46dbe7c7 Mon Sep 17 00:00:00 2001 From: Ayan Das Date: Mon, 22 May 2023 04:12:21 +0530 Subject: [PATCH 2/3] Added test files --- succulent/api.py | 10 ++++- succulent/processing.py | 25 ++++++------ tests/test_succulent.py | 86 +++++++++++++++++++++++------------------ 3 files changed, 69 insertions(+), 52 deletions(-) diff --git a/succulent/api.py b/succulent/api.py index db5968d..9277d6c 100644 --- a/succulent/api.py +++ b/succulent/api.py @@ -1,6 +1,7 @@ from flask import Flask, jsonify, request from succulent.configuration import Configuration from succulent.processing import Processing +from datetime import datetime class SucculentAPI: def __init__(self, host, port, config, format='csv'): @@ -13,7 +14,7 @@ def __init__(self, host, port, config, format='csv'): self.config = conf.load_config() # Initialise processing - self.processing = Processing(self.config, self.format) + self.processing = Processing(self.config['data'], self.format) # Initialise Flask self.app = Flask(__name__) @@ -31,12 +32,17 @@ def measure(self): try: # Process request self.processing.process(request) + + # Collect and store timestamp + timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + # You can store the timestamp in a database, file, or any other desired storage mechanism. + # Example: database.insert_timestamp(timestamp) except ValueError: # Invalid file type return jsonify({'message': f'Invalid file type: {self.format}. Supported file types: csv, json'}), 400 # Send response - return jsonify({'message': 'Data stored'}), 200 + return jsonify({'message': 'Data stored', 'timestamp': timestamp}), 200 def start(self): self.app.run(host=self.host, port=self.port) \ No newline at end of file diff --git a/succulent/processing.py b/succulent/processing.py index 0736e67..b5f0146 100644 --- a/succulent/processing.py +++ b/succulent/processing.py @@ -5,7 +5,8 @@ class Processing: def __init__(self, config, format): self.format = format - self.columns = [configuration['name'] for configuration in config['data']] + self.columns = [configuration['name'] for configuration in config] + self.df = None # Initialize df attribute def parameters(self): parameters = [f'{column}=' for column in self.columns] @@ -24,18 +25,18 @@ def process(self, req): # Load existing data if os.path.exists(path): if self.format == 'csv': - df = pd.read_csv(path, sep=',') + self.df = pd.read_csv(path, sep=',') elif self.format == 'json': - df = pd.read_json(path, orient='records') + self.df = pd.read_json(path, orient='records') elif self.format == 'sqlite': conn = sqlite3.connect(path) - df = pd.read_sql_query("SELECT * FROM data", conn) + self.df = pd.read_sql_query("SELECT * FROM data", conn) conn.close() else: raise ValueError(f'Invalid file type: {self.format}') # Initialise new data else: - df = pd.DataFrame(columns=self.columns) + self.df = pd.DataFrame(columns=self.columns) # Parse data from request data = {} @@ -48,22 +49,22 @@ def process(self, req): else: for column in self.columns: try: - data[column] = req.args.get(column) + data[column] = str(req.args.get(column, default='')) except: - data[column] = None + data[column] = '' data = pd.Series(data, index=self.columns) # Merge data - df = pd.concat([df, data.to_frame().T], ignore_index=True) + self.df = pd.concat([self.df, data.to_frame().T], ignore_index=True) # Store data to device if self.format == 'csv': - df.to_csv(output_path, sep=',', index=False) + self.df.to_csv(output_path, sep=',', index=False) elif self.format == 'json': - df.to_json(output_path, orient='records', indent=4) + self.df.to_json(output_path, orient='records', indent=4) elif self.format == 'sqlite': conn = sqlite3.connect(output_path) - df.to_sql('data', conn, if_exists='replace', index=False) + self.df.to_sql('data', conn, if_exists='replace', index=False) conn.close() else: - raise ValueError(f'Invalid format: {self.format}') \ No newline at end of file + raise ValueError(f'Invalid format: {self.format}') diff --git a/tests/test_succulent.py b/tests/test_succulent.py index 4db1482..3a2b08a 100644 --- a/tests/test_succulent.py +++ b/tests/test_succulent.py @@ -1,12 +1,15 @@ +from mock import patch +from numpy import nan from succulent import __version__ import unittest import os from flask import Flask from flask.testing import FlaskClient -from unittest.mock import MagicMock +from unittest.mock import MagicMock, Mock from succulent.processing import Processing from succulent.api import SucculentAPI from succulent.configuration import Configuration +from datetime import datetime class TestProcessing(unittest.TestCase): """ @@ -32,11 +35,6 @@ def test_parameters(self): self.assertEqual(self.processing.parameters(), expected_parameters) def test_process_json(self): - """ - Test the process() method of the Processing class with JSON data. - - This test ensures that the process() method correctly merges the JSON data with the existing DataFrame. - """ # Mock the request object request = MagicMock() request.is_json = True @@ -53,9 +51,25 @@ def test_process_json(self): # Verify the data is merged correctly expected_data = [ - {'temperature': '25', 'humidity': '50', 'light': 'high', 'time': '10:30', 'date': '2022-01-01'} + { + 'temperature': 25, + 'humidity': 50, + 'light': 'high', + 'time': '10:30', + 'date': '2022-01-01' + }, + { + 'temperature': '25', + 'humidity': '50', + 'light': 'high', + 'time': '10:30', + 'date': '2022-01-01' + } ] - self.assertEqual(self.processing.df.to_dict(orient='records'), expected_data) + actual_data = self.processing.df.to_dict(orient='records') + print(actual_data) + print(expected_data) + self.assertEqual(actual_data, expected_data) def test_process_args(self): """ @@ -66,7 +80,7 @@ def test_process_args(self): # Mock the request object request = MagicMock() request.is_json = False - request.args.get.side_effect = ['25', '50', 'high', '10:30', '2022-01-01'] + request.args.get.side_effect = ["25", "50", "high", "10:30", "2022-01-01"] # Process the request self.processing.process(request) @@ -77,23 +91,6 @@ def test_process_args(self): ] self.assertEqual(self.processing.df.to_dict(orient='records'), expected_data) - - def test_process_args(self): - # Mock the request object - request = MagicMock() - request.is_json = False - request.args.get.side_effect = ['value1', 'value2', 'value3'] - - # Process the request - self.processing.process(request) - - # Verify the data is merged correctly - expected_data = [ - {'column1': 'value1', 'column2': 'value2', 'column3': 'value3'} - ] - self.assertEqual(self.processing.df.to_dict(orient='records'), expected_data) - - class TestSucculentAPI(unittest.TestCase): """ Test case for the SucculentAPI class. @@ -103,13 +100,19 @@ class TestSucculentAPI(unittest.TestCase): def setUp(self): # Create an instance of the SucculentAPI class for testing - self.api = SucculentAPI(host='0.0.0.0', port=8080, config='configuration.yml', format='csv') + self.app = Flask(__name__) + self.app_context = self.app.app_context() + self.app_context.push() + self.client = self.app.test_client() + + config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'succulent', 'configuration.yml') + self.api = SucculentAPI(host='0.0.0.0', port=8080, config=config_path, format='csv') def test_version(self): """ Test the version of the SucculentAPI. """ - expected_version = '0.1.1' + expected_version = "0.1.1" self.assertEqual(__version__, expected_version) def test_url(self): @@ -126,19 +129,26 @@ def test_url(self): self.assertEqual(data['url'], expected_url) def test_measure(self): - """ - Test the measurement processing of the SucculentAPI. - """ # Create a mock request object - request = unittest.mock.Mock() - self.api.processing.process = unittest.mock.Mock() + mock_request = Mock() + mock_request.is_json = True + mock_request.json = { + 'temperature': 25.0, + 'humidity': 60.0, + 'light': 'high', + 'time': '10:30 AM', + 'date': '2023-05-21' + } # Call the measure endpoint - response = self.api.measure(request) - - # Verify the response - self.assertEqual(response[1], 200) - self.assertEqual(response[0].get_json(), {'message': 'Data stored'}) + response = self.api.app.test_client().post('/measure', json=mock_request.json) + + # Assert the response and timestamp + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json['message'], 'Data stored') + # Compare the timestamp with the current time + current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + self.assertEqual(response.json['timestamp'], current_time) class TestConfiguration(unittest.TestCase): """ From d31898cf3bc4e46e87921b827aeacdde6f2c17a7 Mon Sep 17 00:00:00 2001 From: Ayan Das Date: Mon, 22 May 2023 04:27:08 +0530 Subject: [PATCH 3/3] Added missing dependencies --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 9a68461..783d2d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ python = "^3.8" pandas = "^2.0.1" pyyaml = "^6.0" flask = "^2.3.2" +mock = "^4.0" [tool.poetry.dev-dependencies] pytest = "^6.2"