Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

011 - Unit Testing #11

Merged
merged 8 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ env.ini
*.ini
*.sock

*.log

instance/

.pytest_cache/
Expand Down
51 changes: 44 additions & 7 deletions __init__.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,50 @@
from datetime import datetime

from flask import Flask
from flask.logging import default_handler
import logging
from logging.config import dictConfig

from services import db
from routes.health import health_api
from routes.drinks import drinks_api
from routes.users import users_api
from routes.tags import tags_api
from routes.search import search_api
from config import get_env_vars

dictConfig({
"version": 1,
"formatters": {
"default": {
"format": "[%(asctime)s] %(levelname)s in %(module)s: %(message)s",
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"stream": "ext://sys.stdout",
"formatter": "default",
},
"file": {
"class": "logging.FileHandler",
"filename": "cw-be.log",
"formatter": "default",
},
"size-rotate": {
"class": "logging.handlers.RotatingFileHandler",
"filename": "cw-be.log",
"maxBytes": 1000000,
"backupCount": 5,
"formatter": "default",
},
},
"root": {"level": "DEBUG", "handlers": ["console", "file"]},
})

def create_app():
app = Flask(__name__)
app.logger.removeHandler(default_handler)
#allow captain's world webapp to hit server - designed to run on same host

cw_dir, cw_db, cw_secret, cw_flask = get_env_vars()
Expand All @@ -23,18 +57,21 @@ def create_app():
SESSION_COOKIE_SAMESITE = cw_flask['cookie_samesite']
)

print(f"Starting Captain's World Backend Server at {datetime.now()}")
print(f"Debug Mode: {app.config['DEBUG']}")
print(f"Image dir set to: {app.config['DIR']['images']}")
print(f"Session Cookie HTTP Only: {app.config['SESSION_COOKIE_HTTPONLY']}")
print(f"Session Cookie Secure: {app.config['SESSION_COOKIE_SECURE']}")
print(f"Session Cookie Same Site: {app.config['SESSION_COOKIE_SAMESITE']}")
print(f"Secret Key set: {app.secret_key is not None}")
logging.info(f"Starting Captain's World Backend Server at {datetime.now()}")
logging.info(f"Debug Mode: {app.config['DEBUG']}")
logging.info(f"Image dir set to: {app.config['DIR']['images']}")
logging.info(f"Image extensions allowed set to: {app.config['DIR']['extensions']}")
logging.info(f"Session Cookie HTTP Only: {app.config['SESSION_COOKIE_HTTPONLY']}")
logging.info(f"Session Cookie Secure: {app.config['SESSION_COOKIE_SECURE']}")
logging.info(f"Session Cookie Same Site: {app.config['SESSION_COOKIE_SAMESITE']}")
logging.info(f"Secret Key set: {app.secret_key is not None}")

app.register_blueprint(health_api)
app.register_blueprint(drinks_api)
app.register_blueprint(users_api)
app.register_blueprint(tags_api)
app.register_blueprint(search_api)

db.init_app(app)

return app
13 changes: 8 additions & 5 deletions config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import os
import sys

import logging

def get_db_config():
db_config = {
'host' : os.getenv("CW_DB_HOST"),
Expand All @@ -12,7 +14,7 @@ def get_db_config():

for value in db_config:
if db_config[value] is None:
print("ERROR: missing database environment variable(s)!")
logging.error("Missing database environment variable(s)!")
sys.exit()

return db_config
Expand All @@ -24,7 +26,7 @@ def get_dir_config():
}

if dir_config['images'] is None:
print("ERROR: missing directory environment variable(s)!")
logging.error("Missing directory environment variable(s)!")
sys.exit()

return dir_config
Expand All @@ -35,7 +37,7 @@ def get_secret_config():
}
for value in secret_config:
if secret_config[value] is None:
print("ERROR: missing secret environment variable(s)!")
logging.error("Missing secret environment variable(s)!")
sys.exit()

return secret_config
Expand All @@ -49,9 +51,10 @@ def get_flask_config():

for value in flask_config:
if flask_config[value] is None:
print("ERROR: missing flask configuration env vars!")
logging.error("Missing flask configuration env vars!")
sys.exit()
return flask_config

def get_env_vars():
return (get_dir_config(), get_db_config(), get_secret_config(), get_flask_config())
return (get_dir_config(), get_db_config(), get_secret_config(), get_flask_config())

5 changes: 3 additions & 2 deletions models/drink.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import dateutil.parser
import logging

from enum import Enum

Expand Down Expand Up @@ -38,15 +39,15 @@ def validate(self, data):
if 'c_description' not in data or type(data['c_description']) != str:
errors.append("Description missing or malformed")
if len(errors) > 0:
print(errors) #TODO log this properly
logging.warn(errors) #TODO log this properly
return False
return True

def validate_iso_date(self, data):
try:
dateutil.parser.isoparse(data)
except ValueError:
print('LOG THIS: DateEnjoyed string is malformed')
logging.warn('DateEnjoyed string is malformed')
return False
return True

Expand Down
11 changes: 6 additions & 5 deletions models/user.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import bcrypt
import logging

class User:
def __init__(self, data):
Expand All @@ -14,8 +15,8 @@ def check_user(self, username, hashed_pw):
hashed_pw = hashed_pw.encode('utf-8')
return bcrypt.checkpw(self.password, hashed_pw)
except ValueError as ve:
print("Failure to check password via bcrypt")
print(ve)
logging.warn("Failure to check password via bcrypt")
logging.warn(ve)
return False
#return self.username == username and bcrypt.checkpw(self.hashed_pw, hashed_pw)

Expand All @@ -26,14 +27,14 @@ def update_login_query(self):
return f"UPDATE t_users SET c_last_login=NOW() where c_username='{self.username}'"

@staticmethod
def create_session(session):
def create_session_query(session):
return f"INSERT INTO t_sessions (c_session_name) VALUES ('{session}')"

@staticmethod
def fetch_session(session):
def fetch_session_query(session):
return f'SELECT c_id, c_login_time FROM t_sessions WHERE c_session_name="{session}"'

@staticmethod
#forces going thru fetch_session for c_id first
def delete_session(session_id):
def delete_session_query(session_id):
return f'DELETE FROM t_sessions WHERE c_id={session_id}'
60 changes: 31 additions & 29 deletions routes/drinks.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from flask import Blueprint, request, jsonify

from .route_util import save_image, is_authorized, unauthorized_response, _build_cors_preflight_response, _make_cors_response
from .route_util import save_image, is_authorized, unauthorized_response, build_cors_preflight_response, make_cors_response
from services.db import get_db, close_db
from models.drink import Drink

Expand All @@ -12,9 +12,9 @@
@drinks_api.route("/detail/<id>", methods=['GET', 'OPTIONS'])
def drink_desc(id):
if request.method == 'OPTIONS':
return _build_cors_preflight_response(request.origin)
return build_cors_preflight_response(request.origin)

res = _make_cors_response(request.origin)
res = make_cors_response(request.origin)

if id is None:
res.status = 400
Expand All @@ -27,7 +27,7 @@ def drink_desc(id):

cursor.execute(query)
result = cursor.fetchone()
close_db(c)
close_db()

#check for errors, adjust response as necessary
json = jsonify(result)
Expand All @@ -40,11 +40,14 @@ def drink_desc(id):
@drinks_api.route("/detail/<id>/edit", methods=['PUT', 'OPTIONS'])
def update_drink(id):
if request.method == 'OPTIONS':
return _build_cors_preflight_response(request.origin)
return build_cors_preflight_response(request.origin)

res = _make_cors_response(request.origin)
res = make_cors_response(request.origin)

if not is_authorized():
c = get_db()
cursor = c.cursor()

if not is_authorized(cursor):
return unauthorized_response(res)

if id is None:
Expand All @@ -60,12 +63,10 @@ def update_drink(id):
res.set_data('One or more parameters is malformed or missing.')
return res

c = get_db()
cursor = c.cursor()
query = drink.update_drinks_query(id, drink)
cursor.execute(query)
c.commit()
close_db(c)
close_db()

res.status = 200
return res
Expand All @@ -76,9 +77,9 @@ def update_drink(id):
@drinks_api.route("/list", methods=['GET', 'OPTIONS'])
def drink_list():
if request.method == 'OPTIONS':
return _build_cors_preflight_response(request.origin)
return build_cors_preflight_response(request.origin)

res = _make_cors_response(request.origin)
res = make_cors_response(request.origin)

limit = int(request.args.get('limit')) if request.args.get('limit') is not None else 12
offset = int(request.args.get('offset')) if request.args.get('offset') is not None else 0
Expand All @@ -88,7 +89,6 @@ def drink_list():
cursor = connection.cursor()

query = Drink.get_drinks_query(limit, offset)
print(query)

cursor.execute(query)
result = cursor.fetchall()
Expand All @@ -104,17 +104,17 @@ def drink_list():
@drinks_api.route("/list/count", methods=['GET', 'OPTIONS'])
def count_drinks():
if request.method == 'OPTIONS':
return _build_cors_preflight_response(request.origin)
return build_cors_preflight_response(request.origin)

res = _make_cors_response(request.origin)
res = make_cors_response(request.origin)

c = get_db()
cursor = c.cursor()
query = Drink.count_drinks_query()

cursor.execute(query)
result = cursor.fetchone()
close_db(c)
close_db()

json = jsonify({'count' : result['count(c_id)']})
json.headers = res.headers
Expand All @@ -126,11 +126,14 @@ def count_drinks():
@drinks_api.route("/new", methods=['POST', 'OPTIONS'])
def post_drink():
if request.method == 'OPTIONS':
return _build_cors_preflight_response(request.origin)
return build_cors_preflight_response(request.origin)

res = _make_cors_response(request.origin)
res = make_cors_response(request.origin)

if not is_authorized():
c = get_db()
cursor = c.cursor()

if not is_authorized(cursor):
return unauthorized_response(res)

#process drink object from request
Expand All @@ -147,8 +150,6 @@ def post_drink():
res.set_data('One or more parameters is malformed or missing.')
return res

c = get_db()
cursor = c.cursor()
query = drink.post_drink_query()
cursor.execute(query)
c.commit()
Expand All @@ -158,7 +159,7 @@ def post_drink():
get_id = "SELECT c_id, c_name FROM t_drink ORDER BY c_id DESC LIMIT 1"
cursor.execute(get_id)
row = cursor.fetchone()
close_db(c)
close_db()

if row['c_name'] == drink.name:
json = jsonify({'c_id' : row['c_id']})
Expand All @@ -173,11 +174,14 @@ def post_drink():
@drinks_api.route("/img/<id>", methods=['POST', 'OPTIONS'])
def post_drink_image(id):
if request.method == 'OPTIONS':
return _build_cors_preflight_response(request.origin)
return build_cors_preflight_response(request.origin)

res = _make_cors_response(request.origin)
res = make_cors_response(request.origin)

c = get_db()
cursor = c.cursor()

if not is_authorized():
if not is_authorized(cursor):
return unauthorized_response(res)

if 'file' not in request.files or request.files['file'].filename == '':
Expand All @@ -189,17 +193,15 @@ def post_drink_image(id):

filename = save_image(id, img)
if filename is None or filename == '':
close_db()
res.status = 500
res.set_data("Unable to save image to disk")
return res
else:
c = get_db()
cursor = c.cursor()
query = Drink.post_drink_image_query(filename, id)
print(query)
cursor.execute(query)
c.commit()
close_db(c)
close_db()

#what if this fails, or get a bad id?
res.set_data(f"saved {filename} to disk")
Expand Down
13 changes: 9 additions & 4 deletions routes/health.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
from flask import Blueprint
from .route_util import create_response
from flask import Blueprint, make_response

health_api = Blueprint('health', __name__, url_prefix = '/api')

@health_api.route("/health", methods = ['GET'])
def health():
return create_response(status = 200, desc = "OK")
res = make_response()
res.status = 200
res.set_data("OK")
return res

@health_api.route("/availability", methods = ['GET'])
def availability():
return create_response(status = 200, desc = "OK")
res = make_response()
res.status = 200
res.set_data("OK")
return res
Loading