From ddf49ea689e3cf645fe03ffa74157006f3fc8bdc Mon Sep 17 00:00:00 2001 From: Niccolo' Zanotti Date: Thu, 5 Sep 2024 21:25:02 +0200 Subject: [PATCH 1/2] Changing get_current_occupants() to overcome temporary server life: manually open .csv database to check status. --- app.py | 82 ++++++++++++++++++----------------- requirements.txt | 6 +-- test_app.py | 109 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 156 insertions(+), 41 deletions(-) create mode 100644 test_app.py diff --git a/app.py b/app.py index a5d2482..592d359 100644 --- a/app.py +++ b/app.py @@ -5,17 +5,15 @@ import boto3 import csv from datetime import datetime, timezone +from io import StringIO from awsgi import response -import botocore.exceptions + app = Flask(__name__) CORS(app, resources={r"/*": {"origins": "*"}}, supports_credentials=True) # Initialize the S3 client s3 = boto3.client('s3') -BUCKET_NAME = 'ugo-people-tracker' # S3 bucket name - -# In-memory storage for current occupants -occupants = set() +BUCKET_NAME = 'ugo-people-tracker' @app.route('/people/register', methods=['POST']) @@ -23,10 +21,8 @@ def register(): data = request.json name = data.get('name') if name: - occupants.add(name) log_action(name, 'register') - return json.dumps({"status": "registered", "occupants": list(occupants)}), 200, { - 'Content-Type': 'application/json'} + return json.dumps({"status": "registered"}), 200, {'Content-Type': 'application/json'} else: return json.dumps({"error": "Name is required"}), 400, {'Content-Type': 'application/json'} @@ -36,16 +32,15 @@ def unregister(): data = request.json name = data.get('name') if name: - occupants.discard(name) log_action(name, 'unregister') - return json.dumps({"status": "unregistered", "occupants": list(occupants)}), 200, { - 'Content-Type': 'application/json'} + return json.dumps({"status": "unregistered"}), 200, {'Content-Type': 'application/json'} else: return json.dumps({"error": "Name is required"}), 400, {'Content-Type': 'application/json'} @app.route('/people/status', methods=['GET']) def status(): + occupants = get_current_occupants() return json.dumps({ "status": "open" if occupants else "closed", "occupants": list(occupants), @@ -55,53 +50,64 @@ def status(): def log_action(name, action): now = datetime.now(timezone.utc) - log_entry = [now.date(), now.time(), name, action] + log_entry = [now.date().isoformat(), now.time().isoformat(), name, action] append_log_to_s3(log_entry) def append_log_to_s3(log_entry): today = datetime.today() - LOG_FILE_KEY = f'/tmp/{today.strftime("%Y-%m-%d")}-logs.csv' - log_file = f'{LOG_FILE_KEY}' # Temporary file path + LOG_FILE_KEY = f'{today.strftime("%Y-%m-%d")}-logs.csv' + + try: + response = s3.get_object(Bucket=BUCKET_NAME, Key=LOG_FILE_KEY) + existing_content = response['Body'].read().decode('utf-8') + except s3.exceptions.NoSuchKey: + existing_content = 'Date,Time,Name,Action\n' + + csv_buffer = StringIO() + csv_buffer.write(existing_content) + csv_writer = csv.writer(csv_buffer) + csv_writer.writerow(log_entry) + + s3.put_object(Bucket=BUCKET_NAME, Key=LOG_FILE_KEY, Body=csv_buffer.getvalue()) + + +def get_current_occupants(): + today = datetime.today() + LOG_FILE_KEY = f'{today.strftime("%Y-%m-%d")}-logs.csv' - # Attempt to download the existing log file from S3 if it exists try: - s3.download_file(BUCKET_NAME, LOG_FILE_KEY, log_file) - file_exists = True + response = s3.get_object(Bucket=BUCKET_NAME, Key=LOG_FILE_KEY) + csv_content = response['Body'].read().decode('utf-8') + csv_reader = csv.reader(StringIO(csv_content)) + next(csv_reader) # Skip header + + occupants = set() + for row in csv_reader: + if row[3] == 'register': + occupants.add(row[2]) + elif row[3] == 'unregister': + occupants.discard(row[2]) + + return occupants except s3.exceptions.NoSuchKey: - # The file doesn't exist in S3; create a new one - file_exists = False - except botocore.exceptions.ClientError as e: - if e.response['Error']['Code'] == '404': - # The file doesn't exist in S3; create a new one - file_exists = False - else: - # If it's a different error, re-raise it - raise - - # Append the log entry to the file - with open(log_file, 'a', newline='') as file: - writer = csv.writer(file) - if not file_exists: - writer.writerow(['Date', 'Time', 'Name', 'Action']) # Write header if file doesn't exist - writer.writerow(log_entry) - - # Upload the updated log file back to S3 - s3.upload_file(log_file, BUCKET_NAME, LOG_FILE_KEY) + return set() + # Configure logging logger = logging.getLogger() logger.setLevel(logging.INFO) + # AWS Lambda handler def lambda_handler(event, context): # Log the full event (this includes the request body and other information) logger.info(f"Received event: {json.dumps(event)}") - + # Log only the request body (if present) if 'body' in event: logger.info(f"Request body: {event['body']}") - + # Call the response function (assuming it's part of your application logic) return response(app, event, context) diff --git a/requirements.txt b/requirements.txt index b5d1e54..031718d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -flask -flask-cors +aws-wsgi boto3 -aws-wsgi \ No newline at end of file +flask +flask-cors \ No newline at end of file diff --git a/test_app.py b/test_app.py new file mode 100644 index 0000000..2bc771f --- /dev/null +++ b/test_app.py @@ -0,0 +1,109 @@ +import unittest +from unittest.mock import patch, MagicMock +from io import BytesIO +from datetime import datetime +import json +import sys +import os + +# Add the parent directory to the Python path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +from app import app, log_action, get_current_occupants, append_log_to_s3 + + +class TestAffluenzaS3(unittest.TestCase): + def setUp(self): + self.app = app.test_client() + self.app.testing = True + + @patch('app.s3') + def test_register(self, mock_s3): + # Mock S3 get_object and put_object methods + mock_s3.get_object.return_value = {'Body': BytesIO(b'')} + mock_s3.put_object.return_value = {} + + response = self.app.post('/people/register', + data=json.dumps({'name': 'John Doe'}), + content_type='application/json') + + self.assertEqual(response.status_code, 200) + data = json.loads(response.data) + self.assertEqual(data['status'], 'registered') + + # Verify S3 put_object was called + mock_s3.put_object.assert_called() + + @patch('app.s3') + def test_unregister(self, mock_s3): + # Mock S3 get_object and put_object methods + mock_s3.get_object.return_value = {'Body': BytesIO(b'')} + mock_s3.put_object.return_value = {} + + response = self.app.post('/people/unregister', + data=json.dumps({'name': 'John Doe'}), + content_type='application/json') + + self.assertEqual(response.status_code, 200) + data = json.loads(response.data) + self.assertEqual(data['status'], 'unregistered') + + # Verify S3 put_object was called + mock_s3.put_object.assert_called() + + @patch('app.s3') + def test_status(self, mock_s3): + # Mock S3 get_object method + mock_response = { + 'Body': BytesIO( + b'Date,Time,Name,Action\n2023-09-05,12:00:00,John Doe,register\n2023-09-05,12:30:00,Jane Doe,register\n2023-09-05,13:00:00,John Doe,unregister') + } + mock_s3.get_object.return_value = mock_response + + response = self.app.get('/people/status') + + self.assertEqual(response.status_code, 200) + data = json.loads(response.data) + self.assertEqual(data['status'], 'open') + self.assertEqual(data['occupants'], ['Jane Doe']) + self.assertEqual(data['count'], 1) + + @patch('app.s3') + def test_log_action(self, mock_s3): + # Mock S3 get_object and put_object methods + mock_s3.get_object.return_value = {'Body': BytesIO(b'')} + mock_s3.put_object.return_value = {} + + log_action('John Doe', 'register') + + # Verify S3 put_object was called + mock_s3.put_object.assert_called() + + @patch('app.s3') + def test_get_current_occupants(self, mock_s3): + # Mock S3 get_object method + mock_response = { + 'Body': BytesIO( + b'Date,Time,Name,Action\n2023-09-05,12:00:00,John Doe,register\n2023-09-05,12:30:00,Jane Doe,register\n2023-09-05,13:00:00,John Doe,unregister') + } + mock_s3.get_object.return_value = mock_response + + occupants = get_current_occupants() + + self.assertEqual(occupants, {'Jane Doe'}) + + @patch('app.s3') + def test_append_log_to_s3(self, mock_s3): + # Mock S3 get_object and put_object methods + mock_s3.get_object.return_value = {'Body': BytesIO(b'')} + mock_s3.put_object.return_value = {} + + log_entry = [datetime.now().date(), datetime.now().time(), 'John Doe', 'register'] + append_log_to_s3(log_entry) + + # Verify S3 put_object was called + mock_s3.put_object.assert_called() + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file From 95aa6b15d80e9cbed00f30995ab6a85221c08c73 Mon Sep 17 00:00:00 2001 From: Niccolo' Zanotti Date: Thu, 5 Sep 2024 21:27:25 +0200 Subject: [PATCH 2/2] Update action according to new naming of lambda to app.py --- .github/workflows/deploy-lambda-to-aws.yml | 6 +- test_app.py | 109 --------------------- 2 files changed, 3 insertions(+), 112 deletions(-) delete mode 100644 test_app.py diff --git a/.github/workflows/deploy-lambda-to-aws.yml b/.github/workflows/deploy-lambda-to-aws.yml index e4ec9c3..d114498 100644 --- a/.github/workflows/deploy-lambda-to-aws.yml +++ b/.github/workflows/deploy-lambda-to-aws.yml @@ -26,9 +26,9 @@ jobs: - name: Zip package run: | cd package - zip -r9 ../lambda_function.zip . + zip -r9 ../app.zip . cd .. - zip -g lambda_function.zip lambda_function.py + zip -g app.zip app.py - name: Deploy to AWS Lambda env: @@ -36,4 +36,4 @@ jobs: AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_REGION: eu-north-1 run: | - aws lambda update-function-code --function-name registration-real-time --zip-file fileb://lambda_function.zip \ No newline at end of file + aws lambda update-function-code --function-name registration-real-time --zip-file fileb://app.zip \ No newline at end of file diff --git a/test_app.py b/test_app.py deleted file mode 100644 index 2bc771f..0000000 --- a/test_app.py +++ /dev/null @@ -1,109 +0,0 @@ -import unittest -from unittest.mock import patch, MagicMock -from io import BytesIO -from datetime import datetime -import json -import sys -import os - -# Add the parent directory to the Python path -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - -from app import app, log_action, get_current_occupants, append_log_to_s3 - - -class TestAffluenzaS3(unittest.TestCase): - def setUp(self): - self.app = app.test_client() - self.app.testing = True - - @patch('app.s3') - def test_register(self, mock_s3): - # Mock S3 get_object and put_object methods - mock_s3.get_object.return_value = {'Body': BytesIO(b'')} - mock_s3.put_object.return_value = {} - - response = self.app.post('/people/register', - data=json.dumps({'name': 'John Doe'}), - content_type='application/json') - - self.assertEqual(response.status_code, 200) - data = json.loads(response.data) - self.assertEqual(data['status'], 'registered') - - # Verify S3 put_object was called - mock_s3.put_object.assert_called() - - @patch('app.s3') - def test_unregister(self, mock_s3): - # Mock S3 get_object and put_object methods - mock_s3.get_object.return_value = {'Body': BytesIO(b'')} - mock_s3.put_object.return_value = {} - - response = self.app.post('/people/unregister', - data=json.dumps({'name': 'John Doe'}), - content_type='application/json') - - self.assertEqual(response.status_code, 200) - data = json.loads(response.data) - self.assertEqual(data['status'], 'unregistered') - - # Verify S3 put_object was called - mock_s3.put_object.assert_called() - - @patch('app.s3') - def test_status(self, mock_s3): - # Mock S3 get_object method - mock_response = { - 'Body': BytesIO( - b'Date,Time,Name,Action\n2023-09-05,12:00:00,John Doe,register\n2023-09-05,12:30:00,Jane Doe,register\n2023-09-05,13:00:00,John Doe,unregister') - } - mock_s3.get_object.return_value = mock_response - - response = self.app.get('/people/status') - - self.assertEqual(response.status_code, 200) - data = json.loads(response.data) - self.assertEqual(data['status'], 'open') - self.assertEqual(data['occupants'], ['Jane Doe']) - self.assertEqual(data['count'], 1) - - @patch('app.s3') - def test_log_action(self, mock_s3): - # Mock S3 get_object and put_object methods - mock_s3.get_object.return_value = {'Body': BytesIO(b'')} - mock_s3.put_object.return_value = {} - - log_action('John Doe', 'register') - - # Verify S3 put_object was called - mock_s3.put_object.assert_called() - - @patch('app.s3') - def test_get_current_occupants(self, mock_s3): - # Mock S3 get_object method - mock_response = { - 'Body': BytesIO( - b'Date,Time,Name,Action\n2023-09-05,12:00:00,John Doe,register\n2023-09-05,12:30:00,Jane Doe,register\n2023-09-05,13:00:00,John Doe,unregister') - } - mock_s3.get_object.return_value = mock_response - - occupants = get_current_occupants() - - self.assertEqual(occupants, {'Jane Doe'}) - - @patch('app.s3') - def test_append_log_to_s3(self, mock_s3): - # Mock S3 get_object and put_object methods - mock_s3.get_object.return_value = {'Body': BytesIO(b'')} - mock_s3.put_object.return_value = {} - - log_entry = [datetime.now().date(), datetime.now().time(), 'John Doe', 'register'] - append_log_to_s3(log_entry) - - # Verify S3 put_object was called - mock_s3.put_object.assert_called() - - -if __name__ == '__main__': - unittest.main() \ No newline at end of file