diff --git a/.env b/.env index 463e55c..129c1a6 100644 --- a/.env +++ b/.env @@ -1,2 +1,3 @@ DATABASE_URL = "postgres://localhost:5432/feeling" PGUSER = "postgres" +API_URL = "http://localhost:3000" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4751b83..19c9417 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,9 @@ pip-delete-this-directory.txt # Virtual Environment venv +# pytest +.pytest_cache + # JavaScript node_modules diff --git a/README.md b/README.md index 1fe84db..4780281 100644 --- a/README.md +++ b/README.md @@ -46,3 +46,9 @@ npm run develop ```bash npm run stop-database ``` + +6. To stop the database when you're finished: + +```bash +npm run stop-database +``` diff --git a/package.json b/package.json index 69ce6e2..59a5064 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "develop": "sls offline start", "migrate": "alembic revision -m", "upgrade": "alembic upgrade", + "test": "pytest", "start-database": "docker-compose up -d", "stop-database": "docker-compose down" }, diff --git a/requirements.txt b/requirements.txt index 135f830..37afb8c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,10 @@ black pylint +pytest SQLAlchemy==1.2.15 psycopg2-binary==2.7.6.1 python-dotenv==0.10.1 alembic==1.0.5 argon2-cffi==18.3.0 -jsonschema==2.6.0 \ No newline at end of file +jsonschema==2.6.0 +requests==2.22.0 \ No newline at end of file diff --git a/serverless.yml b/serverless.yml index b7bbbc8..d636ae2 100644 --- a/serverless.yml +++ b/serverless.yml @@ -47,12 +47,12 @@ functions: method: delete cors: true - put_settings: - handler: src/functions/settings.put + post_settings: + handler: src/functions/settings.post events: - http: path: /settings - method: put + method: post cors: true get_settings: diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/functions/settings.py b/src/functions/settings.py index 0dd5d38..f3d846d 100644 --- a/src/functions/settings.py +++ b/src/functions/settings.py @@ -31,13 +31,13 @@ def get(event, context, session): @database @token_required @validate(schema) -def put(event, context, session): +def post(event, context, session): body = event["body"] user_id = event["user_id"] # Update settings in database settings = Settings(user_id, body) - session.add(settings) + session.merge(settings) session.commit() return {"statusCode": 200} diff --git a/src/models/Settings.py b/src/models/Settings.py index ba92fbe..14fcfdd 100644 --- a/src/models/Settings.py +++ b/src/models/Settings.py @@ -14,6 +14,9 @@ def __init__(self, user_id, settings): self.user_id = user_id self.settings = settings + def toJson(self): + return {"id": self.id, "user_id": self.user_id, "settings": self.settings} + def __repr__(self): return "".format( self.id, self.user_id, self.settings, self.created_at, self.updated_at diff --git a/src/setup.py b/src/setup.py index b05110e..c213f6b 100644 --- a/src/setup.py +++ b/src/setup.py @@ -19,7 +19,7 @@ # Create tables if they do not exist Base.metadata.create_all(database) -# create a Session +# Create a Session Session = sessionmaker(bind=database) session = Session() diff --git a/src/utils/decorators.py b/src/utils/decorators.py index b4beabd..d82fc4f 100644 --- a/src/utils/decorators.py +++ b/src/utils/decorators.py @@ -74,7 +74,7 @@ def wrap_function(*args): def token_required(function): - """Decorator handle access tokens""" + """Decorator to handle access tokens""" def wrap_function(*args): event = args[0] diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_quote.py b/tests/test_quote.py new file mode 100644 index 0000000..fdd62b4 --- /dev/null +++ b/tests/test_quote.py @@ -0,0 +1,50 @@ +import requests +from dotenv import load_dotenv +from pytest import fixture + +from src.consts import Emotion +from src.models import Quote + +from .utils import base_url, session, token + + +@fixture +def quote(session): + """Adds a temporary quote to the database""" + + quote = Quote( + "To escape criticism: do nothing, say nothing, be nothing.", + "Elbert Hubbard", + Emotion.UPSET, + ) + session.add(quote) + session.commit() + + yield quote + + session.query(Quote).filter(Quote.id == quote.id).delete() + session.commit() + + +def test_get_a_quote(base_url, token, quote): + """Fetch a random quote""" + + emotion = quote.emotion.name.lower() + + url = f"{base_url}/quote/{emotion}" + response = requests.get(url) + json = response.json() + + assert response.status_code == 200 + assert json["emotion"].lower() == emotion + + +def test_get_a_quote_with_unknown_emotion(base_url, token, quote): + """Attempt to fetch a quote but emotion is not valid""" + + emotion = "meh" + + url = f"{base_url}/quote/{emotion}" + response = requests.get(url) + + assert response.status_code == 400 diff --git a/tests/test_settings.py b/tests/test_settings.py new file mode 100644 index 0000000..1cd2acf --- /dev/null +++ b/tests/test_settings.py @@ -0,0 +1,50 @@ +import requests +from dotenv import load_dotenv +from pytest import fixture + +from src.models import Settings + +from .utils import base_url, session, token + + +@fixture +def settings(session, user): + settings = Settings( + user.id, + {"daily_reminder": {"enabled": True, "time": {"hour": 20, "minute": 00}}}, + ) + session.add(settings) + session.commit() + + yield settings + + session.query(Settings).filter(Settings.id == settings.id).delete() + session.commit() + + +def test_get_settings(base_url, token, settings): + """Fetch a user's settings""" + + url = f"{base_url}/settings" + response = requests.get(url, headers={"Authorization": f"Bearer {token}"}) + json = response.json() + + assert response.status_code == 200 + assert json.settings == settings.toJson() + + +def test_update_settings(base_url, token, settings): + """Update a user's settings""" + + new_settings = { + "daily_reminder": {"enabled": False, "time": {"hour": 21, "minute": 30}} + } + + url = f"{base_url}/settings" + response = requests.post( + url, headers={"Authorization": f"Bearer {token}"}, data=new_settings + ) + json = response.json() + + assert response.status_code == 200 + assert json.settings == new_settings diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..e10d9ab --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,51 @@ +import os + +from pytest import fixture +from dotenv import load_dotenv +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker + +from src.models import User + + +@fixture() +def base_url(): + load_dotenv() + return os.getenv("API_URL") + + +@fixture() +def session(): + """Create a SQLAlchemy database session""" + + # Set environment variables from .env + load_dotenv() + + # Connect to database + database_url = os.getenv("DATABASE_URL") + database = create_engine(database_url) + + # Create a session + SessionMaker = sessionmaker(bind=database) + created_session = SessionMaker() + + yield created_session + + created_session.close() + + +@fixture +def user(session): + user = User("Michael", "Lee", "michael_lee@gmail.com", "password") + session.add(user) + session.commit() + + yield user + + session.query(User).filter(User.id == user.id).delete() + session.commit() + + +@fixture +def token(session): + return None