API: refactor (rename mpwo_api to fittrackee_api)
This commit is contained in:
19
fittrackee_api/Dockerfile
Normal file
19
fittrackee_api/Dockerfile
Normal file
@ -0,0 +1,19 @@
|
||||
FROM python:3.6.3
|
||||
|
||||
MAINTAINER SamR1@users.noreply.github.com
|
||||
|
||||
# set working directory
|
||||
RUN mkdir -p /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# add requirements
|
||||
COPY ./requirements.txt /usr/src/app/fittrackee_api/requirements.txt
|
||||
|
||||
# install requirements
|
||||
RUN pip install -r fittrackee_api/requirements.txt
|
||||
|
||||
# add app
|
||||
COPY . /usr/src/app
|
||||
|
||||
# run server
|
||||
CMD flask run --with-threads -h 0.0.0.0
|
5
fittrackee_api/db/Dockerfile
Normal file
5
fittrackee_api/db/Dockerfile
Normal file
@ -0,0 +1,5 @@
|
||||
FROM postgres:10.1
|
||||
|
||||
MAINTAINER SamR1@users.noreply.github.com
|
||||
|
||||
COPY create.sql /docker-entrypoint-initdb.d
|
7
fittrackee_api/db/create.sql
Normal file
7
fittrackee_api/db/create.sql
Normal file
@ -0,0 +1,7 @@
|
||||
DROP DATABASE IF EXISTS fittrackee;
|
||||
DROP DATABASE IF EXISTS fittrackee_test;
|
||||
CREATE DATABASE fittrackee;
|
||||
CREATE DATABASE fittrackee_test;
|
||||
CREATE USER fittrackee WITH PASSWORD 'fittrackee';
|
||||
GRANT ALL PRIVILEGES ON DATABASE fittrackee TO fittrackee;
|
||||
GRANT ALL PRIVILEGES ON DATABASE fittrackee_test TO fittrackee;
|
65
fittrackee_api/fittrackee_api/__init__.py
Normal file
65
fittrackee_api/fittrackee_api/__init__.py
Normal file
@ -0,0 +1,65 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
from flask import Flask
|
||||
from flask_bcrypt import Bcrypt
|
||||
from flask_migrate import Migrate
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
db = SQLAlchemy()
|
||||
bcrypt = Bcrypt()
|
||||
migrate = Migrate()
|
||||
appLog = logging.getLogger('fittrackee_api')
|
||||
|
||||
|
||||
def create_app():
|
||||
# instantiate the app
|
||||
app = Flask(__name__)
|
||||
|
||||
# set config
|
||||
with app.app_context():
|
||||
app_settings = os.getenv('APP_SETTINGS')
|
||||
app.config.from_object(app_settings)
|
||||
|
||||
# set up extensions
|
||||
db.init_app(app)
|
||||
bcrypt.init_app(app)
|
||||
migrate.init_app(app, db)
|
||||
|
||||
from .users.auth import auth_blueprint # noqa
|
||||
from .users.users import users_blueprint # noqa
|
||||
from .activities.activities import activities_blueprint # noqa
|
||||
from .activities.records import records_blueprint # noqa
|
||||
from .activities.sports import sports_blueprint # noqa
|
||||
from .activities.stats import stats_blueprint # noqa
|
||||
|
||||
app.register_blueprint(users_blueprint, url_prefix='/api')
|
||||
app.register_blueprint(auth_blueprint, url_prefix='/api')
|
||||
app.register_blueprint(activities_blueprint, url_prefix='/api')
|
||||
app.register_blueprint(records_blueprint, url_prefix='/api')
|
||||
app.register_blueprint(sports_blueprint, url_prefix='/api')
|
||||
app.register_blueprint(stats_blueprint, url_prefix='/api')
|
||||
|
||||
if app.debug:
|
||||
logging.getLogger('sqlalchemy').setLevel(logging.WARNING)
|
||||
logging.getLogger('sqlalchemy'
|
||||
).handlers = logging.getLogger('werkzeug').handlers
|
||||
logging.getLogger('sqlalchemy.orm').setLevel(logging.WARNING)
|
||||
logging.getLogger('flake8').propagate = False
|
||||
appLog.setLevel(logging.DEBUG)
|
||||
|
||||
if app.debug:
|
||||
# Enable CORS
|
||||
@app.after_request
|
||||
def after_request(response):
|
||||
response.headers.add('Access-Control-Allow-Origin', '*')
|
||||
response.headers.add(
|
||||
'Access-Control-Allow-Headers', 'Content-Type,Authorization'
|
||||
)
|
||||
response.headers.add(
|
||||
'Access-Control-Allow-Methods',
|
||||
'GET,PUT,POST,DELETE,PATCH,OPTIONS'
|
||||
)
|
||||
return response
|
||||
|
||||
return app
|
342
fittrackee_api/fittrackee_api/activities/activities.py
Normal file
342
fittrackee_api/fittrackee_api/activities/activities.py
Normal file
@ -0,0 +1,342 @@
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
|
||||
from fittrackee_api import appLog, db
|
||||
from flask import Blueprint, current_app, jsonify, request, send_file
|
||||
from sqlalchemy import exc
|
||||
|
||||
from ..users.utils import authenticate, verify_extension
|
||||
from .models import Activity
|
||||
from .utils import (
|
||||
ActivityException, create_activity, edit_activity, get_chart_data,
|
||||
process_files
|
||||
)
|
||||
|
||||
activities_blueprint = Blueprint('activities', __name__)
|
||||
|
||||
|
||||
@activities_blueprint.route('/activities', methods=['GET'])
|
||||
@authenticate
|
||||
def get_activities(auth_user_id):
|
||||
"""Get all activities for authenticated user"""
|
||||
try:
|
||||
params = request.args.copy()
|
||||
page = 1 if 'page' not in params.keys() else int(params.get('page'))
|
||||
date_from = params.get('from')
|
||||
date_to = params.get('to')
|
||||
order = params.get('order')
|
||||
per_page = int(params.get('per_page')) if params.get('per_page') else 5
|
||||
activities = Activity.query.filter(
|
||||
Activity.user_id == auth_user_id,
|
||||
Activity.activity_date >= datetime.strptime(date_from, '%Y-%m-%d')
|
||||
if date_from else True,
|
||||
Activity.activity_date <= datetime.strptime(date_to, '%Y-%m-%d')
|
||||
if date_to else True,
|
||||
).order_by(
|
||||
Activity.activity_date.asc()
|
||||
if order == 'asc'
|
||||
else Activity.activity_date.desc()
|
||||
).paginate(
|
||||
page, per_page, False
|
||||
).items
|
||||
response_object = {
|
||||
'status': 'success',
|
||||
'data': {
|
||||
'activities': [activity.serialize() for activity in activities]
|
||||
}
|
||||
}
|
||||
code = 200
|
||||
except Exception as e:
|
||||
appLog.error(e)
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Error. Please try again or contact the administrator.'
|
||||
}
|
||||
code = 500
|
||||
return jsonify(response_object), code
|
||||
|
||||
|
||||
@activities_blueprint.route('/activities/<int:activity_id>', methods=['GET'])
|
||||
@authenticate
|
||||
def get_activity(auth_user_id, activity_id):
|
||||
"""Get an activity"""
|
||||
activity = Activity.query.filter_by(id=activity_id).first()
|
||||
activities_list = []
|
||||
|
||||
if activity:
|
||||
activities_list.append(activity.serialize())
|
||||
status = 'success'
|
||||
code = 200
|
||||
else:
|
||||
status = 'not found'
|
||||
code = 404
|
||||
|
||||
response_object = {
|
||||
'status': status,
|
||||
'data': {
|
||||
'activities': activities_list
|
||||
}
|
||||
}
|
||||
return jsonify(response_object), code
|
||||
|
||||
|
||||
def get_activity_data(auth_user_id, activity_id, data_type):
|
||||
"""Get chart data from an activity gpx file"""
|
||||
activity = Activity.query.filter_by(id=activity_id).first()
|
||||
content = ''
|
||||
if activity:
|
||||
if not activity.gpx or activity.gpx == '':
|
||||
response_object = {
|
||||
'status': 'fail',
|
||||
'message': f'No gpx file for this activity (id: {activity_id})'
|
||||
}
|
||||
return jsonify(response_object), 400
|
||||
|
||||
try:
|
||||
if data_type == 'chart':
|
||||
content = get_chart_data(activity.gpx)
|
||||
else: # data_type == 'gpx'
|
||||
with open(activity.gpx, encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
except Exception as e:
|
||||
appLog.error(e)
|
||||
response_object = {'status': 'error',
|
||||
'message': 'internal error',
|
||||
'data': ({'chart_data': content}
|
||||
if data_type == 'chart'
|
||||
else {'gpx': content})}
|
||||
return jsonify(response_object), 500
|
||||
|
||||
status = 'success'
|
||||
message = ''
|
||||
code = 200
|
||||
else:
|
||||
status = 'not found'
|
||||
message = f'Activity not found (id: {activity_id})'
|
||||
code = 404
|
||||
|
||||
response_object = {'status': status,
|
||||
'message': message,
|
||||
'data': ({'chart_data': content}
|
||||
if data_type == 'chart'
|
||||
else {'gpx': content})}
|
||||
return jsonify(response_object), code
|
||||
|
||||
|
||||
@activities_blueprint.route(
|
||||
'/activities/<int:activity_id>/gpx', methods=['GET']
|
||||
)
|
||||
@authenticate
|
||||
def get_activity_gpx(auth_user_id, activity_id):
|
||||
"""Get gpx file for an activity"""
|
||||
return get_activity_data(auth_user_id, activity_id, 'gpx')
|
||||
|
||||
|
||||
@activities_blueprint.route(
|
||||
'/activities/<int:activity_id>/chart_data', methods=['GET']
|
||||
)
|
||||
@authenticate
|
||||
def get_activity_chart_data(auth_user_id, activity_id):
|
||||
"""Get chart data from an activity gpx file"""
|
||||
return get_activity_data(auth_user_id, activity_id, 'chart')
|
||||
|
||||
|
||||
@activities_blueprint.route('/activities/map/<map_id>', methods=['GET'])
|
||||
def get_map(map_id):
|
||||
try:
|
||||
activity = Activity.query.filter_by(map_id=map_id).first()
|
||||
if not activity:
|
||||
response_object = {
|
||||
'status': 'fail',
|
||||
'message': 'Map does not exist'
|
||||
}
|
||||
return jsonify(response_object), 404
|
||||
else:
|
||||
return send_file(activity.map)
|
||||
except Exception as e:
|
||||
appLog.error(e)
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'internal error.'
|
||||
}
|
||||
return jsonify(response_object), 500
|
||||
|
||||
|
||||
@activities_blueprint.route('/activities', methods=['POST'])
|
||||
@authenticate
|
||||
def post_activity(auth_user_id):
|
||||
"""Post an activity (with gpx file)"""
|
||||
response_object = verify_extension('activity', request)
|
||||
if response_object['status'] != 'success':
|
||||
return jsonify(response_object), 400
|
||||
|
||||
activity_data = json.loads(request.form["data"])
|
||||
if not activity_data or activity_data.get('sport_id') is None:
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Invalid payload.'
|
||||
}
|
||||
return jsonify(response_object), 400
|
||||
|
||||
activity_file = request.files['file']
|
||||
upload_dir = os.path.join(
|
||||
current_app.config['UPLOAD_FOLDER'],
|
||||
'activities',
|
||||
str(auth_user_id))
|
||||
folders = {
|
||||
'extract_dir': os.path.join(upload_dir, 'extract'),
|
||||
'tmp_dir': os.path.join(upload_dir, 'tmp'),
|
||||
}
|
||||
|
||||
try:
|
||||
new_activities = process_files(
|
||||
auth_user_id, activity_data, activity_file, folders
|
||||
)
|
||||
if len(new_activities) > 0:
|
||||
response_object = {
|
||||
'status': 'created',
|
||||
'data': {
|
||||
'activities': [new_activity.serialize()
|
||||
for new_activity in new_activities]
|
||||
}
|
||||
}
|
||||
code = 201
|
||||
else:
|
||||
response_object = {
|
||||
'status': 'fail',
|
||||
'data': {
|
||||
'activities': []
|
||||
}
|
||||
}
|
||||
code = 400
|
||||
except ActivityException as e:
|
||||
db.session.rollback()
|
||||
if e.e:
|
||||
appLog.error(e.e)
|
||||
response_object = {
|
||||
'status': e.status,
|
||||
'message': e.message,
|
||||
}
|
||||
code = 500 if e.status == 'error' else 400
|
||||
|
||||
shutil.rmtree(folders['extract_dir'], ignore_errors=True)
|
||||
shutil.rmtree(folders['tmp_dir'], ignore_errors=True)
|
||||
return jsonify(response_object), code
|
||||
|
||||
|
||||
@activities_blueprint.route('/activities/no_gpx', methods=['POST'])
|
||||
@authenticate
|
||||
def post_activity_no_gpx(auth_user_id):
|
||||
"""Post an activity without gpx file"""
|
||||
activity_data = request.get_json()
|
||||
if not activity_data or activity_data.get('sport_id') is None \
|
||||
or activity_data.get('duration') is None \
|
||||
or activity_data.get('distance') is None \
|
||||
or activity_data.get('activity_date') is None:
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Invalid payload.'
|
||||
}
|
||||
return jsonify(response_object), 400
|
||||
|
||||
try:
|
||||
new_activity = create_activity(auth_user_id, activity_data)
|
||||
db.session.add(new_activity)
|
||||
db.session.commit()
|
||||
|
||||
response_object = {
|
||||
'status': 'created',
|
||||
'data': {
|
||||
'activities': [new_activity.serialize()]
|
||||
}
|
||||
}
|
||||
return jsonify(response_object), 201
|
||||
|
||||
except (exc.IntegrityError, ValueError) as e:
|
||||
db.session.rollback()
|
||||
appLog.error(e)
|
||||
response_object = {
|
||||
'status': 'fail',
|
||||
'message': 'Error during activity save.'
|
||||
}
|
||||
return jsonify(response_object), 500
|
||||
|
||||
|
||||
@activities_blueprint.route('/activities/<int:activity_id>', methods=['PATCH'])
|
||||
@authenticate
|
||||
def update_activity(auth_user_id, activity_id):
|
||||
"""Update an activity"""
|
||||
activity_data = request.get_json()
|
||||
if not activity_data:
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Invalid payload.'
|
||||
}
|
||||
return jsonify(response_object), 400
|
||||
|
||||
try:
|
||||
activity = Activity.query.filter_by(id=activity_id).first()
|
||||
if activity:
|
||||
activity = edit_activity(activity, activity_data)
|
||||
db.session.commit()
|
||||
response_object = {
|
||||
'status': 'success',
|
||||
'data': {
|
||||
'activities': [activity.serialize()]
|
||||
}
|
||||
}
|
||||
code = 200
|
||||
else:
|
||||
response_object = {
|
||||
'status': 'not found',
|
||||
'data': {
|
||||
'activities': []
|
||||
}
|
||||
}
|
||||
code = 404
|
||||
except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
|
||||
db.session.rollback()
|
||||
appLog.error(e)
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Error. Please try again or contact the administrator.'
|
||||
}
|
||||
code = 500
|
||||
return jsonify(response_object), code
|
||||
|
||||
|
||||
@activities_blueprint.route(
|
||||
'/activities/<int:activity_id>', methods=['DELETE']
|
||||
)
|
||||
@authenticate
|
||||
def delete_activity(auth_user_id, activity_id):
|
||||
"""Delete an activity"""
|
||||
try:
|
||||
activity = Activity.query.filter_by(id=activity_id).first()
|
||||
if activity:
|
||||
db.session.delete(activity)
|
||||
db.session.commit()
|
||||
response_object = {
|
||||
'status': 'no content'
|
||||
}
|
||||
code = 204
|
||||
else:
|
||||
response_object = {
|
||||
'status': 'not found',
|
||||
'data': {
|
||||
'activities': []
|
||||
}
|
||||
}
|
||||
code = 404
|
||||
except (exc.IntegrityError, exc.OperationalError, ValueError, OSError) \
|
||||
as e:
|
||||
db.session.rollback()
|
||||
appLog.error(e)
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Error. Please try again or contact the administrator.'
|
||||
}
|
||||
code = 500
|
||||
return jsonify(response_object), code
|
398
fittrackee_api/fittrackee_api/activities/models.py
Normal file
398
fittrackee_api/fittrackee_api/activities/models.py
Normal file
@ -0,0 +1,398 @@
|
||||
import datetime
|
||||
import os
|
||||
|
||||
from fittrackee_api import db
|
||||
from sqlalchemy.dialects import postgresql
|
||||
from sqlalchemy.event import listens_for
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
from sqlalchemy.orm.session import object_session
|
||||
from sqlalchemy.types import Enum
|
||||
|
||||
record_types = [
|
||||
'AS', # 'Best Average Speed'
|
||||
'FD', # 'Farthest Distance'
|
||||
'LD', # 'Longest Duration'
|
||||
'MS', # 'Max speed'
|
||||
]
|
||||
|
||||
|
||||
def convert_timedelta_to_integer(value):
|
||||
hours, minutes, seconds = str(value).split(':')
|
||||
return int(hours) * 3600 + int(minutes) * 60 + int(seconds)
|
||||
|
||||
|
||||
def convert_value_to_integer(record_type, val):
|
||||
if val is None:
|
||||
return None
|
||||
|
||||
if record_type == 'LD':
|
||||
return convert_timedelta_to_integer(val)
|
||||
elif record_type in ['AS', 'MS']:
|
||||
return int(val * 100)
|
||||
else: # 'FD'
|
||||
return int(val * 1000)
|
||||
|
||||
|
||||
def update_records(user_id, sport_id, connection, session):
|
||||
record_table = Record.__table__
|
||||
new_records = Activity.get_user_activity_records(
|
||||
user_id,
|
||||
sport_id)
|
||||
for record_type, record_data in new_records.items():
|
||||
if record_data['record_value']:
|
||||
record = Record.query.filter_by(
|
||||
user_id=user_id,
|
||||
sport_id=sport_id,
|
||||
record_type=record_type,
|
||||
).first()
|
||||
if record:
|
||||
value = convert_value_to_integer(
|
||||
record_type, record_data['record_value']
|
||||
)
|
||||
connection.execute(record_table.update().where(
|
||||
record_table.c.id == record.id,
|
||||
).values(
|
||||
value=value,
|
||||
activity_id=record_data['activity'].id,
|
||||
activity_date=record_data['activity'].activity_date,
|
||||
))
|
||||
else:
|
||||
new_record = Record(
|
||||
activity=record_data['activity'],
|
||||
record_type=record_type
|
||||
)
|
||||
new_record.value = record_data['record_value']
|
||||
session.add(new_record)
|
||||
else:
|
||||
connection.execute(record_table.delete().where(
|
||||
record_table.c.user_id == user_id,
|
||||
).where(
|
||||
record_table.c.sport_id == sport_id,
|
||||
).where(
|
||||
record_table.c.record_type == record_type,
|
||||
))
|
||||
|
||||
|
||||
class Sport(db.Model):
|
||||
__tablename__ = "sports"
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
label = db.Column(db.String(50), unique=True, nullable=False)
|
||||
img = db.Column(db.String(255), unique=True, nullable=True)
|
||||
is_default = db.Column(db.Boolean, default=False, nullable=False)
|
||||
activities = db.relationship('Activity',
|
||||
lazy=True,
|
||||
backref=db.backref('sports', lazy='joined'))
|
||||
records = db.relationship('Record',
|
||||
lazy=True,
|
||||
backref=db.backref('sports', lazy='joined'))
|
||||
|
||||
def __repr__(self):
|
||||
return f'<Sport {self.label!r}>'
|
||||
|
||||
def __init__(self, label):
|
||||
self.label = label
|
||||
|
||||
def serialize(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'label': self.label,
|
||||
'img': self.img,
|
||||
'_can_be_deleted':
|
||||
len(self.activities) == 0 and not self.is_default
|
||||
}
|
||||
|
||||
|
||||
class Activity(db.Model):
|
||||
__tablename__ = "activities"
|
||||
id = db.Column(
|
||||
db.Integer,
|
||||
primary_key=True,
|
||||
autoincrement=True)
|
||||
user_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('users.id'),
|
||||
nullable=False)
|
||||
sport_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('sports.id'),
|
||||
nullable=False)
|
||||
title = db.Column(db.String(255), nullable=True)
|
||||
gpx = db.Column(db.String(255), nullable=True)
|
||||
creation_date = db.Column(
|
||||
db.DateTime, default=datetime.datetime.utcnow)
|
||||
modification_date = db.Column(
|
||||
db.DateTime, onupdate=datetime.datetime.utcnow)
|
||||
activity_date = db.Column(db.DateTime, nullable=False)
|
||||
duration = db.Column(db.Interval, nullable=False)
|
||||
pauses = db.Column(db.Interval, nullable=True)
|
||||
moving = db.Column(db.Interval, nullable=True)
|
||||
distance = db.Column(db.Numeric(6, 3), nullable=True) # kilometers
|
||||
min_alt = db.Column(db.Numeric(6, 2), nullable=True) # meters
|
||||
max_alt = db.Column(db.Numeric(6, 2), nullable=True) # meters
|
||||
descent = db.Column(db.Numeric(7, 2), nullable=True) # meters
|
||||
ascent = db.Column(db.Numeric(7, 2), nullable=True) # meters
|
||||
max_speed = db.Column(db.Numeric(6, 2), nullable=True) # km/h
|
||||
ave_speed = db.Column(db.Numeric(6, 2), nullable=True) # km/h
|
||||
bounds = db.Column(postgresql.ARRAY(db.Float), nullable=True)
|
||||
map = db.Column(db.String(255), nullable=True)
|
||||
map_id = db.Column(db.String(50), nullable=True)
|
||||
segments = db.relationship('ActivitySegment',
|
||||
lazy=True,
|
||||
cascade='all, delete',
|
||||
backref=db.backref(
|
||||
'activities',
|
||||
lazy='joined',
|
||||
single_parent=True))
|
||||
records = db.relationship('Record',
|
||||
lazy=True,
|
||||
cascade='all, delete',
|
||||
backref=db.backref(
|
||||
'activities',
|
||||
lazy='joined',
|
||||
single_parent=True))
|
||||
|
||||
def __str__(self):
|
||||
return f'<Activity \'{self.sports.label}\' - {self.activity_date}>'
|
||||
|
||||
def __init__(self, user_id, sport_id, activity_date, distance, duration):
|
||||
self.user_id = user_id
|
||||
self.sport_id = sport_id
|
||||
self.activity_date = activity_date
|
||||
self.distance = distance
|
||||
self.duration = duration
|
||||
|
||||
def serialize(self):
|
||||
previous_activity = Activity.query.filter(
|
||||
Activity.id != self.id,
|
||||
Activity.user_id == self.user_id,
|
||||
Activity.activity_date <= self.activity_date
|
||||
).order_by(
|
||||
Activity.activity_date.desc()
|
||||
).first()
|
||||
next_activity = Activity.query.filter(
|
||||
Activity.id != self.id,
|
||||
Activity.user_id == self.user_id,
|
||||
Activity.activity_date >= self.activity_date
|
||||
).order_by(
|
||||
Activity.activity_date.asc()
|
||||
).first()
|
||||
return {
|
||||
"id": self.id,
|
||||
"user_id": self.user_id,
|
||||
"sport_id": self.sport_id,
|
||||
"title": self.title,
|
||||
"creation_date": self.creation_date,
|
||||
"modification_date": self.modification_date,
|
||||
"activity_date": self.activity_date,
|
||||
"duration": str(self.duration) if self.duration else None,
|
||||
"pauses": str(self.pauses) if self.pauses else None,
|
||||
"moving": str(self.moving) if self.moving else None,
|
||||
"distance": float(self.distance) if self.distance else None,
|
||||
"min_alt": float(self.min_alt) if self.min_alt else None,
|
||||
"max_alt": float(self.max_alt) if self.max_alt else None,
|
||||
"descent": float(self.descent) if self.descent else None,
|
||||
"ascent": float(self.ascent) if self.ascent else None,
|
||||
"max_speed": float(self.max_speed) if self.max_speed else None,
|
||||
"ave_speed": float(self.ave_speed) if self.ave_speed else None,
|
||||
"with_gpx": self.gpx is not None,
|
||||
"bounds": [float(bound) for bound in self.bounds] if self.bounds else [], # noqa
|
||||
"previous_activity": previous_activity.id if previous_activity else None, # noqa
|
||||
"next_activity": next_activity.id if next_activity else None,
|
||||
"segments": [segment.serialize() for segment in self.segments],
|
||||
"records": [record.serialize() for record in self.records],
|
||||
"map": self.map_id if self.map else None
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_user_activity_records(cls, user_id, sport_id, as_integer=False):
|
||||
record_types_columns = {
|
||||
'AS': 'ave_speed', # 'Average speed'
|
||||
'FD': 'distance', # 'Farthest Distance'
|
||||
'LD': 'moving', # 'Longest Duration'
|
||||
'MS': 'max_speed', # 'Max speed'
|
||||
}
|
||||
records = {}
|
||||
for record_type, column in record_types_columns.items():
|
||||
column_sorted = getattr(getattr(Activity, column), 'desc')()
|
||||
record_activity = Activity.query.filter_by(
|
||||
user_id=user_id,
|
||||
sport_id=sport_id,
|
||||
).order_by(
|
||||
column_sorted,
|
||||
Activity.activity_date,
|
||||
).first()
|
||||
records[record_type] = dict(
|
||||
record_value=(getattr(record_activity, column)
|
||||
if record_activity else None),
|
||||
activity=record_activity)
|
||||
return records
|
||||
|
||||
|
||||
@listens_for(Activity, 'after_insert')
|
||||
def on_activity_insert(mapper, connection, activity):
|
||||
|
||||
@listens_for(db.Session, 'after_flush', once=True)
|
||||
def receive_after_flush(session, context):
|
||||
update_records(activity.user_id, activity.sport_id, connection, session) # noqa
|
||||
|
||||
|
||||
@listens_for(Activity, 'after_update')
|
||||
def on_activity_update(mapper, connection, activity):
|
||||
if object_session(activity).is_modified(activity, include_collections=True): # noqa
|
||||
@listens_for(db.Session, 'after_flush', once=True)
|
||||
def receive_after_flush(session, context):
|
||||
sports_list = [activity.sport_id]
|
||||
records = Record.query.filter_by(
|
||||
activity_id=activity.id,
|
||||
).all()
|
||||
for rec in records:
|
||||
if rec.sport_id not in sports_list:
|
||||
sports_list.append(rec.sport_id)
|
||||
for sport_id in sports_list:
|
||||
update_records(activity.user_id, sport_id, connection, session)
|
||||
|
||||
|
||||
@listens_for(Activity, 'after_delete')
|
||||
def on_activity_delete(mapper, connection, old_record):
|
||||
|
||||
@listens_for(db.Session, 'after_flush', once=True)
|
||||
def receive_after_flush(session, context):
|
||||
if old_record.map:
|
||||
os.remove(old_record.map)
|
||||
if old_record.gpx:
|
||||
os.remove(old_record.gpx)
|
||||
|
||||
|
||||
class ActivitySegment(db.Model):
|
||||
__tablename__ = "activity_segments"
|
||||
activity_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('activities.id'),
|
||||
primary_key=True)
|
||||
segment_id = db.Column(
|
||||
db.Integer,
|
||||
primary_key=True)
|
||||
duration = db.Column(db.Interval, nullable=False)
|
||||
pauses = db.Column(db.Interval, nullable=True)
|
||||
moving = db.Column(db.Interval, nullable=True)
|
||||
distance = db.Column(db.Numeric(6, 3), nullable=True) # kilometers
|
||||
min_alt = db.Column(db.Numeric(6, 2), nullable=True) # meters
|
||||
max_alt = db.Column(db.Numeric(6, 2), nullable=True) # meters
|
||||
descent = db.Column(db.Numeric(7, 2), nullable=True) # meters
|
||||
ascent = db.Column(db.Numeric(7, 2), nullable=True) # meters
|
||||
max_speed = db.Column(db.Numeric(6, 2), nullable=True) # km/h
|
||||
ave_speed = db.Column(db.Numeric(6, 2), nullable=True) # km/h
|
||||
|
||||
def __str__(self):
|
||||
return (f'<Segment \'{self.segment_id}\' '
|
||||
f'for activity \'{self.activity_id}\'>')
|
||||
|
||||
def __init__(self, segment_id, activity_id):
|
||||
self.segment_id = segment_id
|
||||
self.activity_id = activity_id
|
||||
|
||||
def serialize(self):
|
||||
return {
|
||||
"activity_id": self.activity_id,
|
||||
"segment_id": self.segment_id,
|
||||
"duration": str(self.duration) if self.duration else None,
|
||||
"pauses": str(self.pauses) if self.pauses else None,
|
||||
"moving": str(self.moving) if self.moving else None,
|
||||
"distance": float(self.distance) if self.distance else None,
|
||||
"min_alt": float(self.min_alt) if self.min_alt else None,
|
||||
"max_alt": float(self.max_alt) if self.max_alt else None,
|
||||
"descent": float(self.descent) if self.descent else None,
|
||||
"ascent": float(self.ascent) if self.ascent else None,
|
||||
"max_speed": float(self.max_speed) if self.max_speed else None,
|
||||
"ave_speed": float(self.ave_speed) if self.ave_speed else None
|
||||
}
|
||||
|
||||
|
||||
class Record(db.Model):
|
||||
__tablename__ = "records"
|
||||
__table_args__ = (db.UniqueConstraint(
|
||||
'user_id', 'sport_id', 'record_type', name='user_sports_records'),)
|
||||
id = db.Column(
|
||||
db.Integer,
|
||||
primary_key=True,
|
||||
autoincrement=True)
|
||||
user_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('users.id'),
|
||||
nullable=False)
|
||||
sport_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('sports.id'),
|
||||
nullable=False)
|
||||
activity_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('activities.id'),
|
||||
nullable=False)
|
||||
record_type = db.Column(Enum(*record_types, name="record_types"))
|
||||
activity_date = db.Column(db.DateTime, nullable=False)
|
||||
_value = db.Column("value", db.Integer, nullable=True)
|
||||
|
||||
def __str__(self):
|
||||
return (f'<Record {self.sports.label} - '
|
||||
f'{self.record_type} - '
|
||||
f"{self.activity_date.strftime('%Y-%m-%d')}>")
|
||||
|
||||
def __init__(self, activity, record_type):
|
||||
self.user_id = activity.user_id
|
||||
self.sport_id = activity.sport_id
|
||||
self.activity_id = activity.id
|
||||
self.record_type = record_type
|
||||
self.activity_date = activity.activity_date
|
||||
|
||||
@hybrid_property
|
||||
def value(self):
|
||||
if self._value is None:
|
||||
return None
|
||||
if self.record_type == 'LD':
|
||||
return datetime.timedelta(seconds=self._value)
|
||||
elif self.record_type in ['AS', 'MS']:
|
||||
return float(self._value / 100)
|
||||
else: # 'FD'
|
||||
return float(self._value / 1000)
|
||||
|
||||
@value.setter
|
||||
def value(self, val):
|
||||
self._value = convert_value_to_integer(self.record_type, val)
|
||||
|
||||
def serialize(self):
|
||||
if self.value is None:
|
||||
value = None
|
||||
elif self.record_type in ['AS', 'FD', 'MS']:
|
||||
value = float(self.value)
|
||||
else: # 'LD'
|
||||
value = str(self.value)
|
||||
|
||||
return {
|
||||
"id": self.id,
|
||||
"user_id": self.user_id,
|
||||
"sport_id": self.sport_id,
|
||||
"activity_id": self.activity_id,
|
||||
"record_type": self.record_type,
|
||||
"activity_date": self.activity_date,
|
||||
"value": value,
|
||||
}
|
||||
|
||||
|
||||
@listens_for(Record, 'after_delete')
|
||||
def on_record_delete(mapper, connection, old_record):
|
||||
|
||||
@listens_for(db.Session, 'after_flush', once=True)
|
||||
def receive_after_flush(session, context):
|
||||
activity = old_record.activities
|
||||
new_records = Activity.get_user_activity_records(
|
||||
activity.user_id,
|
||||
activity.sport_id)
|
||||
for record_type, record_data in new_records.items():
|
||||
if record_data['record_value'] \
|
||||
and record_type == old_record.record_type:
|
||||
new_record = Record(
|
||||
activity=record_data['activity'],
|
||||
record_type=record_type
|
||||
)
|
||||
new_record.value = record_data['record_value']
|
||||
session.add(new_record)
|
24
fittrackee_api/fittrackee_api/activities/records.py
Normal file
24
fittrackee_api/fittrackee_api/activities/records.py
Normal file
@ -0,0 +1,24 @@
|
||||
from flask import Blueprint, jsonify
|
||||
|
||||
from ..users.utils import authenticate
|
||||
from .models import Record
|
||||
|
||||
records_blueprint = Blueprint('records', __name__)
|
||||
|
||||
|
||||
@records_blueprint.route('/records', methods=['GET'])
|
||||
@authenticate
|
||||
def get_records(auth_user_id):
|
||||
"""Get all records for authenticated user"""
|
||||
records = Record.query.filter_by(user_id=auth_user_id)\
|
||||
.order_by(
|
||||
Record.sport_id.asc(),
|
||||
Record.record_type.asc(),
|
||||
).all()
|
||||
response_object = {
|
||||
'status': 'success',
|
||||
'data': {
|
||||
'records': [record.serialize() for record in records]
|
||||
}
|
||||
}
|
||||
return jsonify(response_object), 200
|
168
fittrackee_api/fittrackee_api/activities/sports.py
Normal file
168
fittrackee_api/fittrackee_api/activities/sports.py
Normal file
@ -0,0 +1,168 @@
|
||||
from fittrackee_api import appLog, db
|
||||
from flask import Blueprint, jsonify, request
|
||||
from sqlalchemy import exc
|
||||
|
||||
from ..users.utils import authenticate, authenticate_as_admin
|
||||
from .models import Sport
|
||||
|
||||
sports_blueprint = Blueprint('sports', __name__)
|
||||
|
||||
|
||||
@sports_blueprint.route('/sports', methods=['GET'])
|
||||
@authenticate
|
||||
def get_sports(auth_user_id):
|
||||
"""Get all sports"""
|
||||
sports = Sport.query.order_by(Sport.id).all()
|
||||
response_object = {
|
||||
'status': 'success',
|
||||
'data': {
|
||||
'sports': [sport.serialize() for sport in sports]
|
||||
}
|
||||
}
|
||||
return jsonify(response_object), 200
|
||||
|
||||
|
||||
@sports_blueprint.route('/sports/<int:sport_id>', methods=['GET'])
|
||||
@authenticate
|
||||
def get_sport(auth_user_id, sport_id):
|
||||
"""Get a sport"""
|
||||
sport = Sport.query.filter_by(id=sport_id).first()
|
||||
if sport:
|
||||
response_object = {
|
||||
'status': 'success',
|
||||
'data': {
|
||||
'sports': [sport.serialize()]
|
||||
}
|
||||
}
|
||||
code = 200
|
||||
else:
|
||||
response_object = {
|
||||
'status': 'not found',
|
||||
'data': {
|
||||
'sports': []
|
||||
}
|
||||
}
|
||||
code = 404
|
||||
return jsonify(response_object), code
|
||||
|
||||
|
||||
@sports_blueprint.route('/sports', methods=['POST'])
|
||||
@authenticate_as_admin
|
||||
def post_sport(auth_user_id):
|
||||
"""Post a sport"""
|
||||
sport_data = request.get_json()
|
||||
if not sport_data or sport_data.get('label') is None:
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Invalid payload.'
|
||||
}
|
||||
return jsonify(response_object), 400
|
||||
|
||||
try:
|
||||
new_sport = Sport(label=sport_data.get('label'))
|
||||
db.session.add(new_sport)
|
||||
db.session.commit()
|
||||
response_object = {
|
||||
'status': 'created',
|
||||
'data': {
|
||||
'sports': [new_sport.serialize()]
|
||||
}
|
||||
}
|
||||
code = 201
|
||||
except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
|
||||
db.session.rollback()
|
||||
appLog.error(e)
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Error. Please try again or contact the administrator.'
|
||||
}
|
||||
code = 500
|
||||
return jsonify(response_object), code
|
||||
|
||||
|
||||
@sports_blueprint.route('/sports/<int:sport_id>', methods=['PATCH'])
|
||||
@authenticate_as_admin
|
||||
def update_sport(auth_user_id, sport_id):
|
||||
"""Update a sport"""
|
||||
sport_data = request.get_json()
|
||||
if not sport_data or sport_data.get('label') is None:
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Invalid payload.'
|
||||
}
|
||||
return jsonify(response_object), 400
|
||||
|
||||
sports_list = []
|
||||
try:
|
||||
sport = Sport.query.filter_by(id=sport_id).first()
|
||||
if sport:
|
||||
sport.label = sport_data.get('label')
|
||||
db.session.commit()
|
||||
sports_list.append({
|
||||
'id': sport.id,
|
||||
'label': sport.label
|
||||
})
|
||||
response_object = {
|
||||
'status': 'success',
|
||||
'data': {
|
||||
'sports': sports_list
|
||||
}
|
||||
}
|
||||
code = 200
|
||||
else:
|
||||
response_object = {
|
||||
'status': 'not found',
|
||||
'data': {
|
||||
'sports': sports_list
|
||||
}
|
||||
}
|
||||
code = 404
|
||||
except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
|
||||
db.session.rollback()
|
||||
appLog.error(e)
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Error. Please try again or contact the administrator.'
|
||||
}
|
||||
code = 500
|
||||
return jsonify(response_object), code
|
||||
|
||||
|
||||
@sports_blueprint.route('/sports/<int:sport_id>', methods=['DELETE'])
|
||||
@authenticate_as_admin
|
||||
def delete_sport(auth_user_id, sport_id):
|
||||
"""Delete a sport"""
|
||||
try:
|
||||
sport = Sport.query.filter_by(id=sport_id).first()
|
||||
if sport:
|
||||
db.session.delete(sport)
|
||||
db.session.commit()
|
||||
response_object = {
|
||||
'status': 'no content'
|
||||
}
|
||||
code = 204
|
||||
else:
|
||||
response_object = {
|
||||
'status': 'not found',
|
||||
'data': {
|
||||
'sports': []
|
||||
}
|
||||
}
|
||||
code = 404
|
||||
except exc.IntegrityError as e:
|
||||
db.session.rollback()
|
||||
appLog.error(e)
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Error. Associated activities exist.'
|
||||
}
|
||||
code = 500
|
||||
except (exc.OperationalError, ValueError) as e:
|
||||
db.session.rollback()
|
||||
appLog.error(e)
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Error. Please try again or contact the administrator.'
|
||||
}
|
||||
code = 500
|
||||
return jsonify(response_object), code
|
131
fittrackee_api/fittrackee_api/activities/stats.py
Normal file
131
fittrackee_api/fittrackee_api/activities/stats.py
Normal file
@ -0,0 +1,131 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from fittrackee_api import appLog
|
||||
from flask import Blueprint, jsonify, request
|
||||
|
||||
from ..users.models import User
|
||||
from ..users.utils import authenticate
|
||||
from .models import Activity, Sport, convert_timedelta_to_integer
|
||||
|
||||
stats_blueprint = Blueprint('stats', __name__)
|
||||
|
||||
|
||||
def get_activities(user_id, type):
|
||||
try:
|
||||
user = User.query.filter_by(id=user_id).first()
|
||||
if not user:
|
||||
response_object = {
|
||||
'status': 'not found',
|
||||
'message': 'User does not exist.'
|
||||
}
|
||||
return jsonify(response_object), 404
|
||||
|
||||
params = request.args.copy()
|
||||
date_from = params.get('from')
|
||||
date_to = params.get('to')
|
||||
sport_id = params.get('sport_id')
|
||||
time = params.get('time')
|
||||
|
||||
if type == 'by_sport':
|
||||
sport_id = params.get('sport_id')
|
||||
if sport_id:
|
||||
sport = Sport.query.filter_by(id=sport_id).first()
|
||||
if not sport:
|
||||
print('not sport')
|
||||
response_object = {
|
||||
'status': 'not found',
|
||||
'message': 'Sport does not exist.'
|
||||
}
|
||||
return jsonify(response_object), 404
|
||||
|
||||
activities = Activity.query.filter(
|
||||
Activity.user_id == user_id,
|
||||
Activity.activity_date >= datetime.strptime(date_from, '%Y-%m-%d')
|
||||
if date_from else True,
|
||||
Activity.activity_date <= datetime.strptime(date_to, '%Y-%m-%d')
|
||||
if date_to else True,
|
||||
Activity.sport_id == sport_id if sport_id else True,
|
||||
).order_by(
|
||||
Activity.activity_date.asc()
|
||||
).all()
|
||||
|
||||
activities_list = {}
|
||||
for activity in activities:
|
||||
if type == 'by_sport':
|
||||
sport_id = activity.sport_id
|
||||
if sport_id not in activities_list:
|
||||
activities_list[sport_id] = {
|
||||
'nb_activities': 0,
|
||||
'total_distance': 0.,
|
||||
'total_duration': 0,
|
||||
}
|
||||
activities_list[sport_id]['nb_activities'] += 1
|
||||
activities_list[sport_id]['total_distance'] += \
|
||||
float(activity.distance)
|
||||
activities_list[sport_id]['total_duration'] += \
|
||||
convert_timedelta_to_integer(activity.duration)
|
||||
|
||||
else:
|
||||
if time == 'week':
|
||||
activity_date = activity.activity_date - timedelta(
|
||||
days=activity.activity_date.isoweekday()
|
||||
)
|
||||
time_period = datetime.strftime(activity_date, "%Y-%m-%d")
|
||||
elif time == 'weekm': # week start Monday
|
||||
activity_date = activity.activity_date - timedelta(
|
||||
days=activity.activity_date.weekday()
|
||||
)
|
||||
time_period = datetime.strftime(activity_date, "%Y-%m-%d")
|
||||
elif time == 'month':
|
||||
time_period = datetime.strftime(activity.activity_date, "%Y-%m") # noqa
|
||||
elif time == 'year' or not time:
|
||||
time_period = datetime.strftime(activity.activity_date, "%Y") # noqa
|
||||
else:
|
||||
response_object = {
|
||||
'status': 'fail',
|
||||
'message': 'Invalid time period.'
|
||||
}
|
||||
return jsonify(response_object), 400
|
||||
sport_id = activity.sport_id
|
||||
if time_period not in activities_list:
|
||||
activities_list[time_period] = {}
|
||||
if sport_id not in activities_list[time_period]:
|
||||
activities_list[time_period][sport_id] = {
|
||||
'nb_activities': 0,
|
||||
'total_distance': 0.,
|
||||
'total_duration': 0,
|
||||
}
|
||||
activities_list[time_period][sport_id]['nb_activities'] += 1
|
||||
activities_list[time_period][sport_id]['total_distance'] += \
|
||||
float(activity.distance)
|
||||
activities_list[time_period][sport_id]['total_duration'] += \
|
||||
convert_timedelta_to_integer(activity.duration)
|
||||
|
||||
response_object = {
|
||||
'status': 'success',
|
||||
'data': {
|
||||
'statistics': activities_list
|
||||
}
|
||||
}
|
||||
code = 200
|
||||
except Exception as e:
|
||||
appLog.error(e)
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Error. Please try again or contact the administrator.'
|
||||
}
|
||||
code = 500
|
||||
return jsonify(response_object), code
|
||||
|
||||
|
||||
@stats_blueprint.route('/stats/<int:user_id>/by_time', methods=['GET'])
|
||||
@authenticate
|
||||
def get_activities_by_time(auth_user_id, user_id):
|
||||
"""Get activities statistics for a user"""
|
||||
return get_activities(user_id, 'by_time')
|
||||
|
||||
|
||||
@stats_blueprint.route('/stats/<int:user_id>/by_sport', methods=['GET'])
|
||||
@authenticate
|
||||
def get_activities_by_sport(auth_user_id, user_id):
|
||||
return get_activities(user_id, 'by_sport')
|
362
fittrackee_api/fittrackee_api/activities/utils.py
Normal file
362
fittrackee_api/fittrackee_api/activities/utils.py
Normal file
@ -0,0 +1,362 @@
|
||||
import hashlib
|
||||
import os
|
||||
import tempfile
|
||||
import zipfile
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import gpxpy.gpx
|
||||
from fittrackee_api import db
|
||||
from flask import current_app
|
||||
from sqlalchemy import exc
|
||||
from staticmap import Line, StaticMap
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from .models import Activity, ActivitySegment, Sport
|
||||
|
||||
|
||||
class ActivityException(Exception):
|
||||
def __init__(self, status, message, e):
|
||||
self.status = status
|
||||
self.message = message
|
||||
self.e = e
|
||||
|
||||
|
||||
def update_activity_data(activity, gpx_data):
|
||||
"""activity could be a complete activity or an activity segment"""
|
||||
activity.pauses = gpx_data['stop_time']
|
||||
activity.moving = gpx_data['moving_time']
|
||||
activity.min_alt = gpx_data['elevation_min']
|
||||
activity.max_alt = gpx_data['elevation_max']
|
||||
activity.descent = gpx_data['downhill']
|
||||
activity.ascent = gpx_data['uphill']
|
||||
activity.max_speed = gpx_data['max_speed']
|
||||
activity.ave_speed = gpx_data['average_speed']
|
||||
return activity
|
||||
|
||||
|
||||
def create_activity(
|
||||
auth_user_id, activity_data, gpx_data=None
|
||||
):
|
||||
activity_date = gpx_data['start'] if gpx_data else datetime.strptime(
|
||||
activity_data.get('activity_date'), '%Y-%m-%d %H:%M')
|
||||
duration = gpx_data['duration'] if gpx_data \
|
||||
else timedelta(seconds=activity_data.get('duration'))
|
||||
distance = gpx_data['distance'] if gpx_data \
|
||||
else activity_data.get('distance')
|
||||
title = gpx_data['name'] if gpx_data \
|
||||
else activity_data.get('title')
|
||||
|
||||
new_activity = Activity(
|
||||
user_id=auth_user_id,
|
||||
sport_id=activity_data.get('sport_id'),
|
||||
activity_date=activity_date,
|
||||
distance=distance,
|
||||
duration=duration
|
||||
)
|
||||
|
||||
if title is not None and title != '':
|
||||
new_activity.title = title
|
||||
else:
|
||||
sport = Sport.query.filter_by(id=new_activity.sport_id).first()
|
||||
new_activity.title = f'{sport.label} - {new_activity.activity_date}'
|
||||
|
||||
if gpx_data:
|
||||
new_activity.gpx = gpx_data['filename']
|
||||
new_activity.bounds = gpx_data['bounds']
|
||||
update_activity_data(new_activity, gpx_data)
|
||||
else:
|
||||
new_activity.moving = duration
|
||||
new_activity.ave_speed = (None
|
||||
if duration.seconds == 0
|
||||
else float(new_activity.distance) /
|
||||
(duration.seconds / 3600))
|
||||
new_activity.max_speed = new_activity.ave_speed
|
||||
return new_activity
|
||||
|
||||
|
||||
def create_segment(activity_id, segment_data):
|
||||
new_segment = ActivitySegment(
|
||||
activity_id=activity_id,
|
||||
segment_id=segment_data['idx']
|
||||
)
|
||||
new_segment.duration = segment_data['duration']
|
||||
new_segment.distance = segment_data['distance']
|
||||
update_activity_data(new_segment, segment_data)
|
||||
return new_segment
|
||||
|
||||
|
||||
def edit_activity(activity, activity_data):
|
||||
if activity_data.get('sport_id'):
|
||||
activity.sport_id = activity_data.get('sport_id')
|
||||
if activity_data.get('title'):
|
||||
activity.title = activity_data.get('title')
|
||||
if not activity.gpx:
|
||||
if activity_data.get('activity_date'):
|
||||
activity.activity_date = datetime.strptime(
|
||||
activity_data.get('activity_date'), '%Y-%m-%d %H:%M')
|
||||
if activity_data.get('duration'):
|
||||
activity.duration = timedelta(
|
||||
seconds=activity_data.get('duration'))
|
||||
activity.moving = activity.duration
|
||||
if activity_data.get('distance'):
|
||||
activity.distance = activity_data.get('distance')
|
||||
activity.ave_speed = (None if activity.duration.seconds == 0
|
||||
else float(activity.distance) /
|
||||
(activity.duration.seconds / 3600))
|
||||
activity.max_speed = activity.ave_speed
|
||||
return activity
|
||||
|
||||
|
||||
def get_gpx_data(parsed_gpx, max_speed, start):
|
||||
gpx_data = {'max_speed': (max_speed / 1000) * 3600, 'start': start}
|
||||
|
||||
duration = parsed_gpx.get_duration()
|
||||
gpx_data['duration'] = timedelta(seconds=duration)
|
||||
|
||||
ele = parsed_gpx.get_elevation_extremes()
|
||||
gpx_data['elevation_max'] = ele.maximum
|
||||
gpx_data['elevation_min'] = ele.minimum
|
||||
|
||||
hill = parsed_gpx.get_uphill_downhill()
|
||||
gpx_data['uphill'] = hill.uphill
|
||||
gpx_data['downhill'] = hill.downhill
|
||||
|
||||
mv = parsed_gpx.get_moving_data()
|
||||
gpx_data['moving_time'] = timedelta(seconds=mv.moving_time)
|
||||
gpx_data['stop_time'] = timedelta(seconds=mv.stopped_time)
|
||||
distance = mv.moving_distance + mv.stopped_distance
|
||||
gpx_data['distance'] = distance / 1000
|
||||
|
||||
average_speed = distance / mv.moving_time if mv.moving_time > 0 else 0
|
||||
gpx_data['average_speed'] = (average_speed / 1000) * 3600
|
||||
|
||||
return gpx_data
|
||||
|
||||
|
||||
def open_gpx_file(gpx_file):
|
||||
gpx_file = open(gpx_file, 'r')
|
||||
gpx = gpxpy.parse(gpx_file)
|
||||
if len(gpx.tracks) == 0:
|
||||
return None
|
||||
return gpx
|
||||
|
||||
|
||||
def get_gpx_info(gpx_file):
|
||||
gpx = open_gpx_file(gpx_file)
|
||||
if gpx is None:
|
||||
return None
|
||||
|
||||
gpx_data = {
|
||||
'name': gpx.tracks[0].name,
|
||||
'segments': []
|
||||
}
|
||||
max_speed = 0
|
||||
start = 0
|
||||
map_data = []
|
||||
|
||||
for segment_idx, segment in enumerate(gpx.tracks[0].segments):
|
||||
segment_start = 0
|
||||
for point_idx, point in enumerate(segment.points):
|
||||
if point_idx == 0 and start == 0:
|
||||
start = point.time
|
||||
map_data.append([
|
||||
point.longitude, point.latitude
|
||||
])
|
||||
segment_max_speed = (segment.get_moving_data().max_speed
|
||||
if segment.get_moving_data().max_speed
|
||||
else 0)
|
||||
|
||||
if segment_max_speed > max_speed:
|
||||
max_speed = segment_max_speed
|
||||
|
||||
segment_data = get_gpx_data(
|
||||
segment, segment_max_speed, segment_start
|
||||
)
|
||||
segment_data['idx'] = segment_idx
|
||||
gpx_data['segments'].append(segment_data)
|
||||
|
||||
full_gpx_data = get_gpx_data(gpx, max_speed, start)
|
||||
gpx_data = {**gpx_data, **full_gpx_data}
|
||||
bounds = gpx.get_bounds()
|
||||
gpx_data['bounds'] = [
|
||||
bounds.min_latitude,
|
||||
bounds.min_longitude,
|
||||
bounds.max_latitude,
|
||||
bounds.max_longitude
|
||||
]
|
||||
|
||||
return gpx_data, map_data
|
||||
|
||||
|
||||
def get_chart_data(gpx_file):
|
||||
gpx = open_gpx_file(gpx_file)
|
||||
if gpx is None:
|
||||
return None
|
||||
|
||||
chart_data = []
|
||||
first_point = None
|
||||
previous_point = None
|
||||
previous_distance = 0
|
||||
|
||||
for segment_idx, segment in enumerate(gpx.tracks[0].segments):
|
||||
for point_idx, point in enumerate(segment.points):
|
||||
if segment_idx == 0 and point_idx == 0:
|
||||
first_point = point
|
||||
distance = (point.distance_3d(previous_point)
|
||||
if (point.elevation
|
||||
and previous_point
|
||||
and previous_point.elevation)
|
||||
else point.distance_2d(previous_point)
|
||||
)
|
||||
distance = 0 if distance is None else distance
|
||||
distance += previous_distance
|
||||
speed = (round((segment.get_speed(point_idx) / 1000)*3600, 2)
|
||||
if segment.get_speed(point_idx) is not None
|
||||
else 0)
|
||||
chart_data.append({
|
||||
'distance': (round(distance / 1000, 2)
|
||||
if distance is not None else 0),
|
||||
'duration': point.time_difference(first_point),
|
||||
'elevation': (round(point.elevation, 1)
|
||||
if point.elevation is not None else 0),
|
||||
'speed': speed,
|
||||
'time': point.time,
|
||||
})
|
||||
previous_point = point
|
||||
previous_distance = distance
|
||||
|
||||
return chart_data
|
||||
|
||||
|
||||
def get_file_path(auth_user_id, dir_path, filename):
|
||||
if not os.path.exists(dir_path):
|
||||
os.makedirs(dir_path)
|
||||
file_path = os.path.join(dir_path, filename)
|
||||
return file_path
|
||||
|
||||
|
||||
def get_new_file_path(
|
||||
auth_user_id, activity_date, sport, old_filename=None, extension=None
|
||||
):
|
||||
if not extension:
|
||||
extension = f".{old_filename.rsplit('.', 1)[1].lower()}"
|
||||
_, new_filename = tempfile.mkstemp(
|
||||
prefix=f'{activity_date}_{sport}_',
|
||||
suffix=extension
|
||||
)
|
||||
dir_path = os.path.join(
|
||||
current_app.config['UPLOAD_FOLDER'], 'activities', str(auth_user_id))
|
||||
if not os.path.exists(dir_path):
|
||||
os.makedirs(dir_path)
|
||||
file_path = os.path.join(dir_path,
|
||||
new_filename.replace('/tmp/', ''))
|
||||
return file_path
|
||||
|
||||
|
||||
def generate_map(map_filepath, map_data):
|
||||
m = StaticMap(400, 225, 10)
|
||||
line = Line(map_data, '#3388FF', 4)
|
||||
m.add_line(line)
|
||||
image = m.render()
|
||||
image.save(map_filepath)
|
||||
|
||||
|
||||
def get_map_hash(map_filepath):
|
||||
"""
|
||||
md5 hash is used as id instead of activity id, to retrieve map image
|
||||
(maps are sensitive data)
|
||||
"""
|
||||
md5 = hashlib.md5()
|
||||
with open(map_filepath, 'rb') as f:
|
||||
for chunk in iter(lambda: f.read(128 * md5.block_size), b''):
|
||||
md5.update(chunk)
|
||||
return md5.hexdigest()
|
||||
|
||||
|
||||
def process_one_gpx_file(params, filename):
|
||||
try:
|
||||
gpx_data, map_data = get_gpx_info(params['file_path'])
|
||||
new_filepath = get_new_file_path(
|
||||
auth_user_id=params['auth_user_id'],
|
||||
activity_date=gpx_data['start'],
|
||||
old_filename=filename,
|
||||
sport=params['sport_label']
|
||||
)
|
||||
os.rename(params['file_path'], new_filepath)
|
||||
gpx_data['filename'] = new_filepath
|
||||
|
||||
map_filepath = get_new_file_path(
|
||||
auth_user_id=params['auth_user_id'],
|
||||
activity_date=gpx_data['start'],
|
||||
extension='.png',
|
||||
sport=params['sport_label']
|
||||
)
|
||||
generate_map(map_filepath, map_data)
|
||||
except (gpxpy.gpx.GPXXMLSyntaxException, TypeError) as e:
|
||||
raise ActivityException('error', 'Error during gpx file parsing.', e)
|
||||
except Exception as e:
|
||||
raise ActivityException('error', 'Error during activity file save.', e)
|
||||
|
||||
try:
|
||||
new_activity = create_activity(
|
||||
params['auth_user_id'], params['activity_data'], gpx_data)
|
||||
new_activity.map = map_filepath
|
||||
new_activity.map_id = get_map_hash(map_filepath)
|
||||
db.session.add(new_activity)
|
||||
db.session.flush()
|
||||
|
||||
for segment_data in gpx_data['segments']:
|
||||
new_segment = create_segment(new_activity.id, segment_data)
|
||||
db.session.add(new_segment)
|
||||
db.session.commit()
|
||||
return new_activity
|
||||
except (exc.IntegrityError, ValueError) as e:
|
||||
raise ActivityException('fail', 'Error during activity save.', e)
|
||||
|
||||
|
||||
def process_zip_archive(common_params, extract_dir):
|
||||
with zipfile.ZipFile(common_params['file_path'], "r") as zip_ref:
|
||||
zip_ref.extractall(extract_dir)
|
||||
|
||||
new_activities = []
|
||||
|
||||
for gpx_file in os.listdir(extract_dir):
|
||||
if ('.' in gpx_file and gpx_file.rsplit('.', 1)[1].lower()
|
||||
in current_app.config.get('ACTIVITY_ALLOWED_EXTENSIONS')):
|
||||
file_path = os.path.join(extract_dir, gpx_file)
|
||||
params = common_params
|
||||
params['file_path'] = file_path
|
||||
new_activity = process_one_gpx_file(params, gpx_file)
|
||||
new_activities.append(new_activity)
|
||||
|
||||
return new_activities
|
||||
|
||||
|
||||
def process_files(auth_user_id, activity_data, activity_file, folders):
|
||||
filename = secure_filename(activity_file.filename)
|
||||
extension = f".{filename.rsplit('.', 1)[1].lower()}"
|
||||
file_path = get_file_path(auth_user_id, folders['tmp_dir'], filename)
|
||||
sport = Sport.query.filter_by(id=activity_data.get('sport_id')).first()
|
||||
if not sport:
|
||||
raise ActivityException(
|
||||
'error',
|
||||
f"Sport id: {activity_data.get('sport_id')} does not exist",
|
||||
None
|
||||
)
|
||||
|
||||
common_params = {
|
||||
'auth_user_id': auth_user_id,
|
||||
'activity_data': activity_data,
|
||||
'file_path': file_path,
|
||||
'sport_label': sport.label,
|
||||
}
|
||||
|
||||
try:
|
||||
activity_file.save(file_path)
|
||||
except Exception as e:
|
||||
raise ActivityException('error', 'Error during activity file save.', e)
|
||||
|
||||
if extension == ".gpx":
|
||||
return [process_one_gpx_file(common_params, filename)]
|
||||
else:
|
||||
return process_zip_archive(common_params, folders['extract_dir'])
|
41
fittrackee_api/fittrackee_api/config.py
Normal file
41
fittrackee_api/fittrackee_api/config.py
Normal file
@ -0,0 +1,41 @@
|
||||
import os
|
||||
|
||||
from flask import current_app
|
||||
|
||||
|
||||
class BaseConfig:
|
||||
"""Base configuration"""
|
||||
DEBUG = False
|
||||
TESTING = False
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
BCRYPT_LOG_ROUNDS = 13
|
||||
TOKEN_EXPIRATION_DAYS = 30
|
||||
TOKEN_EXPIRATION_SECONDS = 0
|
||||
UPLOAD_FOLDER = os.path.join(
|
||||
current_app.root_path, 'uploads'
|
||||
)
|
||||
PICTURE_ALLOWED_EXTENSIONS = {'jpg', 'png', 'gif'}
|
||||
ACTIVITY_ALLOWED_EXTENSIONS = {'gpx', 'zip'}
|
||||
|
||||
|
||||
class DevelopmentConfig(BaseConfig):
|
||||
"""Development configuration"""
|
||||
DEBUG = True
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
|
||||
SECRET_KEY = 'development key'
|
||||
USERNAME = 'admin'
|
||||
PASSWORD = 'default'
|
||||
BCRYPT_LOG_ROUNDS = 4
|
||||
|
||||
|
||||
class TestingConfig(BaseConfig):
|
||||
"""Testing configuration"""
|
||||
DEBUG = True
|
||||
TESTING = True
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_TEST_URL')
|
||||
SECRET_KEY = 'test key'
|
||||
USERNAME = 'admin'
|
||||
PASSWORD = 'default'
|
||||
BCRYPT_LOG_ROUNDS = 4
|
||||
TOKEN_EXPIRATION_DAYS = 0
|
||||
TOKEN_EXPIRATION_SECONDS = 3
|
0
fittrackee_api/fittrackee_api/tests/__init__.py
Normal file
0
fittrackee_api/fittrackee_api/tests/__init__.py
Normal file
453
fittrackee_api/fittrackee_api/tests/conftest.py
Normal file
453
fittrackee_api/fittrackee_api/tests/conftest.py
Normal file
@ -0,0 +1,453 @@
|
||||
import datetime
|
||||
import os
|
||||
|
||||
import pytest
|
||||
from fittrackee_api import create_app, db
|
||||
from fittrackee_api.activities.models import Activity, ActivitySegment, Sport
|
||||
from fittrackee_api.users.models import User
|
||||
|
||||
os.environ["FLASK_ENV"] = 'testing'
|
||||
os.environ["APP_SETTINGS"] = 'fittrackee_api.config.TestingConfig'
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
app = create_app()
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
yield app
|
||||
db.session.remove()
|
||||
db.drop_all()
|
||||
return app
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def user_1():
|
||||
user = User(username='test', email='test@test.com', password='12345678')
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
return user
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def user_1_admin():
|
||||
admin = User(
|
||||
username='admin',
|
||||
email='admin@example.com',
|
||||
password='12345678'
|
||||
)
|
||||
admin.admin = True
|
||||
db.session.add(admin)
|
||||
db.session.commit()
|
||||
return admin
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def user_1_full():
|
||||
user = User(username='test', email='test@test.com', password='12345678')
|
||||
user.first_name = 'John'
|
||||
user.last_name = 'Doe'
|
||||
user.bio = 'just a random guy'
|
||||
user.location = 'somewhere'
|
||||
user.birth_date = datetime.datetime.strptime('01/01/1980', '%d/%m/%Y')
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
return user
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def user_2():
|
||||
user = User(username='toto', email='toto@toto.com', password='87654321')
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
return user
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def sport_1_cycling():
|
||||
sport = Sport(label='Cycling')
|
||||
db.session.add(sport)
|
||||
db.session.commit()
|
||||
return sport
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def sport_2_running():
|
||||
sport = Sport(label='Running')
|
||||
db.session.add(sport)
|
||||
db.session.commit()
|
||||
return sport
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def activity_cycling_user_1():
|
||||
activity = Activity(
|
||||
user_id=1,
|
||||
sport_id=1,
|
||||
activity_date=datetime.datetime.strptime('01/01/2018', '%d/%m/%Y'),
|
||||
distance=10,
|
||||
duration=datetime.timedelta(seconds=1024)
|
||||
)
|
||||
activity.max_speed = 10
|
||||
activity.ave_speed = 10
|
||||
activity.moving = activity.duration
|
||||
db.session.add(activity)
|
||||
db.session.commit()
|
||||
return activity
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def activity_cycling_user_1_segment():
|
||||
activity_segment = ActivitySegment(
|
||||
activity_id=1,
|
||||
segment_id=0
|
||||
)
|
||||
activity_segment.duration = datetime.timedelta(seconds=6000)
|
||||
activity_segment.distance = 5
|
||||
db.session.add(activity_segment)
|
||||
db.session.commit()
|
||||
return activity_segment
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def activity_running_user_1():
|
||||
activity = Activity(
|
||||
user_id=1,
|
||||
sport_id=2,
|
||||
activity_date=datetime.datetime.strptime('01/04/2018', '%d/%m/%Y'),
|
||||
distance=12,
|
||||
duration=datetime.timedelta(seconds=6000)
|
||||
)
|
||||
db.session.add(activity)
|
||||
db.session.commit()
|
||||
return activity
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def seven_activities_user_1():
|
||||
activity = Activity(
|
||||
user_id=1,
|
||||
sport_id=1,
|
||||
activity_date=datetime.datetime.strptime('20/03/2017', '%d/%m/%Y'),
|
||||
distance=5,
|
||||
duration=datetime.timedelta(seconds=1024)
|
||||
)
|
||||
db.session.add(activity)
|
||||
db.session.flush()
|
||||
activity = Activity(
|
||||
user_id=1,
|
||||
sport_id=1,
|
||||
activity_date=datetime.datetime.strptime('01/06/2017', '%d/%m/%Y'),
|
||||
distance=10,
|
||||
duration=datetime.timedelta(seconds=3456)
|
||||
)
|
||||
db.session.add(activity)
|
||||
db.session.flush()
|
||||
activity = Activity(
|
||||
user_id=1,
|
||||
sport_id=1,
|
||||
activity_date=datetime.datetime.strptime('01/01/2018', '%d/%m/%Y'),
|
||||
distance=10,
|
||||
duration=datetime.timedelta(seconds=1024)
|
||||
)
|
||||
db.session.add(activity)
|
||||
db.session.flush()
|
||||
activity = Activity(
|
||||
user_id=1,
|
||||
sport_id=1,
|
||||
activity_date=datetime.datetime.strptime('23/02/2018', '%d/%m/%Y'),
|
||||
distance=1,
|
||||
duration=datetime.timedelta(seconds=600)
|
||||
)
|
||||
db.session.add(activity)
|
||||
db.session.flush()
|
||||
activity = Activity(
|
||||
user_id=1,
|
||||
sport_id=1,
|
||||
activity_date=datetime.datetime.strptime('23/02/2018', '%d/%m/%Y'),
|
||||
distance=10,
|
||||
duration=datetime.timedelta(seconds=1000)
|
||||
)
|
||||
db.session.add(activity)
|
||||
db.session.flush()
|
||||
activity = Activity(
|
||||
user_id=1,
|
||||
sport_id=1,
|
||||
activity_date=datetime.datetime.strptime('01/04/2018', '%d/%m/%Y'),
|
||||
distance=8,
|
||||
duration=datetime.timedelta(seconds=6000)
|
||||
)
|
||||
db.session.add(activity)
|
||||
db.session.flush()
|
||||
activity = Activity(
|
||||
user_id=1,
|
||||
sport_id=1,
|
||||
activity_date=datetime.datetime.strptime('09/05/2018', '%d/%m/%Y'),
|
||||
distance=10,
|
||||
duration=datetime.timedelta(seconds=3000)
|
||||
)
|
||||
db.session.add(activity)
|
||||
db.session.commit()
|
||||
return activity
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def activity_cycling_user_2():
|
||||
activity = Activity(
|
||||
user_id=2,
|
||||
sport_id=1,
|
||||
activity_date=datetime.datetime.strptime('23/01/2018', '%d/%m/%Y'),
|
||||
distance=15,
|
||||
duration=datetime.timedelta(seconds=3600)
|
||||
)
|
||||
db.session.add(activity)
|
||||
db.session.commit()
|
||||
return activity
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def gpx_file():
|
||||
return (
|
||||
'<?xml version=\'1.0\' encoding=\'UTF-8\'?>'
|
||||
'<gpx xmlns:gpxdata="http://www.cluetrust.com/XML/GPXDATA/1/0" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxext="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns="http://www.topografix.com/GPX/1/1">' # noqa
|
||||
' <metadata/>'
|
||||
' <trk>'
|
||||
' <name>just an activity</name>'
|
||||
' <trkseg>'
|
||||
' <trkpt lat="44.68095" lon="6.07367">'
|
||||
' <ele>998</ele>'
|
||||
' <time>2018-03-13T12:44:45Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.68091" lon="6.07367">'
|
||||
' <ele>998</ele>'
|
||||
' <time>2018-03-13T12:44:50Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.6808" lon="6.07364">'
|
||||
' <ele>994</ele>'
|
||||
' <time>2018-03-13T12:45:00Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.68075" lon="6.07364">'
|
||||
' <ele>994</ele>'
|
||||
' <time>2018-03-13T12:45:05Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.68071" lon="6.07364">'
|
||||
' <ele>994</ele>'
|
||||
' <time>2018-03-13T12:45:10Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.68049" lon="6.07361">'
|
||||
' <ele>993</ele>'
|
||||
' <time>2018-03-13T12:45:30Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.68019" lon="6.07356">'
|
||||
' <ele>992</ele>'
|
||||
' <time>2018-03-13T12:45:55Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.68014" lon="6.07355">'
|
||||
' <ele>992</ele>'
|
||||
' <time>2018-03-13T12:46:00Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67995" lon="6.07358">'
|
||||
' <ele>987</ele>'
|
||||
' <time>2018-03-13T12:46:15Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67977" lon="6.07364">'
|
||||
' <ele>987</ele>'
|
||||
' <time>2018-03-13T12:46:30Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67972" lon="6.07367">'
|
||||
' <ele>987</ele>'
|
||||
' <time>2018-03-13T12:46:35Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67966" lon="6.07368">'
|
||||
' <ele>987</ele>'
|
||||
' <time>2018-03-13T12:46:40Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67961" lon="6.0737">'
|
||||
' <ele>986</ele>'
|
||||
' <time>2018-03-13T12:46:45Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67938" lon="6.07377">'
|
||||
' <ele>986</ele>'
|
||||
' <time>2018-03-13T12:47:05Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67933" lon="6.07381">'
|
||||
' <ele>986</ele>'
|
||||
' <time>2018-03-13T12:47:10Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67922" lon="6.07385">'
|
||||
' <ele>985</ele>'
|
||||
' <time>2018-03-13T12:47:20Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67911" lon="6.0739">'
|
||||
' <ele>980</ele>'
|
||||
' <time>2018-03-13T12:47:30Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.679" lon="6.07399">'
|
||||
' <ele>980</ele>'
|
||||
' <time>2018-03-13T12:47:40Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67896" lon="6.07402">'
|
||||
' <ele>980</ele>'
|
||||
' <time>2018-03-13T12:47:45Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67884" lon="6.07408">'
|
||||
' <ele>979</ele>'
|
||||
' <time>2018-03-13T12:47:55Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67863" lon="6.07423">'
|
||||
' <ele>981</ele>'
|
||||
' <time>2018-03-13T12:48:15Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67858" lon="6.07425">'
|
||||
' <ele>980</ele>'
|
||||
' <time>2018-03-13T12:48:20Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67842" lon="6.07434">'
|
||||
' <ele>979</ele>'
|
||||
' <time>2018-03-13T12:48:35Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67837" lon="6.07435">'
|
||||
' <ele>979</ele>'
|
||||
' <time>2018-03-13T12:48:40Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67822" lon="6.07442">'
|
||||
' <ele>975</ele>'
|
||||
' <time>2018-03-13T12:48:55Z</time>'
|
||||
' </trkpt>'
|
||||
' </trkseg>'
|
||||
' </trk>'
|
||||
'</gpx>'
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def gpx_file_wo_name():
|
||||
return (
|
||||
'<?xml version=\'1.0\' encoding=\'UTF-8\'?>'
|
||||
'<gpx xmlns:gpxdata="http://www.cluetrust.com/XML/GPXDATA/1/0" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxext="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns="http://www.topografix.com/GPX/1/1">' # noqa
|
||||
' <metadata/>'
|
||||
' <trk>'
|
||||
' <trkseg>'
|
||||
' <trkpt lat="44.68095" lon="6.07367">'
|
||||
' <ele>998</ele>'
|
||||
' <time>2018-03-13T12:44:45Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.68091" lon="6.07367">'
|
||||
' <ele>998</ele>'
|
||||
' <time>2018-03-13T12:44:50Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.6808" lon="6.07364">'
|
||||
' <ele>994</ele>'
|
||||
' <time>2018-03-13T12:45:00Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.68075" lon="6.07364">'
|
||||
' <ele>994</ele>'
|
||||
' <time>2018-03-13T12:45:05Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.68071" lon="6.07364">'
|
||||
' <ele>994</ele>'
|
||||
' <time>2018-03-13T12:45:10Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.68049" lon="6.07361">'
|
||||
' <ele>993</ele>'
|
||||
' <time>2018-03-13T12:45:30Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.68019" lon="6.07356">'
|
||||
' <ele>992</ele>'
|
||||
' <time>2018-03-13T12:45:55Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.68014" lon="6.07355">'
|
||||
' <ele>992</ele>'
|
||||
' <time>2018-03-13T12:46:00Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67995" lon="6.07358">'
|
||||
' <ele>987</ele>'
|
||||
' <time>2018-03-13T12:46:15Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67977" lon="6.07364">'
|
||||
' <ele>987</ele>'
|
||||
' <time>2018-03-13T12:46:30Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67972" lon="6.07367">'
|
||||
' <ele>987</ele>'
|
||||
' <time>2018-03-13T12:46:35Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67966" lon="6.07368">'
|
||||
' <ele>987</ele>'
|
||||
' <time>2018-03-13T12:46:40Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67961" lon="6.0737">'
|
||||
' <ele>986</ele>'
|
||||
' <time>2018-03-13T12:46:45Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67938" lon="6.07377">'
|
||||
' <ele>986</ele>'
|
||||
' <time>2018-03-13T12:47:05Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67933" lon="6.07381">'
|
||||
' <ele>986</ele>'
|
||||
' <time>2018-03-13T12:47:10Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67922" lon="6.07385">'
|
||||
' <ele>985</ele>'
|
||||
' <time>2018-03-13T12:47:20Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67911" lon="6.0739">'
|
||||
' <ele>980</ele>'
|
||||
' <time>2018-03-13T12:47:30Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.679" lon="6.07399">'
|
||||
' <ele>980</ele>'
|
||||
' <time>2018-03-13T12:47:40Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67896" lon="6.07402">'
|
||||
' <ele>980</ele>'
|
||||
' <time>2018-03-13T12:47:45Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67884" lon="6.07408">'
|
||||
' <ele>979</ele>'
|
||||
' <time>2018-03-13T12:47:55Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67863" lon="6.07423">'
|
||||
' <ele>981</ele>'
|
||||
' <time>2018-03-13T12:48:15Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67858" lon="6.07425">'
|
||||
' <ele>980</ele>'
|
||||
' <time>2018-03-13T12:48:20Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67842" lon="6.07434">'
|
||||
' <ele>979</ele>'
|
||||
' <time>2018-03-13T12:48:35Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67837" lon="6.07435">'
|
||||
' <ele>979</ele>'
|
||||
' <time>2018-03-13T12:48:40Z</time>'
|
||||
' </trkpt>'
|
||||
' <trkpt lat="44.67822" lon="6.07442">'
|
||||
' <ele>975</ele>'
|
||||
' <time>2018-03-13T12:48:55Z</time>'
|
||||
' </trkpt>'
|
||||
' </trkseg>'
|
||||
' </trk>'
|
||||
'</gpx>'
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def gpx_file_wo_track():
|
||||
return (
|
||||
'<?xml version=\'1.0\' encoding=\'UTF-8\'?>'
|
||||
'<gpx xmlns:gpxdata="http://www.cluetrust.com/XML/GPXDATA/1/0" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxext="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns="http://www.topografix.com/GPX/1/1">' # noqa
|
||||
' <metadata/>'
|
||||
'</gpx>'
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def gpx_file_invalid_xml():
|
||||
return (
|
||||
'<?xml version=\'1.0\' encoding=\'UTF-8\'?>'
|
||||
'<gpx xmlns:gpxdata="http://www.cluetrust.com/XML/GPXDATA/1/0" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxext="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns="http://www.topografix.com/GPX/1/1">' # noqa
|
||||
' <metadata/>'
|
||||
)
|
BIN
fittrackee_api/fittrackee_api/tests/files/gpx_test.zip
Normal file
BIN
fittrackee_api/fittrackee_api/tests/files/gpx_test.zip
Normal file
Binary file not shown.
BIN
fittrackee_api/fittrackee_api/tests/files/gpx_test_folder.zip
Normal file
BIN
fittrackee_api/fittrackee_api/tests/files/gpx_test_folder.zip
Normal file
Binary file not shown.
BIN
fittrackee_api/fittrackee_api/tests/files/gpx_test_incorrect.zip
Normal file
BIN
fittrackee_api/fittrackee_api/tests/files/gpx_test_incorrect.zip
Normal file
Binary file not shown.
660
fittrackee_api/fittrackee_api/tests/test_activities_api_0_get.py
Normal file
660
fittrackee_api/fittrackee_api/tests/test_activities_api_0_get.py
Normal file
@ -0,0 +1,660 @@
|
||||
import json
|
||||
|
||||
|
||||
def test_get_all_activities_for_authenticated_user(
|
||||
app, user_1, user_2, sport_1_cycling, sport_2_running,
|
||||
activity_cycling_user_1, activity_cycling_user_2, activity_running_user_1,
|
||||
):
|
||||
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
'/api/activities',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['activities']) == 2
|
||||
|
||||
assert 'creation_date' in data['data']['activities'][0]
|
||||
assert 'Sun, 01 Apr 2018 00:00:00 GMT' == data['data']['activities'][0]['activity_date'] # noqa
|
||||
assert 1 == data['data']['activities'][0]['user_id']
|
||||
assert 2 == data['data']['activities'][0]['sport_id']
|
||||
assert 12.0 == data['data']['activities'][0]['distance']
|
||||
assert '1:40:00' == data['data']['activities'][0]['duration']
|
||||
|
||||
assert 'creation_date' in data['data']['activities'][1]
|
||||
assert 'Mon, 01 Jan 2018 00:00:00 GMT' == data['data']['activities'][1]['activity_date'] # noqa
|
||||
assert 1 == data['data']['activities'][1]['user_id']
|
||||
assert 1 == data['data']['activities'][1]['sport_id']
|
||||
assert 10.0 == data['data']['activities'][1]['distance']
|
||||
assert '0:17:04' == data['data']['activities'][1]['duration']
|
||||
|
||||
|
||||
def test_get_activities_for_authenticated_user_no_activity(
|
||||
app, user_1, user_2, sport_1_cycling, sport_2_running,
|
||||
activity_cycling_user_1, activity_running_user_1,
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='toto@toto.com',
|
||||
password='87654321'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
'/api/activities',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['activities']) == 0
|
||||
|
||||
|
||||
def test_get_activities_for_authenticated_user_no_authentication(app):
|
||||
client = app.test_client()
|
||||
response = client.get('/api/activities')
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 401
|
||||
assert 'error' in data['status']
|
||||
assert 'Provide a valid auth token.' in data['message']
|
||||
|
||||
|
||||
def test_get_activities_pagination(
|
||||
app, user_1, sport_1_cycling, seven_activities_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
'/api/activities',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['activities']) == 5
|
||||
assert 'creation_date' in data['data']['activities'][0]
|
||||
assert 'Wed, 09 May 2018 00:00:00 GMT' == data['data']['activities'][0]['activity_date'] # noqa
|
||||
assert '0:50:00' == data['data']['activities'][0]['duration']
|
||||
assert 'creation_date' in data['data']['activities'][4]
|
||||
assert 'Mon, 01 Jan 2018 00:00:00 GMT' == data['data']['activities'][4]['activity_date'] # noqa
|
||||
assert '0:17:04' == data['data']['activities'][4]['duration']
|
||||
|
||||
response = client.get(
|
||||
'/api/activities?page=1',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['activities']) == 5
|
||||
assert 'creation_date' in data['data']['activities'][0]
|
||||
assert 'Wed, 09 May 2018 00:00:00 GMT' == data['data']['activities'][0]['activity_date'] # noqa
|
||||
assert '0:50:00' == data['data']['activities'][0]['duration']
|
||||
assert 'creation_date' in data['data']['activities'][4]
|
||||
assert 'Mon, 01 Jan 2018 00:00:00 GMT' == data['data']['activities'][4]['activity_date'] # noqa
|
||||
assert '0:17:04' == data['data']['activities'][4]['duration']
|
||||
|
||||
response = client.get(
|
||||
'/api/activities?page=2',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['activities']) == 2
|
||||
assert 'creation_date' in data['data']['activities'][0]
|
||||
assert 'Thu, 01 Jun 2017 00:00:00 GMT' == data['data']['activities'][0]['activity_date'] # noqa
|
||||
assert '0:57:36' == data['data']['activities'][0]['duration']
|
||||
assert 'creation_date' in data['data']['activities'][1]
|
||||
assert 'Mon, 20 Mar 2017 00:00:00 GMT' == data['data']['activities'][1]['activity_date'] # noqa
|
||||
assert '0:17:04' == data['data']['activities'][1]['duration']
|
||||
|
||||
response = client.get(
|
||||
'/api/activities?page=3',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['activities']) == 0
|
||||
|
||||
|
||||
def test_get_activities_pagination_error(
|
||||
app, user_1, sport_1_cycling, seven_activities_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
'/api/activities?page=A',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 500
|
||||
assert 'error' in data['status']
|
||||
assert 'Error. Please try again or contact the administrator.' in data['message'] # noqa
|
||||
|
||||
|
||||
def test_get_activities_date_filter(
|
||||
app, user_1, sport_1_cycling, seven_activities_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
'/api/activities?from=2018-02-01&to=2018-02-28',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['activities']) == 2
|
||||
assert 'creation_date' in data['data']['activities'][0]
|
||||
assert 'Fri, 23 Feb 2018 00:00:00 GMT' == data['data']['activities'][0]['activity_date'] # noqa
|
||||
assert '0:10:00' == data['data']['activities'][0]['duration']
|
||||
assert 'creation_date' in data['data']['activities'][1]
|
||||
assert 'Fri, 23 Feb 2018 00:00:00 GMT' == data['data']['activities'][1]['activity_date'] # noqa
|
||||
assert '0:16:40' == data['data']['activities'][1]['duration']
|
||||
|
||||
|
||||
def test_get_activities_date_filter_no_results(
|
||||
app, user_1, sport_1_cycling, seven_activities_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
'/api/activities?from=2018-03-01&to=2018-03-30',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['activities']) == 0
|
||||
|
||||
|
||||
def test_get_activities_date_filter_from(
|
||||
app, user_1, sport_1_cycling, seven_activities_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
'/api/activities?from=2018-04-01',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['activities']) == 2
|
||||
assert 'creation_date' in data['data']['activities'][0]
|
||||
assert 'Wed, 09 May 2018 00:00:00 GMT' == data['data']['activities'][0]['activity_date'] # noqa
|
||||
assert 'Sun, 01 Apr 2018 00:00:00 GMT' == data['data']['activities'][1]['activity_date'] # noqa
|
||||
|
||||
|
||||
def test_get_activities_date_filter_to(
|
||||
app, user_1, sport_1_cycling, seven_activities_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
'/api/activities?to=2017-12-31',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['activities']) == 2
|
||||
assert 'Thu, 01 Jun 2017 00:00:00 GMT' == data['data']['activities'][0]['activity_date'] # noqa
|
||||
assert 'Mon, 20 Mar 2017 00:00:00 GMT' == data['data']['activities'][1]['activity_date'] # noqa
|
||||
|
||||
|
||||
def test_get_activities_date_filter_paginate(
|
||||
app, user_1, sport_1_cycling, seven_activities_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
'/api/activities?from=2017-01-01&page=2',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['activities']) == 2
|
||||
assert 'Thu, 01 Jun 2017 00:00:00 GMT' == data['data']['activities'][0]['activity_date'] # noqa
|
||||
assert 'Mon, 20 Mar 2017 00:00:00 GMT' == data['data']['activities'][1]['activity_date'] # noqa
|
||||
|
||||
|
||||
def test_get_activities_order(
|
||||
app, user_1, sport_1_cycling, seven_activities_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
'/api/activities?order=asc',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['activities']) == 5
|
||||
assert 'Mon, 20 Mar 2017 00:00:00 GMT' == data['data']['activities'][0]['activity_date'] # noqa
|
||||
assert 'Fri, 23 Feb 2018 00:00:00 GMT' == data['data']['activities'][4]['activity_date'] # noqa
|
||||
|
||||
|
||||
def test_get_activities_date_filter_paginate_order(
|
||||
app, user_1, sport_1_cycling, seven_activities_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
'/api/activities?from=2017-01-01&page=2&order=asc',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['activities']) == 2
|
||||
assert 'Sun, 01 Apr 2018 00:00:00 GMT' == data['data']['activities'][0]['activity_date'] # noqa
|
||||
assert 'Wed, 09 May 2018 00:00:00 GMT' == data['data']['activities'][1]['activity_date'] # noqa
|
||||
|
||||
|
||||
def test_get_an_activity(
|
||||
app, user_1, sport_1_cycling, activity_cycling_user_1
|
||||
):
|
||||
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
'/api/activities/1',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['activities']) == 1
|
||||
|
||||
assert 'creation_date' in data['data']['activities'][0]
|
||||
assert 'Mon, 01 Jan 2018 00:00:00 GMT' == data['data']['activities'][0]['activity_date'] # noqa
|
||||
assert 1 == data['data']['activities'][0]['user_id']
|
||||
assert 1 == data['data']['activities'][0]['sport_id']
|
||||
assert 10.0 == data['data']['activities'][0]['distance']
|
||||
assert '0:17:04' == data['data']['activities'][0]['duration']
|
||||
|
||||
|
||||
def test_get_activities_per_page(
|
||||
app, user_1, sport_1_cycling, seven_activities_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
'/api/activities?per_page=10',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['activities']) == 7
|
||||
assert 'Wed, 09 May 2018 00:00:00 GMT' == data['data']['activities'][0]['activity_date'] # noqa
|
||||
assert 'Mon, 20 Mar 2017 00:00:00 GMT' == data['data']['activities'][6]['activity_date'] # noqa
|
||||
|
||||
response = client.get(
|
||||
'/api/activities?per_page=3',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['activities']) == 3
|
||||
assert 'Wed, 09 May 2018 00:00:00 GMT' == data['data']['activities'][0]['activity_date'] # noqa
|
||||
assert 'Fri, 23 Feb 2018 00:00:00 GMT' == data['data']['activities'][2]['activity_date'] # noqa
|
||||
|
||||
|
||||
def test_get_an_activity_invalid_id(app, user_1):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
'/api/activities/11',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 404
|
||||
assert 'not found' in data['status']
|
||||
assert len(data['data']['activities']) == 0
|
||||
|
||||
|
||||
def test_get_an_activity_no_actvity_no_gpx(app, user_1):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
'/api/activities/11/gpx',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 404
|
||||
assert 'not found' in data['status']
|
||||
assert 'Activity not found (id: 11)' in data['message']
|
||||
assert data['data']['gpx'] == ''
|
||||
|
||||
response = client.get(
|
||||
'/api/activities/11/chart_data',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 404
|
||||
assert 'not found' in data['status']
|
||||
assert 'Activity not found (id: 11)' in data['message']
|
||||
assert data['data']['chart_data'] == ''
|
||||
|
||||
|
||||
def test_get_an_activity_activity_no_gpx(
|
||||
app, user_1, sport_1_cycling, activity_cycling_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
'/api/activities/1/gpx',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 400
|
||||
assert 'fail' in data['status']
|
||||
assert 'No gpx file for this activity (id: 1)' in data['message']
|
||||
|
||||
response = client.get(
|
||||
'/api/activities/1/chart_data',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 400
|
||||
assert 'fail' in data['status']
|
||||
assert 'No gpx file for this activity (id: 1)' in data['message']
|
||||
|
||||
|
||||
def test_get_an_activity_activity_invalid_gpx(
|
||||
app, user_1, sport_1_cycling, activity_cycling_user_1
|
||||
):
|
||||
activity_cycling_user_1.gpx = "some path"
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
'/api/activities/1/gpx',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 500
|
||||
assert 'error' in data['status']
|
||||
assert 'internal error' in data['message']
|
||||
assert data['data']['gpx'] == ''
|
||||
|
||||
response = client.get(
|
||||
'/api/activities/1/chart_data',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 500
|
||||
assert 'error' in data['status']
|
||||
assert 'internal error' in data['message']
|
||||
assert data['data']['chart_data'] == ''
|
||||
|
||||
|
||||
def test_get_map_no_activity(
|
||||
app, user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
'/api/activities/map/123',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 404
|
||||
assert 'fail' in data['status']
|
||||
assert 'Map does not exist' in data['message']
|
@ -0,0 +1,792 @@
|
||||
import json
|
||||
import os
|
||||
from io import BytesIO
|
||||
|
||||
from fittrackee_api.activities.models import Activity
|
||||
|
||||
|
||||
def assert_activity_data_with_gpx(data):
|
||||
assert 'creation_date' in data['data']['activities'][0]
|
||||
assert 'Tue, 13 Mar 2018 12:44:45 GMT' == data['data']['activities'][0]['activity_date'] # noqa
|
||||
assert 1 == data['data']['activities'][0]['user_id']
|
||||
assert 1 == data['data']['activities'][0]['sport_id']
|
||||
assert '0:04:10' == data['data']['activities'][0]['duration']
|
||||
assert data['data']['activities'][0]['ascent'] == 0.4
|
||||
assert data['data']['activities'][0]['ave_speed'] == 4.6
|
||||
assert data['data']['activities'][0]['descent'] == 23.4
|
||||
assert data['data']['activities'][0]['distance'] == 0.32
|
||||
assert data['data']['activities'][0]['max_alt'] == 998.0
|
||||
assert data['data']['activities'][0]['max_speed'] == 5.11
|
||||
assert data['data']['activities'][0]['min_alt'] == 975.0
|
||||
assert data['data']['activities'][0]['moving'] == '0:04:10'
|
||||
assert data['data']['activities'][0]['pauses'] is None
|
||||
assert data['data']['activities'][0]['with_gpx'] is True
|
||||
assert data['data']['activities'][0]['map'] is not None
|
||||
assert len(data['data']['activities'][0]['segments']) == 1
|
||||
|
||||
segment = data['data']['activities'][0]['segments'][0]
|
||||
assert segment['activity_id'] == 1
|
||||
assert segment['segment_id'] == 0
|
||||
assert segment['duration'] == '0:04:10'
|
||||
assert segment['ascent'] == 0.4
|
||||
assert segment['ave_speed'] == 4.6
|
||||
assert segment['descent'] == 23.4
|
||||
assert segment['distance'] == 0.32
|
||||
assert segment['max_alt'] == 998.0
|
||||
assert segment['max_speed'] == 5.11
|
||||
assert segment['min_alt'] == 975.0
|
||||
assert segment['moving'] == '0:04:10'
|
||||
assert segment['pauses'] is None
|
||||
|
||||
records = data['data']['activities'][0]['records']
|
||||
assert len(records) == 4
|
||||
assert records[0]['sport_id'] == 1
|
||||
assert records[0]['activity_id'] == 1
|
||||
assert records[0]['record_type'] == 'MS'
|
||||
assert records[0]['activity_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||
assert records[0]['value'] == 5.11
|
||||
assert records[1]['sport_id'] == 1
|
||||
assert records[1]['activity_id'] == 1
|
||||
assert records[1]['record_type'] == 'LD'
|
||||
assert records[1]['activity_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||
assert records[1]['value'] == '0:04:10'
|
||||
assert records[2]['sport_id'] == 1
|
||||
assert records[2]['activity_id'] == 1
|
||||
assert records[2]['record_type'] == 'FD'
|
||||
assert records[2]['activity_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||
assert records[2]['value'] == 0.32
|
||||
assert records[3]['sport_id'] == 1
|
||||
assert records[3]['activity_id'] == 1
|
||||
assert records[3]['record_type'] == 'AS'
|
||||
assert records[3]['activity_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||
assert records[3]['value'] == 4.6
|
||||
|
||||
|
||||
def assert_activity_data_wo_gpx(data):
|
||||
assert 'creation_date' in data['data']['activities'][0]
|
||||
assert data['data']['activities'][0]['activity_date'] == 'Tue, 15 May 2018 14:05:00 GMT' # noqa
|
||||
assert data['data']['activities'][0]['user_id'] == 1
|
||||
assert data['data']['activities'][0]['sport_id'] == 1
|
||||
assert data['data']['activities'][0]['duration'] == '1:00:00'
|
||||
assert data['data']['activities'][0]['title'] == 'Cycling - 2018-05-15 14:05:00' # noqa
|
||||
assert data['data']['activities'][0]['ascent'] is None
|
||||
assert data['data']['activities'][0]['ave_speed'] == 10.0
|
||||
assert data['data']['activities'][0]['descent'] is None
|
||||
assert data['data']['activities'][0]['distance'] == 10.0
|
||||
assert data['data']['activities'][0]['max_alt'] is None
|
||||
assert data['data']['activities'][0]['max_speed'] == 10.0
|
||||
assert data['data']['activities'][0]['min_alt'] is None
|
||||
assert data['data']['activities'][0]['moving'] == '1:00:00'
|
||||
assert data['data']['activities'][0]['pauses'] is None
|
||||
assert data['data']['activities'][0]['with_gpx'] is False
|
||||
assert data['data']['activities'][0]['map'] is None
|
||||
|
||||
assert len(data['data']['activities'][0]['segments']) == 0
|
||||
|
||||
records = data['data']['activities'][0]['records']
|
||||
assert len(records) == 4
|
||||
assert records[0]['sport_id'] == 1
|
||||
assert records[0]['activity_id'] == 1
|
||||
assert records[0]['record_type'] == 'MS'
|
||||
assert records[0]['activity_date'] == 'Tue, 15 May 2018 14:05:00 GMT'
|
||||
assert records[0]['value'] == 10.0
|
||||
assert records[1]['sport_id'] == 1
|
||||
assert records[1]['activity_id'] == 1
|
||||
assert records[1]['record_type'] == 'LD'
|
||||
assert records[1]['activity_date'] == 'Tue, 15 May 2018 14:05:00 GMT'
|
||||
assert records[1]['value'] == '1:00:00'
|
||||
assert records[2]['sport_id'] == 1
|
||||
assert records[2]['activity_id'] == 1
|
||||
assert records[2]['record_type'] == 'FD'
|
||||
assert records[2]['activity_date'] == 'Tue, 15 May 2018 14:05:00 GMT'
|
||||
assert records[2]['value'] == 10.0
|
||||
assert records[3]['sport_id'] == 1
|
||||
assert records[3]['activity_id'] == 1
|
||||
assert records[3]['record_type'] == 'AS'
|
||||
assert records[3]['activity_date'] == 'Tue, 15 May 2018 14:05:00 GMT'
|
||||
assert records[3]['value'] == 10.0
|
||||
|
||||
|
||||
def test_add_an_activity_gpx(app, user_1, sport_1_cycling, gpx_file):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/activities',
|
||||
data=dict(
|
||||
file=(BytesIO(str.encode(gpx_file)), 'example.gpx'),
|
||||
data='{"sport_id": 1}'
|
||||
),
|
||||
headers=dict(
|
||||
content_type='multipart/form-data',
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 201
|
||||
assert 'created' in data['status']
|
||||
assert len(data['data']['activities']) == 1
|
||||
assert 'just an activity' == data['data']['activities'][0]['title']
|
||||
assert_activity_data_with_gpx(data)
|
||||
|
||||
|
||||
def test_get_an_activity_with_gpx(app, user_1, sport_1_cycling, gpx_file):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
client.post(
|
||||
'/api/activities',
|
||||
data=dict(
|
||||
file=(BytesIO(str.encode(gpx_file)), 'example.gpx'),
|
||||
data='{"sport_id": 1}'
|
||||
),
|
||||
headers=dict(
|
||||
content_type='multipart/form-data',
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
response = client.get(
|
||||
'/api/activities/1',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['activities']) == 1
|
||||
assert 'just an activity' == data['data']['activities'][0]['title']
|
||||
assert_activity_data_with_gpx(data)
|
||||
|
||||
map_id = data['data']['activities'][0]['map']
|
||||
|
||||
response = client.get(
|
||||
'/api/activities/1/gpx',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert '' in data['message']
|
||||
assert len(data['data']['gpx']) != ''
|
||||
|
||||
response = client.get(
|
||||
f'/api/activities/map/{map_id}',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# error case in the same test to avoid generate a new map file
|
||||
activity = Activity.query.filter_by(id=1).first()
|
||||
activity.map = 'incorrect path'
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert '' in data['message']
|
||||
assert len(data['data']['gpx']) != ''
|
||||
|
||||
response = client.get(
|
||||
f'/api/activities/map/{map_id}',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 500
|
||||
assert data['status'] == 'error'
|
||||
assert data['message'] == 'internal error.'
|
||||
|
||||
|
||||
def test_get_chart_data_activty_with_gpx(
|
||||
app, user_1, sport_1_cycling, gpx_file
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
client.post(
|
||||
'/api/activities',
|
||||
data=dict(
|
||||
file=(BytesIO(str.encode(gpx_file)), 'example.gpx'),
|
||||
data='{"sport_id": 1}'
|
||||
),
|
||||
headers=dict(
|
||||
content_type='multipart/form-data',
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
response = client.get(
|
||||
'/api/activities/1/chart_data',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert data['message'] == ''
|
||||
assert data['data']['chart_data'] != ''
|
||||
|
||||
|
||||
def test_add_an_activity_with_gpx_without_name(
|
||||
app, user_1, sport_1_cycling, gpx_file_wo_name
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/activities',
|
||||
data=dict(
|
||||
file=(BytesIO(str.encode(gpx_file_wo_name)), 'example.gpx'),
|
||||
data='{"sport_id": 1}'
|
||||
),
|
||||
headers=dict(
|
||||
content_type='multipart/form-data',
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 201
|
||||
assert 'created' in data['status']
|
||||
assert len(data['data']['activities']) == 1
|
||||
assert 'Cycling - 2018-03-13 12:44:45' == data['data']['activities'][0]['title'] # noqa
|
||||
assert_activity_data_with_gpx(data)
|
||||
|
||||
|
||||
def test_add_an_activity_with_gpx_invalid_file(
|
||||
app, user_1, sport_1_cycling, gpx_file_wo_track
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/activities',
|
||||
data=dict(
|
||||
file=(BytesIO(str.encode(gpx_file_wo_track)), 'example.gpx'),
|
||||
data='{"sport_id": 1}'
|
||||
),
|
||||
headers=dict(
|
||||
content_type='multipart/form-data',
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 500
|
||||
assert 'error' in data['status']
|
||||
assert 'Error during gpx file parsing.' in data['message']
|
||||
assert 'data' not in data
|
||||
|
||||
|
||||
def test_add_an_activity_with_gpx_invalid_xml(
|
||||
app, user_1, sport_1_cycling, gpx_file_invalid_xml
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/activities',
|
||||
data=dict(
|
||||
file=(BytesIO(str.encode(gpx_file_invalid_xml)), 'example.gpx'),
|
||||
data='{"sport_id": 1}'
|
||||
),
|
||||
headers=dict(
|
||||
content_type='multipart/form-data',
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 500
|
||||
assert 'error' in data['status']
|
||||
assert 'Error during gpx file parsing.' in data['message']
|
||||
assert 'data' not in data
|
||||
|
||||
|
||||
def test_add_an_activity_gpx_invalid_extension(
|
||||
app, user_1, sport_1_cycling, gpx_file
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/activities',
|
||||
data=dict(
|
||||
file=(BytesIO(str.encode(gpx_file)), 'example.png'),
|
||||
data='{"sport_id": 1}'
|
||||
),
|
||||
headers=dict(
|
||||
content_type='multipart/form-data',
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 400
|
||||
assert data['status'] == 'fail'
|
||||
assert data['message'] == 'File extension not allowed.'
|
||||
|
||||
|
||||
def test_add_an_activity_gpx_no_sport_id(
|
||||
app, user_1, sport_1_cycling, gpx_file
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/activities',
|
||||
data=dict(
|
||||
file=(BytesIO(str.encode(gpx_file)), 'example.gpx'),
|
||||
data='{}'
|
||||
),
|
||||
headers=dict(
|
||||
content_type='multipart/form-data',
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 400
|
||||
assert data['status'] == 'error'
|
||||
assert data['message'] == 'Invalid payload.'
|
||||
|
||||
|
||||
def test_add_an_activity_gpx_incorrect_sport_id(
|
||||
app, user_1, sport_1_cycling, gpx_file
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/activities',
|
||||
data=dict(
|
||||
file=(BytesIO(str.encode(gpx_file)), 'example.gpx'),
|
||||
data='{"sport_id": 2}'
|
||||
),
|
||||
headers=dict(
|
||||
content_type='multipart/form-data',
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 500
|
||||
assert data['status'] == 'error'
|
||||
assert data['message'] == 'Sport id: 2 does not exist'
|
||||
|
||||
|
||||
def test_add_an_activity_gpx_no_file(app, user_1, sport_1_cycling):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/activities',
|
||||
data=dict(
|
||||
data='{}'
|
||||
),
|
||||
headers=dict(
|
||||
content_type='multipart/form-data',
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 400
|
||||
assert data['status'] == 'fail'
|
||||
assert data['message'] == 'No file part.'
|
||||
|
||||
|
||||
def test_add_an_activity_no_gpx(app, user_1, sport_1_cycling):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/activities/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
sport_id=1,
|
||||
duration=3600,
|
||||
activity_date='2018-05-15 14:05',
|
||||
distance=10
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 201
|
||||
assert 'created' in data['status']
|
||||
assert len(data['data']['activities']) == 1
|
||||
assert_activity_data_wo_gpx(data)
|
||||
|
||||
|
||||
def test_get_an_activity_wo_gpx(app, user_1, sport_1_cycling):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
client.post(
|
||||
'/api/activities/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
sport_id=1,
|
||||
duration=3600,
|
||||
activity_date='2018-05-15 14:05',
|
||||
distance=10
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
response = client.get(
|
||||
'/api/activities/1',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['activities']) == 1
|
||||
assert_activity_data_wo_gpx(data)
|
||||
|
||||
|
||||
def test_add_an_activity_no_gpx_invalid_payload(app, user_1, sport_1_cycling):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/activities/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
sport_id=1,
|
||||
duration=3600,
|
||||
distance=10
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 400
|
||||
assert 'error' in data['status']
|
||||
assert 'Invalid payload.' in data['message']
|
||||
|
||||
|
||||
def test_add_an_activity_no_gpx_error(app, user_1, sport_1_cycling):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/activities/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
sport_id=1,
|
||||
duration=3600,
|
||||
activity_date='15/2018',
|
||||
distance=10
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 500
|
||||
assert 'fail' in data['status']
|
||||
assert 'Error during activity save.' in data['message']
|
||||
|
||||
|
||||
def test_add_activity_zero_value(
|
||||
app, user_1, sport_1_cycling, sport_2_running
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/activities/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
sport_id=1,
|
||||
duration=0,
|
||||
activity_date='2018-05-14 14:05',
|
||||
distance=0,
|
||||
title='Activity test'
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 201
|
||||
assert 'created' in data['status']
|
||||
assert len(data['data']['activities']) == 1
|
||||
assert 'creation_date' in data['data']['activities'][0]
|
||||
assert data['data']['activities'][0]['activity_date'] == 'Mon, 14 May 2018 14:05:00 GMT' # noqa
|
||||
assert data['data']['activities'][0]['user_id'] == 1
|
||||
assert data['data']['activities'][0]['sport_id'] == 1
|
||||
assert data['data']['activities'][0]['duration'] is None
|
||||
assert data['data']['activities'][0]['title'] == 'Activity test' # noqa
|
||||
assert data['data']['activities'][0]['ascent'] is None
|
||||
assert data['data']['activities'][0]['ave_speed'] is None
|
||||
assert data['data']['activities'][0]['descent'] is None
|
||||
assert data['data']['activities'][0]['distance'] is None
|
||||
assert data['data']['activities'][0]['max_alt'] is None
|
||||
assert data['data']['activities'][0]['max_speed'] is None
|
||||
assert data['data']['activities'][0]['min_alt'] is None
|
||||
assert data['data']['activities'][0]['moving'] is None
|
||||
assert data['data']['activities'][0]['pauses'] is None
|
||||
assert data['data']['activities'][0]['with_gpx'] is False
|
||||
|
||||
assert len(data['data']['activities'][0]['segments']) == 0
|
||||
assert len(data['data']['activities'][0]['records']) == 0
|
||||
|
||||
|
||||
def test_get_an_activity_with_zip(app, user_1, sport_1_cycling):
|
||||
file_path = os.path.join(app.root_path, 'tests/files/gpx_test.zip')
|
||||
# 'gpx_test.zip' contains 3 gpx files (same data) and 1 non-gpx file
|
||||
|
||||
with open(file_path, 'rb') as zip_file:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/activities',
|
||||
data=dict(
|
||||
file=(zip_file, 'gpx_test.zip'),
|
||||
data='{"sport_id": 1}'
|
||||
),
|
||||
headers=dict(
|
||||
content_type='multipart/form-data',
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 201
|
||||
assert 'created' in data['status']
|
||||
assert len(data['data']['activities']) == 3
|
||||
assert 'just an activity' == data['data']['activities'][0]['title']
|
||||
assert_activity_data_with_gpx(data)
|
||||
|
||||
|
||||
def test_get_an_activity_with_zip_folder(app, user_1, sport_1_cycling):
|
||||
file_path = os.path.join(app.root_path, 'tests/files/gpx_test_folder.zip')
|
||||
# 'gpx_test_folder.zip' contains 3 gpx files (same data) and 1 non-gpx file
|
||||
# in a folder
|
||||
|
||||
with open(file_path, 'rb') as zip_file:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/activities',
|
||||
data=dict(
|
||||
file=(zip_file, 'gpx_test_folder.zip'),
|
||||
data='{"sport_id": 1}'
|
||||
),
|
||||
headers=dict(
|
||||
content_type='multipart/form-data',
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 400
|
||||
assert 'fail' in data['status']
|
||||
assert len(data['data']['activities']) == 0
|
||||
|
||||
|
||||
def test_get_an_activity_with_zip_incorrect_file(app, user_1, sport_1_cycling):
|
||||
file_path = os.path.join(app.root_path, 'tests/files/gpx_test_incorrect.zip') # noqa
|
||||
# 'gpx_test_incorrect.zip' contains 2 gpx files, one is incorrect
|
||||
|
||||
with open(file_path, 'rb') as zip_file:
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/activities',
|
||||
data=dict(
|
||||
file=(zip_file, 'gpx_test_incorrect.zip'),
|
||||
data='{"sport_id": 1}'
|
||||
),
|
||||
headers=dict(
|
||||
content_type='multipart/form-data',
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 500
|
||||
assert 'error' in data['status']
|
||||
assert 'Error during gpx file parsing.' in data['message']
|
||||
assert 'data' not in data
|
@ -0,0 +1,597 @@
|
||||
import json
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
def assert_activity_data_with_gpx(data):
|
||||
assert 'creation_date' in data['data']['activities'][0]
|
||||
assert 'Tue, 13 Mar 2018 12:44:45 GMT' == data['data']['activities'][0]['activity_date'] # noqa
|
||||
assert 1 == data['data']['activities'][0]['user_id']
|
||||
assert '0:04:10' == data['data']['activities'][0]['duration']
|
||||
assert data['data']['activities'][0]['ascent'] == 0.4
|
||||
assert data['data']['activities'][0]['ave_speed'] == 4.6
|
||||
assert data['data']['activities'][0]['descent'] == 23.4
|
||||
assert data['data']['activities'][0]['distance'] == 0.32
|
||||
assert data['data']['activities'][0]['max_alt'] == 998.0
|
||||
assert data['data']['activities'][0]['max_speed'] == 5.11
|
||||
assert data['data']['activities'][0]['min_alt'] == 975.0
|
||||
assert data['data']['activities'][0]['moving'] == '0:04:10'
|
||||
assert data['data']['activities'][0]['pauses'] is None
|
||||
assert data['data']['activities'][0]['with_gpx'] is True
|
||||
|
||||
records = data['data']['activities'][0]['records']
|
||||
assert len(records) == 4
|
||||
assert records[0]['sport_id'] == 2
|
||||
assert records[0]['activity_id'] == 1
|
||||
assert records[0]['record_type'] == 'MS'
|
||||
assert records[0]['activity_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||
assert records[0]['value'] == 5.11
|
||||
assert records[1]['sport_id'] == 2
|
||||
assert records[1]['activity_id'] == 1
|
||||
assert records[1]['record_type'] == 'LD'
|
||||
assert records[1]['activity_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||
assert records[1]['value'] == '0:04:10'
|
||||
assert records[2]['sport_id'] == 2
|
||||
assert records[2]['activity_id'] == 1
|
||||
assert records[2]['record_type'] == 'FD'
|
||||
assert records[2]['activity_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||
assert records[2]['value'] == 0.32
|
||||
assert records[3]['sport_id'] == 2
|
||||
assert records[3]['activity_id'] == 1
|
||||
assert records[3]['record_type'] == 'AS'
|
||||
assert records[3]['activity_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||
assert records[3]['value'] == 4.6
|
||||
|
||||
|
||||
def test_edit_an_activity_with_gpx(
|
||||
app, user_1, sport_1_cycling, sport_2_running, gpx_file
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
client.post(
|
||||
'/api/activities',
|
||||
data=dict(
|
||||
file=(BytesIO(str.encode(gpx_file)), 'example.gpx'),
|
||||
data='{"sport_id": 1}'
|
||||
),
|
||||
headers=dict(
|
||||
content_type='multipart/form-data',
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
response = client.patch(
|
||||
'/api/activities/1',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
sport_id=2,
|
||||
title="Activity test",
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['activities']) == 1
|
||||
assert 2 == data['data']['activities'][0]['sport_id']
|
||||
assert data['data']['activities'][0]['title'] == 'Activity test'
|
||||
assert_activity_data_with_gpx(data)
|
||||
|
||||
|
||||
def test_edit_an_activity_with_gpx_partial(
|
||||
app, user_1, sport_1_cycling, sport_2_running, gpx_file
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
client.post(
|
||||
'/api/activities',
|
||||
data=dict(
|
||||
file=(BytesIO(str.encode(gpx_file)), 'example.gpx'),
|
||||
data='{"sport_id": 1}'
|
||||
),
|
||||
headers=dict(
|
||||
content_type='multipart/form-data',
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
response = client.patch(
|
||||
'/api/activities/1',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
sport_id=2,
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['activities']) == 1
|
||||
assert 2 == data['data']['activities'][0]['sport_id']
|
||||
assert data['data']['activities'][0]['title'] == 'just an activity'
|
||||
assert_activity_data_with_gpx(data)
|
||||
|
||||
|
||||
def test_edit_an_activity_with_gpx_invalid_payload(
|
||||
app, user_1, sport_1_cycling, gpx_file
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
client.post(
|
||||
'/api/activities',
|
||||
data=dict(
|
||||
file=(BytesIO(str.encode(gpx_file)), 'example.gpx'),
|
||||
data='{"sport_id": 1}'
|
||||
),
|
||||
headers=dict(
|
||||
content_type='multipart/form-data',
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
response = client.patch(
|
||||
'/api/activities/1',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict()),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 400
|
||||
assert 'error' in data['status']
|
||||
assert 'Invalid payload.' in data['message']
|
||||
|
||||
|
||||
def test_edit_an_activity_with_gpx_incorrect_data(
|
||||
app, user_1, sport_1_cycling, gpx_file
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
client.post(
|
||||
'/api/activities',
|
||||
data=dict(
|
||||
file=(BytesIO(str.encode(gpx_file)), 'example.gpx'),
|
||||
data='{"sport_id": 1}'
|
||||
),
|
||||
headers=dict(
|
||||
content_type='multipart/form-data',
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
response = client.patch(
|
||||
'/api/activities/1',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
sport_id=2,
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 500
|
||||
assert 'error' in data['status']
|
||||
assert 'Error. Please try again or contact the administrator.' in data['message'] # noqa
|
||||
|
||||
|
||||
def test_edit_an_activity_wo_gpx(
|
||||
app, user_1, sport_1_cycling, sport_2_running
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
|
||||
response = client.post(
|
||||
'/api/activities/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
sport_id=1,
|
||||
duration=3600,
|
||||
activity_date='2018-05-14 14:05',
|
||||
distance=7,
|
||||
title='Activity test'
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 201
|
||||
assert 'created' in data['status']
|
||||
assert len(data['data']['activities']) == 1
|
||||
assert 'creation_date' in data['data']['activities'][0]
|
||||
assert data['data']['activities'][0]['activity_date'] == 'Mon, 14 May 2018 14:05:00 GMT' # noqa
|
||||
assert data['data']['activities'][0]['user_id'] == 1
|
||||
assert data['data']['activities'][0]['sport_id'] == 1
|
||||
assert data['data']['activities'][0]['duration'] == '1:00:00'
|
||||
assert data['data']['activities'][0]['title'] == 'Activity test' # noqa
|
||||
assert data['data']['activities'][0]['ascent'] is None
|
||||
assert data['data']['activities'][0]['ave_speed'] == 7.0
|
||||
assert data['data']['activities'][0]['descent'] is None
|
||||
assert data['data']['activities'][0]['distance'] == 7.0
|
||||
assert data['data']['activities'][0]['max_alt'] is None
|
||||
assert data['data']['activities'][0]['max_speed'] == 7.0
|
||||
assert data['data']['activities'][0]['min_alt'] is None
|
||||
assert data['data']['activities'][0]['moving'] == '1:00:00'
|
||||
assert data['data']['activities'][0]['pauses'] is None
|
||||
assert data['data']['activities'][0]['with_gpx'] is False
|
||||
|
||||
records = data['data']['activities'][0]['records']
|
||||
assert len(records) == 4
|
||||
assert records[0]['sport_id'] == 1
|
||||
assert records[0]['activity_id'] == 1
|
||||
assert records[0]['record_type'] == 'MS'
|
||||
assert records[0]['activity_date'] == 'Mon, 14 May 2018 14:05:00 GMT'
|
||||
assert records[0]['value'] == 7.0
|
||||
assert records[1]['sport_id'] == 1
|
||||
assert records[1]['activity_id'] == 1
|
||||
assert records[1]['record_type'] == 'LD'
|
||||
assert records[1]['activity_date'] == 'Mon, 14 May 2018 14:05:00 GMT'
|
||||
assert records[1]['value'] == '1:00:00'
|
||||
assert records[2]['sport_id'] == 1
|
||||
assert records[2]['activity_id'] == 1
|
||||
assert records[2]['record_type'] == 'FD'
|
||||
assert records[2]['activity_date'] == 'Mon, 14 May 2018 14:05:00 GMT'
|
||||
assert records[2]['value'] == 7.0
|
||||
assert records[3]['sport_id'] == 1
|
||||
assert records[3]['activity_id'] == 1
|
||||
assert records[3]['record_type'] == 'AS'
|
||||
assert records[3]['activity_date'] == 'Mon, 14 May 2018 14:05:00 GMT'
|
||||
assert records[3]['value'] == 7.0
|
||||
|
||||
response = client.patch(
|
||||
'/api/activities/1',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
sport_id=2,
|
||||
duration=3600,
|
||||
activity_date='2018-05-15 14:05',
|
||||
distance=8,
|
||||
title='Activity test'
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['activities']) == 1
|
||||
assert 'creation_date' in data['data']['activities'][0]
|
||||
assert data['data']['activities'][0]['activity_date'] == 'Tue, 15 May 2018 14:05:00 GMT' # noqa
|
||||
assert data['data']['activities'][0]['user_id'] == 1
|
||||
assert data['data']['activities'][0]['sport_id'] == 2
|
||||
assert data['data']['activities'][0]['duration'] == '1:00:00'
|
||||
assert data['data']['activities'][0]['title'] == 'Activity test'
|
||||
assert data['data']['activities'][0]['ascent'] is None
|
||||
assert data['data']['activities'][0]['ave_speed'] == 8.0
|
||||
assert data['data']['activities'][0]['descent'] is None
|
||||
assert data['data']['activities'][0]['distance'] == 8.0
|
||||
assert data['data']['activities'][0]['max_alt'] is None
|
||||
assert data['data']['activities'][0]['max_speed'] == 8.0
|
||||
assert data['data']['activities'][0]['min_alt'] is None
|
||||
assert data['data']['activities'][0]['moving'] == '1:00:00'
|
||||
assert data['data']['activities'][0]['pauses'] is None
|
||||
assert data['data']['activities'][0]['with_gpx'] is False
|
||||
|
||||
records = data['data']['activities'][0]['records']
|
||||
assert len(records) == 4
|
||||
assert records[0]['sport_id'] == 2
|
||||
assert records[0]['activity_id'] == 1
|
||||
assert records[0]['record_type'] == 'MS'
|
||||
assert records[0]['activity_date'] == 'Tue, 15 May 2018 14:05:00 GMT'
|
||||
assert records[0]['value'] == 8.0
|
||||
assert records[1]['sport_id'] == 2
|
||||
assert records[1]['activity_id'] == 1
|
||||
assert records[1]['record_type'] == 'LD'
|
||||
assert records[1]['activity_date'] == 'Tue, 15 May 2018 14:05:00 GMT'
|
||||
assert records[1]['value'] == '1:00:00'
|
||||
assert records[2]['sport_id'] == 2
|
||||
assert records[2]['activity_id'] == 1
|
||||
assert records[2]['record_type'] == 'FD'
|
||||
assert records[2]['activity_date'] == 'Tue, 15 May 2018 14:05:00 GMT'
|
||||
assert records[2]['value'] == 8.0
|
||||
assert records[3]['sport_id'] == 2
|
||||
assert records[3]['activity_id'] == 1
|
||||
assert records[3]['record_type'] == 'AS'
|
||||
assert records[3]['activity_date'] == 'Tue, 15 May 2018 14:05:00 GMT'
|
||||
assert records[3]['value'] == 8.0
|
||||
|
||||
|
||||
def test_edit_an_activity_wo_gpx_partial(
|
||||
app, user_1, sport_1_cycling
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/activities/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
sport_id=1,
|
||||
duration=3600,
|
||||
activity_date='2018-05-14 14:05',
|
||||
distance=7
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 201
|
||||
assert 'created' in data['status']
|
||||
assert len(data['data']['activities']) == 1
|
||||
assert 'creation_date' in data['data']['activities'][0]
|
||||
assert data['data']['activities'][0][
|
||||
'activity_date'] == 'Mon, 14 May 2018 14:05:00 GMT' # noqa
|
||||
assert data['data']['activities'][0]['user_id'] == 1
|
||||
assert data['data']['activities'][0]['sport_id'] == 1
|
||||
assert data['data']['activities'][0]['duration'] == '1:00:00'
|
||||
assert data['data']['activities'][0]['title'] == 'Cycling - 2018-05-14 14:05:00' # noqa
|
||||
assert data['data']['activities'][0]['ascent'] is None
|
||||
assert data['data']['activities'][0]['ave_speed'] == 7.0
|
||||
assert data['data']['activities'][0]['descent'] is None
|
||||
assert data['data']['activities'][0]['distance'] == 7.0
|
||||
assert data['data']['activities'][0]['max_alt'] is None
|
||||
assert data['data']['activities'][0]['max_speed'] == 7.0
|
||||
assert data['data']['activities'][0]['min_alt'] is None
|
||||
assert data['data']['activities'][0]['moving'] == '1:00:00'
|
||||
assert data['data']['activities'][0]['pauses'] is None
|
||||
assert data['data']['activities'][0]['with_gpx'] is False
|
||||
|
||||
records = data['data']['activities'][0]['records']
|
||||
assert len(records) == 4
|
||||
assert records[0]['sport_id'] == 1
|
||||
assert records[0]['activity_id'] == 1
|
||||
assert records[0]['record_type'] == 'MS'
|
||||
assert records[0]['activity_date'] == 'Mon, 14 May 2018 14:05:00 GMT'
|
||||
assert records[0]['value'] == 7.0
|
||||
assert records[1]['sport_id'] == 1
|
||||
assert records[1]['activity_id'] == 1
|
||||
assert records[1]['record_type'] == 'LD'
|
||||
assert records[1]['activity_date'] == 'Mon, 14 May 2018 14:05:00 GMT'
|
||||
assert records[1]['value'] == '1:00:00'
|
||||
assert records[2]['sport_id'] == 1
|
||||
assert records[2]['activity_id'] == 1
|
||||
assert records[2]['record_type'] == 'FD'
|
||||
assert records[2]['activity_date'] == 'Mon, 14 May 2018 14:05:00 GMT'
|
||||
assert records[2]['value'] == 7.0
|
||||
assert records[3]['sport_id'] == 1
|
||||
assert records[3]['activity_id'] == 1
|
||||
assert records[3]['record_type'] == 'AS'
|
||||
assert records[3]['activity_date'] == 'Mon, 14 May 2018 14:05:00 GMT'
|
||||
assert records[3]['value'] == 7.0
|
||||
|
||||
response = client.patch(
|
||||
'/api/activities/1',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
sport_id=1,
|
||||
distance=10
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['activities']) == 1
|
||||
assert 'creation_date' in data['data']['activities'][0]
|
||||
assert data['data']['activities'][0]['activity_date'] == 'Mon, 14 May 2018 14:05:00 GMT' # noqa
|
||||
assert data['data']['activities'][0]['user_id'] == 1
|
||||
assert data['data']['activities'][0]['sport_id'] == 1
|
||||
assert data['data']['activities'][0]['duration'] == '1:00:00'
|
||||
assert data['data']['activities'][0]['title'] == 'Cycling - 2018-05-14 14:05:00' # noqa
|
||||
assert data['data']['activities'][0]['ascent'] is None
|
||||
assert data['data']['activities'][0]['ave_speed'] == 10.0
|
||||
assert data['data']['activities'][0]['descent'] is None
|
||||
assert data['data']['activities'][0]['distance'] == 10.0
|
||||
assert data['data']['activities'][0]['max_alt'] is None
|
||||
assert data['data']['activities'][0]['max_speed'] == 10.0
|
||||
assert data['data']['activities'][0]['min_alt'] is None
|
||||
assert data['data']['activities'][0]['moving'] == '1:00:00'
|
||||
assert data['data']['activities'][0]['pauses'] is None
|
||||
assert data['data']['activities'][0]['with_gpx'] is False
|
||||
|
||||
records = data['data']['activities'][0]['records']
|
||||
assert len(records) == 4
|
||||
assert records[0]['sport_id'] == 1
|
||||
assert records[0]['activity_id'] == 1
|
||||
assert records[0]['record_type'] == 'MS'
|
||||
assert records[0]['activity_date'] == 'Mon, 14 May 2018 14:05:00 GMT'
|
||||
assert records[0]['value'] == 10.0
|
||||
assert records[1]['sport_id'] == 1
|
||||
assert records[1]['activity_id'] == 1
|
||||
assert records[1]['record_type'] == 'LD'
|
||||
assert records[1]['activity_date'] == 'Mon, 14 May 2018 14:05:00 GMT'
|
||||
assert records[1]['value'] == '1:00:00'
|
||||
assert records[2]['sport_id'] == 1
|
||||
assert records[2]['activity_id'] == 1
|
||||
assert records[2]['record_type'] == 'FD'
|
||||
assert records[2]['activity_date'] == 'Mon, 14 May 2018 14:05:00 GMT'
|
||||
assert records[2]['value'] == 10.0
|
||||
assert records[3]['sport_id'] == 1
|
||||
assert records[3]['activity_id'] == 1
|
||||
assert records[3]['record_type'] == 'AS'
|
||||
assert records[3]['activity_date'] == 'Mon, 14 May 2018 14:05:00 GMT'
|
||||
assert records[3]['value'] == 10.0
|
||||
|
||||
|
||||
def test_edit_an_activity_wo_gpx_invalid_payload(
|
||||
app, user_1, sport_1_cycling, activity_cycling_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.patch(
|
||||
'/api/activities/1',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict()),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 400
|
||||
assert 'error' in data['status']
|
||||
assert 'Invalid payload.' in data['message']
|
||||
|
||||
|
||||
def test_edit_an_activity_wo_gpx_incorrect_data(
|
||||
app, user_1, sport_1_cycling, activity_cycling_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.patch(
|
||||
'/api/activities/1',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
sport_id=1,
|
||||
duration=3600,
|
||||
activity_date='15/2018',
|
||||
distance=10
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 500
|
||||
assert 'error' in data['status']
|
||||
assert 'Error. Please try again or contact the administrator.' \
|
||||
in data['message']
|
||||
|
||||
|
||||
def test_edit_an_activity_no_activity(
|
||||
app, user_1, sport_1_cycling
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.patch(
|
||||
'/api/activities/1',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
sport_id=1,
|
||||
duration=3600,
|
||||
activity_date='2018-05-15 14:05',
|
||||
distance=10
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 404
|
||||
assert 'not found' in data['status']
|
||||
assert len(data['data']['activities']) == 0
|
@ -0,0 +1,136 @@
|
||||
import json
|
||||
import os
|
||||
from io import BytesIO
|
||||
|
||||
from fittrackee_api.activities.models import Activity
|
||||
|
||||
|
||||
def get_gpx_filepath(activity_id):
|
||||
activity = Activity.query.filter_by(id=activity_id).first()
|
||||
return activity.gpx
|
||||
|
||||
|
||||
def test_delete_an_activity_with_gpx(app, user_1, sport_1_cycling, gpx_file):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
client.post(
|
||||
'/api/activities',
|
||||
data=dict(
|
||||
file=(BytesIO(str.encode(gpx_file)), 'example.gpx'),
|
||||
data='{"sport_id": 1}'
|
||||
),
|
||||
headers=dict(
|
||||
content_type='multipart/form-data',
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
response = client.delete(
|
||||
'/api/activities/1',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
|
||||
assert response.status_code == 204
|
||||
|
||||
|
||||
def test_delete_an_activity_wo_gpx(
|
||||
app, user_1, sport_1_cycling, activity_cycling_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.delete(
|
||||
'/api/activities/1',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
assert response.status_code == 204
|
||||
|
||||
|
||||
def test_delete_an_activity_no_activityy(app, user_1):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.delete(
|
||||
'/api/activities/9999',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert response.status_code == 404
|
||||
assert 'not found' in data['status']
|
||||
|
||||
|
||||
def test_delete_an_activity_with_gpx_invalid_file(
|
||||
app, user_1, sport_1_cycling, gpx_file):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
client.post(
|
||||
'/api/activities',
|
||||
data=dict(
|
||||
file=(BytesIO(str.encode(gpx_file)), 'example.gpx'),
|
||||
data='{"sport_id": 1}'
|
||||
),
|
||||
headers=dict(
|
||||
content_type='multipart/form-data',
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
|
||||
gpx_filepath = get_gpx_filepath(1)
|
||||
os.remove(gpx_filepath)
|
||||
|
||||
response = client.delete(
|
||||
'/api/activities/1',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 500
|
||||
assert 'error' in data['status']
|
||||
assert 'Error. Please try again or contact the administrator.' \
|
||||
in data['message']
|
22
fittrackee_api/fittrackee_api/tests/test_activities_model.py
Normal file
22
fittrackee_api/fittrackee_api/tests/test_activities_model.py
Normal file
@ -0,0 +1,22 @@
|
||||
|
||||
|
||||
def test_add_activity(
|
||||
app, sport_1_cycling, user_1, activity_cycling_user_1
|
||||
):
|
||||
activity_cycling_user_1.title = 'Test'
|
||||
|
||||
assert 1 == activity_cycling_user_1.id
|
||||
assert 1 == activity_cycling_user_1.user_id
|
||||
assert 1 == activity_cycling_user_1.sport_id
|
||||
assert '2018-01-01 00:00:00' == str(activity_cycling_user_1.activity_date)
|
||||
assert 10.0 == float(activity_cycling_user_1.distance)
|
||||
assert '0:17:04' == str(activity_cycling_user_1.duration)
|
||||
assert 'Test' == activity_cycling_user_1.title
|
||||
assert '<Activity \'Cycling\' - 2018-01-01 00:00:00>' == str(activity_cycling_user_1) # noqa
|
||||
|
||||
|
||||
def test_add_segment(
|
||||
app, sport_1_cycling, user_1, activity_cycling_user_1,
|
||||
activity_cycling_user_1_segment
|
||||
):
|
||||
assert '<Segment \'0\' for activity \'1\'>' == str(activity_cycling_user_1_segment) # noqa
|
752
fittrackee_api/fittrackee_api/tests/test_auth_api.py
Normal file
752
fittrackee_api/fittrackee_api/tests/test_auth_api.py
Normal file
@ -0,0 +1,752 @@
|
||||
import json
|
||||
import time
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
def test_user_registration(app):
|
||||
client = app.test_client()
|
||||
response = client.post(
|
||||
'/api/auth/register',
|
||||
data=json.dumps(dict(
|
||||
username='justatest',
|
||||
email='test@test.com',
|
||||
password='12345678',
|
||||
password_conf='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'success'
|
||||
assert data['message'] == 'Successfully registered.'
|
||||
assert data['auth_token']
|
||||
assert response.content_type == 'application/json'
|
||||
assert response.status_code == 201
|
||||
|
||||
|
||||
def test_user_registration_user_already_exists(app, user_1):
|
||||
client = app.test_client()
|
||||
response = client.post(
|
||||
'/api/auth/register',
|
||||
data=json.dumps(dict(
|
||||
username='test',
|
||||
email='test@test.com',
|
||||
password='12345678',
|
||||
password_conf='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'error'
|
||||
assert data['message'] == 'Sorry. That user already exists.'
|
||||
assert response.content_type == 'application/json'
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
def test_user_registration_invalid_short_username(app):
|
||||
client = app.test_client()
|
||||
response = client.post(
|
||||
'/api/auth/register',
|
||||
data=json.dumps(dict(
|
||||
username='t',
|
||||
email='test@test.com',
|
||||
password='12345678',
|
||||
password_conf='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'error'
|
||||
assert data['message'] == "Errors: Username: 3 to 12 characters required.\n" # noqa
|
||||
assert response.content_type == 'application/json'
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
def test_user_registration_invalid_long_username(app):
|
||||
client = app.test_client()
|
||||
response = client.post(
|
||||
'/api/auth/register',
|
||||
data=json.dumps(dict(
|
||||
username='testestestestestest',
|
||||
email='test@test.com',
|
||||
password='12345678',
|
||||
password_conf='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'error'
|
||||
assert data['message'] == "Errors: Username: 3 to 12 characters required.\n" # noqa
|
||||
assert response.content_type == 'application/json'
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
def test_user_registration_invalid_email(app):
|
||||
client = app.test_client()
|
||||
response = client.post(
|
||||
'/api/auth/register',
|
||||
data=json.dumps(dict(
|
||||
username='test',
|
||||
email='test@test',
|
||||
password='12345678',
|
||||
password_conf='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'error'
|
||||
assert data['message'] == "Errors: Valid email must be provided.\n" # noqa
|
||||
assert response.content_type == 'application/json'
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
def test_user_registration_invalid_short_password(app):
|
||||
client = app.test_client()
|
||||
response = client.post(
|
||||
'/api/auth/register',
|
||||
data=json.dumps(dict(
|
||||
username='test',
|
||||
email='test@test.com',
|
||||
password='1234567',
|
||||
password_conf='1234567'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'error'
|
||||
assert data['message'] == "Errors: Password: 8 characters required.\n" # noqa
|
||||
assert response.content_type == 'application/json'
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
def test_user_registration_mismatched_password(app):
|
||||
client = app.test_client()
|
||||
response = client.post(
|
||||
'/api/auth/register',
|
||||
data=json.dumps(dict(
|
||||
username='test',
|
||||
email='test@test.com',
|
||||
password='12345678',
|
||||
password_conf='87654321'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'error'
|
||||
assert data['message'] == "Errors: Password and password confirmation don\'t match.\n" # noqa
|
||||
assert response.content_type == 'application/json'
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
def test_user_registration_invalid_json(app):
|
||||
client = app.test_client()
|
||||
response = client.post(
|
||||
'/api/auth/register',
|
||||
data=json.dumps(dict()),
|
||||
content_type='application/json'
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert response.status_code, 400
|
||||
assert 'Invalid payload.', data['message']
|
||||
assert 'error', data['status']
|
||||
|
||||
|
||||
def test_user_registration_invalid_json_keys_no_username(app):
|
||||
client = app.test_client()
|
||||
response = client.post(
|
||||
'/api/auth/register',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678',
|
||||
password_conf='12345678')),
|
||||
content_type='application/json',
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert response.status_code == 400
|
||||
assert 'Invalid payload.' in data['message']
|
||||
assert 'error' in data['status']
|
||||
|
||||
|
||||
def test_user_registration_invalid_json_keys_no_email(app):
|
||||
client = app.test_client()
|
||||
response = client.post(
|
||||
'/api/auth/register',
|
||||
data=json.dumps(dict(
|
||||
username='test',
|
||||
password='12345678',
|
||||
password_conf='12345678')),
|
||||
content_type='application/json',
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert response.status_code == 400
|
||||
assert 'Invalid payload.' in data['message']
|
||||
assert 'error' in data['status']
|
||||
|
||||
|
||||
def test_user_registration_invalid_json_keys_no_password(app):
|
||||
client = app.test_client()
|
||||
response = client.post(
|
||||
'/api/auth/register',
|
||||
data=json.dumps(dict(
|
||||
username='test',
|
||||
email='test@test.com',
|
||||
password_conf='12345678')),
|
||||
content_type='application/json',
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert response.status_code == 400
|
||||
assert 'Invalid payload.', data['message']
|
||||
assert 'error', data['status']
|
||||
|
||||
|
||||
def test_user_registration_invalid_json_keys_no_password_conf(app):
|
||||
client = app.test_client()
|
||||
response = client.post(
|
||||
'/api/auth/register',
|
||||
data=json.dumps(dict(
|
||||
username='test',
|
||||
email='test@test.com',
|
||||
password='12345678')),
|
||||
content_type='application/json',
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert response.status_code == 400
|
||||
assert 'Invalid payload.' in data['message']
|
||||
assert 'error' in data['status']
|
||||
|
||||
|
||||
def test_user_registration_invalid_data(app):
|
||||
client = app.test_client()
|
||||
response = client.post(
|
||||
'/api/auth/register',
|
||||
data=json.dumps(dict(
|
||||
username=1,
|
||||
email='test@test.com',
|
||||
password='12345678',
|
||||
password_conf='12345678'
|
||||
)),
|
||||
content_type='application/json',
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert response.status_code == 500
|
||||
assert 'Error. Please try again or contact the administrator.' in data['message'] # noqa
|
||||
assert 'error' in data['status']
|
||||
|
||||
|
||||
def test_login_registered_user(app, user_1):
|
||||
client = app.test_client()
|
||||
response = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'success'
|
||||
assert data['message'] == 'Successfully logged in.'
|
||||
assert data['auth_token']
|
||||
assert response.content_type == 'application/json'
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_login_no_registered_user(app):
|
||||
client = app.test_client()
|
||||
response = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'error'
|
||||
assert data['message'] == 'Invalid credentials.'
|
||||
assert response.content_type == 'application/json'
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_login_invalid_payload(app):
|
||||
client = app.test_client()
|
||||
response = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict()),
|
||||
content_type='application/json'
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'error'
|
||||
assert data['message'] == 'Invalid payload.'
|
||||
assert response.content_type == 'application/json'
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
def test_login_registered_user_invalid_password(app, user_1):
|
||||
client = app.test_client()
|
||||
response = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='123456789'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'error'
|
||||
assert data['message'] == 'Invalid credentials.'
|
||||
assert response.content_type == 'application/json'
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_logout(app, user_1):
|
||||
client = app.test_client()
|
||||
# user login
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
# valid token logout
|
||||
response = client.get(
|
||||
'/api/auth/logout',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'success'
|
||||
assert data['message'] == 'Successfully logged out.'
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_logout_expired_token(app, user_1):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
# invalid token logout
|
||||
time.sleep(4)
|
||||
response = client.get(
|
||||
'/api/auth/logout',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'error'
|
||||
assert data['message'] == 'Signature expired. Please log in again.'
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_logout_invalid(app):
|
||||
client = app.test_client()
|
||||
response = client.get(
|
||||
'/api/auth/logout',
|
||||
headers=dict(Authorization='Bearer invalid'))
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'error'
|
||||
assert data['message'] == 'Invalid token. Please log in again.'
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_logout_invalid_headers(app):
|
||||
client = app.test_client()
|
||||
response = client.get(
|
||||
'/api/auth/logout',
|
||||
headers=dict())
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'error'
|
||||
assert data['message'] == 'Provide a valid auth token.'
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_user_profile_minimal(app, user_1):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
'/api/auth/profile',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'success'
|
||||
assert data['data'] is not None
|
||||
assert data['data']['username'] == 'test'
|
||||
assert data['data']['email'] == 'test@test.com'
|
||||
assert data['data']['created_at']
|
||||
assert not data['data']['admin']
|
||||
assert data['data']['nb_activities'] == 0
|
||||
assert data['data']['nb_sports'] == 0
|
||||
assert data['data']['total_distance'] == 0
|
||||
assert data['data']['total_duration'] == '0:00:00'
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_user_profile_full(app, user_1_full):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
'/api/auth/profile',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'success'
|
||||
assert data['data'] is not None
|
||||
assert data['data']['username'] == 'test'
|
||||
assert data['data']['email'] == 'test@test.com'
|
||||
assert data['data']['created_at']
|
||||
assert not data['data']['admin']
|
||||
assert data['data']['first_name'] == 'John'
|
||||
assert data['data']['last_name'] == 'Doe'
|
||||
assert data['data']['birth_date']
|
||||
assert data['data']['bio'] == 'just a random guy'
|
||||
assert data['data']['location'] == 'somewhere'
|
||||
assert data['data']['nb_activities'] == 0
|
||||
assert data['data']['nb_sports'] == 0
|
||||
assert data['data']['total_distance'] == 0
|
||||
assert data['data']['total_duration'] == '0:00:00'
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_user_profile_with_activities(
|
||||
app, user_1, sport_1_cycling, sport_2_running,
|
||||
activity_cycling_user_1, activity_running_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
'/api/auth/profile',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'success'
|
||||
assert data['data'] is not None
|
||||
assert data['data']['username'] == 'test'
|
||||
assert data['data']['email'] == 'test@test.com'
|
||||
assert data['data']['created_at']
|
||||
assert not data['data']['admin']
|
||||
assert data['data']['nb_activities'] == 2
|
||||
assert data['data']['nb_sports'] == 2
|
||||
assert data['data']['total_distance'] == 22
|
||||
assert data['data']['total_duration'] == '1:57:04'
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_invalid_profile(app):
|
||||
client = app.test_client()
|
||||
response = client.get(
|
||||
'/api/auth/profile',
|
||||
headers=dict(Authorization='Bearer invalid'))
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'error'
|
||||
assert data['message'] == 'Invalid token. Please log in again.'
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_user_profile_valid_update(app, user_1):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/auth/profile/edit',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
first_name='John',
|
||||
last_name='Doe',
|
||||
location='Somewhere',
|
||||
bio='just a random guy',
|
||||
birth_date='1980-01-01',
|
||||
password='87654321',
|
||||
password_conf='87654321'
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'success'
|
||||
assert data['message'] == 'User profile updated.'
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_user_profile_valid_update_without_password(app, user_1):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/auth/profile/edit',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
first_name='John',
|
||||
last_name='Doe',
|
||||
location='Somewhere',
|
||||
bio='just a random guy',
|
||||
birth_date='1980-01-01'
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'success'
|
||||
assert data['message'] == 'User profile updated.'
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_user_profile_valid_update_with_one_field(app, user_1):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/auth/profile/edit',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
first_name='John'
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'success'
|
||||
assert data['message'] == 'User profile updated.'
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_user_profile_update_invalid_json(app, user_1):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/auth/profile/edit',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict()),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert response.status_code == 400
|
||||
assert 'Invalid payload.' in data['message']
|
||||
assert 'error' in data['status']
|
||||
|
||||
|
||||
def test_user_profile_invalid_password(app, user_1):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/auth/profile/edit',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
first_name='John',
|
||||
last_name='Doe',
|
||||
location='Somewhere',
|
||||
bio='just a random guy',
|
||||
birth_date='1980-01-01',
|
||||
password='87654321',
|
||||
password_conf='876543210'
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'error'
|
||||
assert data['message'] == 'Password and password confirmation don\'t match.\n' # noqa
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
def test_user_profile_missing_password_conf(app, user_1):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/auth/profile/edit',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
first_name='John',
|
||||
last_name='Doe',
|
||||
location='Somewhere',
|
||||
bio='just a random guy',
|
||||
birth_date='1980-01-01',
|
||||
password='87654321',
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'error'
|
||||
assert data['message'] == 'Password and password confirmation don\'t match.\n' # noqa
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
def test_update_user_picture(app, user_1):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/auth/picture',
|
||||
data=dict(
|
||||
file=(BytesIO(b'avatar'), 'avatar.png')
|
||||
),
|
||||
headers=dict(
|
||||
content_type='multipart/form-data',
|
||||
authorization='Bearer ' +
|
||||
json.loads(resp_login.data.decode())['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'success'
|
||||
assert data['message'] == 'User picture updated.'
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_update_user_no_picture(app, user_1):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/auth/picture',
|
||||
headers=dict(
|
||||
content_type='multipart/form-data',
|
||||
authorization='Bearer ' +
|
||||
json.loads(resp_login.data.decode())['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'fail'
|
||||
assert data['message'] == 'No file part.'
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
def test_update_user_invalid_picture(app, user_1):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/auth/picture',
|
||||
data=dict(
|
||||
file=(BytesIO(b'avatar'), 'avatar.bmp')
|
||||
),
|
||||
headers=dict(
|
||||
content_type='multipart/form-data',
|
||||
authorization='Bearer ' +
|
||||
json.loads(resp_login.data.decode())['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
assert data['status'] == 'fail'
|
||||
assert data['message'] == 'File extension not allowed.'
|
||||
assert response.status_code == 400
|
18
fittrackee_api/fittrackee_api/tests/test_config.py
Normal file
18
fittrackee_api/fittrackee_api/tests/test_config.py
Normal file
@ -0,0 +1,18 @@
|
||||
import os
|
||||
|
||||
|
||||
def test_development_config(app):
|
||||
app.config.from_object('fittrackee_api.config.DevelopmentConfig')
|
||||
assert app.config['DEBUG']
|
||||
assert not app.config['TESTING']
|
||||
assert app.config['SQLALCHEMY_DATABASE_URI'] == os.environ.get(
|
||||
'DATABASE_URL')
|
||||
|
||||
|
||||
def test_testing_config(app):
|
||||
app.config.from_object('fittrackee_api.config.TestingConfig')
|
||||
assert app.config['DEBUG']
|
||||
assert app.config['TESTING']
|
||||
assert not app.config['PRESERVE_CONTEXT_ON_EXCEPTION']
|
||||
assert app.config['SQLALCHEMY_DATABASE_URI'] == os.environ.get(
|
||||
'DATABASE_TEST_URL')
|
823
fittrackee_api/fittrackee_api/tests/test_records_api.py
Normal file
823
fittrackee_api/fittrackee_api/tests/test_records_api.py
Normal file
@ -0,0 +1,823 @@
|
||||
import json
|
||||
|
||||
|
||||
def test_get_records_for_authenticated_user(
|
||||
app, user_1, user_2, sport_1_cycling, sport_2_running,
|
||||
activity_cycling_user_1, activity_cycling_user_2
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
'/api/records',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['records']) == 4
|
||||
|
||||
assert 'Mon, 01 Jan 2018 00:00:00 GMT' == data['data']['records'][0]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][0]['user_id']
|
||||
assert 1 == data['data']['records'][0]['sport_id']
|
||||
assert 1 == data['data']['records'][0]['activity_id']
|
||||
assert 'AS' == data['data']['records'][0]['record_type']
|
||||
assert 'value' in data['data']['records'][0]
|
||||
|
||||
assert 'Mon, 01 Jan 2018 00:00:00 GMT' == data['data']['records'][1]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][1]['user_id']
|
||||
assert 1 == data['data']['records'][1]['sport_id']
|
||||
assert 1 == data['data']['records'][1]['activity_id']
|
||||
assert 'FD' == data['data']['records'][1]['record_type']
|
||||
assert 'value' in data['data']['records'][1]
|
||||
|
||||
assert 'Mon, 01 Jan 2018 00:00:00 GMT' == data['data']['records'][2]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][2]['user_id']
|
||||
assert 1 == data['data']['records'][2]['sport_id']
|
||||
assert 1 == data['data']['records'][2]['activity_id']
|
||||
assert 'LD' == data['data']['records'][2]['record_type']
|
||||
assert 'value' in data['data']['records'][2]
|
||||
|
||||
assert 'Mon, 01 Jan 2018 00:00:00 GMT' == data['data']['records'][3]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][3]['user_id']
|
||||
assert 1 == data['data']['records'][3]['sport_id']
|
||||
assert 1 == data['data']['records'][3]['activity_id']
|
||||
assert 'MS' == data['data']['records'][3]['record_type']
|
||||
assert 'value' in data['data']['records'][3]
|
||||
|
||||
|
||||
def test_get_records_no_activities_user_1(
|
||||
app, user_1, user_2, sport_1_cycling, sport_2_running,
|
||||
activity_cycling_user_2
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
'/api/records',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['records']) == 0
|
||||
|
||||
|
||||
def test_add_activity_zero_value(
|
||||
app, user_1, sport_1_cycling, sport_2_running
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
client.post(
|
||||
'/api/activities/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
sport_id=1,
|
||||
duration=0,
|
||||
activity_date='2018-05-14 14:05',
|
||||
distance=0,
|
||||
title='Activity test'
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
response = client.get(
|
||||
'/api/records',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['records']) == 0
|
||||
|
||||
|
||||
def test_get_records_after_activities_post_and_patch(
|
||||
app, user_1, sport_1_cycling
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
client.post(
|
||||
'/api/activities/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
sport_id=1,
|
||||
duration=3600,
|
||||
activity_date='2018-05-14 14:05',
|
||||
distance=7,
|
||||
title='Activity test 1'
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
response = client.get(
|
||||
'/api/records',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['records']) == 4
|
||||
|
||||
assert 'Mon, 14 May 2018 14:05:00 GMT' == data['data']['records'][0]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][0]['user_id']
|
||||
assert 1 == data['data']['records'][0]['sport_id']
|
||||
assert 1 == data['data']['records'][0]['activity_id']
|
||||
assert 'AS' == data['data']['records'][0]['record_type']
|
||||
assert 7.0 == data['data']['records'][0]['value']
|
||||
|
||||
assert 'Mon, 14 May 2018 14:05:00 GMT' == data['data']['records'][1]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][1]['user_id']
|
||||
assert 1 == data['data']['records'][1]['sport_id']
|
||||
assert 1 == data['data']['records'][1]['activity_id']
|
||||
assert 'FD' == data['data']['records'][1]['record_type']
|
||||
assert 7.0 == data['data']['records'][1]['value']
|
||||
|
||||
assert 'Mon, 14 May 2018 14:05:00 GMT' == data['data']['records'][2]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][2]['user_id']
|
||||
assert 1 == data['data']['records'][2]['sport_id']
|
||||
assert 1 == data['data']['records'][2]['activity_id']
|
||||
assert 'LD' == data['data']['records'][2]['record_type']
|
||||
assert '1:00:00' == data['data']['records'][2]['value']
|
||||
|
||||
assert 'Mon, 14 May 2018 14:05:00 GMT' == data['data']['records'][3]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][3]['user_id']
|
||||
assert 1 == data['data']['records'][3]['sport_id']
|
||||
assert 1 == data['data']['records'][3]['activity_id']
|
||||
assert 'MS' == data['data']['records'][3]['record_type']
|
||||
assert 7.0 == data['data']['records'][3]['value']
|
||||
|
||||
# Post activity with lower duration (same sport)
|
||||
# => 2 new records: Average speed and Max speed
|
||||
client.post(
|
||||
'/api/activities/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
sport_id=1,
|
||||
duration=3000,
|
||||
activity_date='2018-05-15 14:05',
|
||||
distance=7,
|
||||
title='Activity test 2'
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
response = client.get(
|
||||
'/api/records',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['records']) == 4
|
||||
|
||||
assert 'Tue, 15 May 2018 14:05:00 GMT' == data['data']['records'][0]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][0]['user_id']
|
||||
assert 1 == data['data']['records'][0]['sport_id']
|
||||
assert 2 == data['data']['records'][0]['activity_id']
|
||||
assert 'AS' == data['data']['records'][0]['record_type']
|
||||
assert 8.4 == data['data']['records'][0]['value']
|
||||
|
||||
assert 'Mon, 14 May 2018 14:05:00 GMT' == data['data']['records'][1]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][1]['user_id']
|
||||
assert 1 == data['data']['records'][1]['sport_id']
|
||||
assert 1 == data['data']['records'][1]['activity_id']
|
||||
assert 'FD' == data['data']['records'][1]['record_type']
|
||||
assert 7.0 == data['data']['records'][1]['value']
|
||||
|
||||
assert 'Mon, 14 May 2018 14:05:00 GMT' == data['data']['records'][2]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][2]['user_id']
|
||||
assert 1 == data['data']['records'][2]['sport_id']
|
||||
assert 1 == data['data']['records'][2]['activity_id']
|
||||
assert 'LD' == data['data']['records'][2]['record_type']
|
||||
assert '1:00:00' == data['data']['records'][2]['value']
|
||||
|
||||
assert 'Tue, 15 May 2018 14:05:00 GMT' == data['data']['records'][0]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][0]['user_id']
|
||||
assert 1 == data['data']['records'][0]['sport_id']
|
||||
assert 2 == data['data']['records'][0]['activity_id']
|
||||
assert 'MS' == data['data']['records'][3]['record_type']
|
||||
assert 8.4 == data['data']['records'][3]['value']
|
||||
|
||||
# Post activity with no new records
|
||||
client.post(
|
||||
'/api/activities/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
sport_id=1,
|
||||
duration=3500,
|
||||
activity_date='2018-05-16 14:05',
|
||||
distance=6.5,
|
||||
title='Activity test 3'
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
response = client.get(
|
||||
'/api/records',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['records']) == 4
|
||||
|
||||
assert 'Tue, 15 May 2018 14:05:00 GMT' == data['data']['records'][0]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][0]['user_id']
|
||||
assert 1 == data['data']['records'][0]['sport_id']
|
||||
assert 2 == data['data']['records'][0]['activity_id']
|
||||
assert 'AS' == data['data']['records'][0]['record_type']
|
||||
assert 8.4 == data['data']['records'][0]['value']
|
||||
|
||||
assert 'Mon, 14 May 2018 14:05:00 GMT' == data['data']['records'][1]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][1]['user_id']
|
||||
assert 1 == data['data']['records'][1]['sport_id']
|
||||
assert 1 == data['data']['records'][1]['activity_id']
|
||||
assert 'FD' == data['data']['records'][1]['record_type']
|
||||
assert 7.0 == data['data']['records'][1]['value']
|
||||
|
||||
assert 'Mon, 14 May 2018 14:05:00 GMT' == data['data']['records'][2]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][2]['user_id']
|
||||
assert 1 == data['data']['records'][2]['sport_id']
|
||||
assert 1 == data['data']['records'][2]['activity_id']
|
||||
assert 'LD' == data['data']['records'][2]['record_type']
|
||||
assert '1:00:00' == data['data']['records'][2]['value']
|
||||
|
||||
assert 'Tue, 15 May 2018 14:05:00 GMT' == data['data']['records'][0]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][0]['user_id']
|
||||
assert 1 == data['data']['records'][0]['sport_id']
|
||||
assert 2 == data['data']['records'][0]['activity_id']
|
||||
assert 'MS' == data['data']['records'][3]['record_type']
|
||||
assert 8.4 == data['data']['records'][3]['value']
|
||||
|
||||
# Edit last activity
|
||||
# 1 new record: Longest duration
|
||||
client.patch(
|
||||
'/api/activities/3',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
duration=4000,
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
response = client.get(
|
||||
'/api/records',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['records']) == 4
|
||||
|
||||
assert 'Tue, 15 May 2018 14:05:00 GMT' == data['data']['records'][0]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][0]['user_id']
|
||||
assert 1 == data['data']['records'][0]['sport_id']
|
||||
assert 2 == data['data']['records'][0]['activity_id']
|
||||
assert 'AS' == data['data']['records'][0]['record_type']
|
||||
assert 8.4 == data['data']['records'][0]['value']
|
||||
|
||||
assert 'Mon, 14 May 2018 14:05:00 GMT' == data['data']['records'][1]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][1]['user_id']
|
||||
assert 1 == data['data']['records'][1]['sport_id']
|
||||
assert 1 == data['data']['records'][1]['activity_id']
|
||||
assert 'FD' == data['data']['records'][1]['record_type']
|
||||
assert 7.0 == data['data']['records'][1]['value']
|
||||
|
||||
assert 'Wed, 16 May 2018 14:05:00 GMT' == data['data']['records'][2]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][2]['user_id']
|
||||
assert 1 == data['data']['records'][2]['sport_id']
|
||||
assert 3 == data['data']['records'][2]['activity_id']
|
||||
assert 'LD' == data['data']['records'][2]['record_type']
|
||||
assert '1:06:40' == data['data']['records'][2]['value']
|
||||
|
||||
assert 'Tue, 15 May 2018 14:05:00 GMT' == data['data']['records'][0]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][0]['user_id']
|
||||
assert 1 == data['data']['records'][0]['sport_id']
|
||||
assert 2 == data['data']['records'][0]['activity_id']
|
||||
assert 'MS' == data['data']['records'][3]['record_type']
|
||||
assert 8.4 == data['data']['records'][3]['value']
|
||||
|
||||
# delete activity 2 => AS and MS record update
|
||||
client.delete(
|
||||
'/api/activities/2',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
response = client.get(
|
||||
'/api/records',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['records']) == 4
|
||||
|
||||
assert 'Mon, 14 May 2018 14:05:00 GMT' == data['data']['records'][0]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][0]['user_id']
|
||||
assert 1 == data['data']['records'][0]['sport_id']
|
||||
assert 1 == data['data']['records'][0]['activity_id']
|
||||
assert 'AS' == data['data']['records'][0]['record_type']
|
||||
assert 7.0 == data['data']['records'][0]['value']
|
||||
|
||||
assert 'Mon, 14 May 2018 14:05:00 GMT' == data['data']['records'][1]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][1]['user_id']
|
||||
assert 1 == data['data']['records'][1]['sport_id']
|
||||
assert 1 == data['data']['records'][1]['activity_id']
|
||||
assert 'FD' == data['data']['records'][1]['record_type']
|
||||
assert 7.0 == data['data']['records'][1]['value']
|
||||
|
||||
assert 'Wed, 16 May 2018 14:05:00 GMT' == data['data']['records'][2]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][2]['user_id']
|
||||
assert 1 == data['data']['records'][2]['sport_id']
|
||||
assert 3 == data['data']['records'][2]['activity_id']
|
||||
assert 'LD' == data['data']['records'][2]['record_type']
|
||||
assert '1:06:40' == data['data']['records'][2]['value']
|
||||
|
||||
assert 'Mon, 14 May 2018 14:05:00 GMT' == data['data']['records'][3]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][3]['user_id']
|
||||
assert 1 == data['data']['records'][3]['sport_id']
|
||||
assert 1 == data['data']['records'][3]['activity_id']
|
||||
assert 'MS' == data['data']['records'][3]['record_type']
|
||||
assert 7.0 == data['data']['records'][3]['value']
|
||||
|
||||
# add an activity with the same data as activity 1 except with a later date
|
||||
# => no change in record
|
||||
client.post(
|
||||
'/api/activities/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
sport_id=1,
|
||||
duration=3600,
|
||||
activity_date='2018-05-20 14:05',
|
||||
distance=7,
|
||||
title='Activity test 4'
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
response = client.get(
|
||||
'/api/records',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['records']) == 4
|
||||
|
||||
assert 'Mon, 14 May 2018 14:05:00 GMT' == data['data']['records'][0]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][0]['user_id']
|
||||
assert 1 == data['data']['records'][0]['sport_id']
|
||||
assert 1 == data['data']['records'][0]['activity_id']
|
||||
assert 'AS' == data['data']['records'][0]['record_type']
|
||||
assert 7.0 == data['data']['records'][0]['value']
|
||||
|
||||
assert 'Mon, 14 May 2018 14:05:00 GMT' == data['data']['records'][1]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][1]['user_id']
|
||||
assert 1 == data['data']['records'][1]['sport_id']
|
||||
assert 1 == data['data']['records'][1]['activity_id']
|
||||
assert 'FD' == data['data']['records'][1]['record_type']
|
||||
assert 7.0 == data['data']['records'][1]['value']
|
||||
|
||||
assert 'Wed, 16 May 2018 14:05:00 GMT' == data['data']['records'][2]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][2]['user_id']
|
||||
assert 1 == data['data']['records'][2]['sport_id']
|
||||
assert 3 == data['data']['records'][2]['activity_id']
|
||||
assert 'LD' == data['data']['records'][2]['record_type']
|
||||
assert '1:06:40' == data['data']['records'][2]['value']
|
||||
|
||||
assert 'Mon, 14 May 2018 14:05:00 GMT' == data['data']['records'][3]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][3]['user_id']
|
||||
assert 1 == data['data']['records'][3]['sport_id']
|
||||
assert 1 == data['data']['records'][3]['activity_id']
|
||||
assert 'MS' == data['data']['records'][3]['record_type']
|
||||
assert 7.0 == data['data']['records'][3]['value']
|
||||
|
||||
# add an activity with the same data as activity 1 except with
|
||||
# an earlier date
|
||||
# => record update (activity 5 replace activity 1)
|
||||
|
||||
client.post(
|
||||
'/api/activities/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
sport_id=1,
|
||||
duration=3600,
|
||||
activity_date='2018-05-14 08:05',
|
||||
distance=7,
|
||||
title='Activity test 5'
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
response = client.get(
|
||||
'/api/records',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['records']) == 4
|
||||
|
||||
assert 'Mon, 14 May 2018 08:05:00 GMT' == data['data']['records'][0]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][0]['user_id']
|
||||
assert 1 == data['data']['records'][0]['sport_id']
|
||||
assert 5 == data['data']['records'][0]['activity_id']
|
||||
assert 'AS' == data['data']['records'][0]['record_type']
|
||||
assert 7.0 == data['data']['records'][0]['value']
|
||||
|
||||
assert 'Mon, 14 May 2018 08:05:00 GMT' == data['data']['records'][1]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][1]['user_id']
|
||||
assert 1 == data['data']['records'][1]['sport_id']
|
||||
assert 5 == data['data']['records'][1]['activity_id']
|
||||
assert 'FD' == data['data']['records'][1]['record_type']
|
||||
assert 7.0 == data['data']['records'][1]['value']
|
||||
|
||||
assert 'Wed, 16 May 2018 14:05:00 GMT' == data['data']['records'][2]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][2]['user_id']
|
||||
assert 1 == data['data']['records'][2]['sport_id']
|
||||
assert 3 == data['data']['records'][2]['activity_id']
|
||||
assert 'LD' == data['data']['records'][2]['record_type']
|
||||
assert '1:06:40' == data['data']['records'][2]['value']
|
||||
|
||||
assert 'Mon, 14 May 2018 08:05:00 GMT' == data['data']['records'][3]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][3]['user_id']
|
||||
assert 1 == data['data']['records'][3]['sport_id']
|
||||
assert 5 == data['data']['records'][3]['activity_id']
|
||||
assert 'MS' == data['data']['records'][3]['record_type']
|
||||
assert 7.0 == data['data']['records'][3]['value']
|
||||
|
||||
# delete all activities - no more records
|
||||
client.delete(
|
||||
'/api/activities/1',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
client.delete(
|
||||
'/api/activities/3',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
client.delete(
|
||||
'/api/activities/4',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
client.delete(
|
||||
'/api/activities/5',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
response = client.get(
|
||||
'/api/records',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['records']) == 0
|
||||
|
||||
|
||||
def test_get_records_after_sport_change(
|
||||
app, user_1, sport_1_cycling, sport_2_running
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
client.post(
|
||||
'/api/activities/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
sport_id=1,
|
||||
duration=3600,
|
||||
activity_date='2018-05-14 14:05',
|
||||
distance=7,
|
||||
title='Activity test 1'
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
client.post(
|
||||
'/api/activities/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
sport_id=2,
|
||||
duration=3600,
|
||||
activity_date='2018-05-16 16:05',
|
||||
distance=20,
|
||||
title='Activity test 2'
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
client.post(
|
||||
'/api/activities/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
sport_id=1,
|
||||
duration=3000,
|
||||
activity_date='2018-05-17 17:05',
|
||||
distance=3,
|
||||
title='Activity test 3'
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
client.post(
|
||||
'/api/activities/no_gpx',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
sport_id=2,
|
||||
duration=3000,
|
||||
activity_date='2018-05-18 18:05',
|
||||
distance=10,
|
||||
title='Activity test 4'
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
response = client.get(
|
||||
'/api/records',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['records']) == 8
|
||||
|
||||
assert 'Mon, 14 May 2018 14:05:00 GMT' == data['data']['records'][0]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][0]['user_id']
|
||||
assert 1 == data['data']['records'][0]['sport_id']
|
||||
assert 1 == data['data']['records'][0]['activity_id']
|
||||
assert 'AS' == data['data']['records'][0]['record_type']
|
||||
assert 7.0 == data['data']['records'][0]['value']
|
||||
|
||||
assert 'Mon, 14 May 2018 14:05:00 GMT' == data['data']['records'][1]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][1]['user_id']
|
||||
assert 1 == data['data']['records'][1]['sport_id']
|
||||
assert 1 == data['data']['records'][1]['activity_id']
|
||||
assert 'FD' == data['data']['records'][1]['record_type']
|
||||
assert 7.0 == data['data']['records'][1]['value']
|
||||
|
||||
assert 'Mon, 14 May 2018 14:05:00 GMT' == data['data']['records'][2]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][2]['user_id']
|
||||
assert 1 == data['data']['records'][2]['sport_id']
|
||||
assert 1 == data['data']['records'][2]['activity_id']
|
||||
assert 'LD' == data['data']['records'][2]['record_type']
|
||||
assert '1:00:00' == data['data']['records'][2]['value']
|
||||
|
||||
assert 'Mon, 14 May 2018 14:05:00 GMT' == data['data']['records'][3]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][3]['user_id']
|
||||
assert 1 == data['data']['records'][3]['sport_id']
|
||||
assert 1 == data['data']['records'][3]['activity_id']
|
||||
assert 'MS' == data['data']['records'][3]['record_type']
|
||||
assert 7.0 == data['data']['records'][3]['value']
|
||||
|
||||
assert 'Wed, 16 May 2018 16:05:00 GMT' == data['data']['records'][4]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][4]['user_id']
|
||||
assert 2 == data['data']['records'][4]['sport_id']
|
||||
assert 2 == data['data']['records'][4]['activity_id']
|
||||
assert 'AS' == data['data']['records'][4]['record_type']
|
||||
assert 20.0 == data['data']['records'][4]['value']
|
||||
|
||||
assert 'Wed, 16 May 2018 16:05:00 GMT' == data['data']['records'][5]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][5]['user_id']
|
||||
assert 2 == data['data']['records'][5]['sport_id']
|
||||
assert 2 == data['data']['records'][5]['activity_id']
|
||||
assert 'FD' == data['data']['records'][5]['record_type']
|
||||
assert 20.0 == data['data']['records'][5]['value']
|
||||
|
||||
assert 'Wed, 16 May 2018 16:05:00 GMT' == data['data']['records'][6]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][6]['user_id']
|
||||
assert 2 == data['data']['records'][6]['sport_id']
|
||||
assert 2 == data['data']['records'][6]['activity_id']
|
||||
assert 'LD' == data['data']['records'][6]['record_type']
|
||||
assert '1:00:00' == data['data']['records'][6]['value']
|
||||
|
||||
assert 'Wed, 16 May 2018 16:05:00 GMT' == data['data']['records'][7]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][7]['user_id']
|
||||
assert 2 == data['data']['records'][7]['sport_id']
|
||||
assert 2 == data['data']['records'][7]['activity_id']
|
||||
assert 'MS' == data['data']['records'][7]['record_type']
|
||||
assert 20.0 == data['data']['records'][7]['value']
|
||||
|
||||
client.patch(
|
||||
'/api/activities/2',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
sport_id=1,
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
response = client.get(
|
||||
'/api/records',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert len(data['data']['records']) == 8
|
||||
|
||||
assert 'Wed, 16 May 2018 16:05:00 GMT' == data['data']['records'][0]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][0]['user_id']
|
||||
assert 1 == data['data']['records'][0]['sport_id']
|
||||
assert 2 == data['data']['records'][0]['activity_id']
|
||||
assert 'AS' == data['data']['records'][0]['record_type']
|
||||
assert 20.0 == data['data']['records'][0]['value']
|
||||
|
||||
assert 'Wed, 16 May 2018 16:05:00 GMT' == data['data']['records'][1]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][1]['user_id']
|
||||
assert 1 == data['data']['records'][1]['sport_id']
|
||||
assert 2 == data['data']['records'][1]['activity_id']
|
||||
assert 'FD' == data['data']['records'][1]['record_type']
|
||||
assert 20.0 == data['data']['records'][1]['value']
|
||||
|
||||
assert 'Mon, 14 May 2018 14:05:00 GMT' == data['data']['records'][2]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][2]['user_id']
|
||||
assert 1 == data['data']['records'][2]['sport_id']
|
||||
assert 1 == data['data']['records'][2]['activity_id']
|
||||
assert 'LD' == data['data']['records'][2]['record_type']
|
||||
assert '1:00:00' == data['data']['records'][2]['value']
|
||||
|
||||
assert 'Wed, 16 May 2018 16:05:00 GMT' == data['data']['records'][3]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][3]['user_id']
|
||||
assert 1 == data['data']['records'][3]['sport_id']
|
||||
assert 2 == data['data']['records'][3]['activity_id']
|
||||
assert 'MS' == data['data']['records'][3]['record_type']
|
||||
assert 20.0 == data['data']['records'][3]['value']
|
||||
|
||||
assert 'Fri, 18 May 2018 18:05:00 GMT' == data['data']['records'][4]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][4]['user_id']
|
||||
assert 2 == data['data']['records'][4]['sport_id']
|
||||
assert 4 == data['data']['records'][4]['activity_id']
|
||||
assert 'AS' == data['data']['records'][4]['record_type']
|
||||
assert 12.0 == data['data']['records'][4]['value']
|
||||
|
||||
assert 'Fri, 18 May 2018 18:05:00 GMT' == data['data']['records'][5]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][5]['user_id']
|
||||
assert 2 == data['data']['records'][5]['sport_id']
|
||||
assert 4 == data['data']['records'][5]['activity_id']
|
||||
assert 'FD' == data['data']['records'][5]['record_type']
|
||||
assert 10.0 == data['data']['records'][5]['value']
|
||||
|
||||
assert 'Fri, 18 May 2018 18:05:00 GMT' == data['data']['records'][6]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][6]['user_id']
|
||||
assert 2 == data['data']['records'][6]['sport_id']
|
||||
assert 4 == data['data']['records'][6]['activity_id']
|
||||
assert 'LD' == data['data']['records'][6]['record_type']
|
||||
assert '0:50:00' == data['data']['records'][6]['value']
|
||||
|
||||
assert 'Fri, 18 May 2018 18:05:00 GMT' == data['data']['records'][7]['activity_date'] # noqa
|
||||
assert 1 == data['data']['records'][7]['user_id']
|
||||
assert 2 == data['data']['records'][7]['sport_id']
|
||||
assert 4 == data['data']['records'][7]['activity_id']
|
||||
assert 'MS' == data['data']['records'][7]['record_type']
|
||||
assert 12.0 == data['data']['records'][7]['value']
|
140
fittrackee_api/fittrackee_api/tests/test_records_model.py
Normal file
140
fittrackee_api/fittrackee_api/tests/test_records_model.py
Normal file
@ -0,0 +1,140 @@
|
||||
import datetime
|
||||
|
||||
from fittrackee_api.activities.models import Record
|
||||
|
||||
|
||||
def test_record_model(
|
||||
app, user_1, sport_1_cycling, activity_cycling_user_1
|
||||
):
|
||||
record_ld = Record.query.filter_by(
|
||||
user_id=activity_cycling_user_1.user_id,
|
||||
sport_id=activity_cycling_user_1.sport_id,
|
||||
record_type='LD',
|
||||
).first()
|
||||
assert 1 == record_ld.user_id
|
||||
assert 1 == record_ld.sport_id
|
||||
assert 1 == record_ld.activity_id
|
||||
assert 'LD' == record_ld.record_type
|
||||
assert '2018-01-01 00:00:00' == str(record_ld.activity_date)
|
||||
assert '<Record Cycling - LD - 2018-01-01>' == str(record_ld)
|
||||
|
||||
record_serialize = record_ld.serialize()
|
||||
assert 'id' in record_serialize
|
||||
assert 'user_id' in record_serialize
|
||||
assert 'sport_id' in record_serialize
|
||||
assert 'activity_id' in record_serialize
|
||||
assert 'record_type' in record_serialize
|
||||
assert 'activity_date' in record_serialize
|
||||
assert 'value' in record_serialize
|
||||
|
||||
|
||||
def test_record_model_none_value(
|
||||
app, user_1, sport_1_cycling, activity_cycling_user_1
|
||||
):
|
||||
record_ld = Record.query.filter_by(
|
||||
user_id=activity_cycling_user_1.user_id,
|
||||
sport_id=activity_cycling_user_1.sport_id,
|
||||
record_type='LD',
|
||||
).first()
|
||||
record_ld.value = None
|
||||
assert 1 == record_ld.user_id
|
||||
assert 1 == record_ld.sport_id
|
||||
assert 1 == record_ld.activity_id
|
||||
assert 'LD' == record_ld.record_type
|
||||
assert '2018-01-01 00:00:00' == str(record_ld.activity_date)
|
||||
assert '<Record Cycling - LD - 2018-01-01>' == str(record_ld)
|
||||
assert record_ld.value is None
|
||||
|
||||
record_serialize = record_ld.serialize()
|
||||
assert record_serialize['value'] is None
|
||||
|
||||
|
||||
def test_add_as_records(
|
||||
app, user_1, sport_1_cycling, activity_cycling_user_1
|
||||
):
|
||||
record_as = Record.query.filter_by(
|
||||
user_id=activity_cycling_user_1.user_id,
|
||||
sport_id=activity_cycling_user_1.sport_id,
|
||||
record_type='AS',
|
||||
).first()
|
||||
|
||||
assert isinstance(record_as.value, float)
|
||||
assert record_as.value == 10.0
|
||||
assert record_as._value == 1000
|
||||
|
||||
record_serialize = record_as.serialize()
|
||||
assert record_serialize.get('value') == 10.0
|
||||
assert isinstance(record_serialize.get('value'), float)
|
||||
|
||||
|
||||
def test_add_fd_records(
|
||||
app, user_1, sport_1_cycling, activity_cycling_user_1
|
||||
):
|
||||
record_fd = Record.query.filter_by(
|
||||
user_id=activity_cycling_user_1.user_id,
|
||||
sport_id=activity_cycling_user_1.sport_id,
|
||||
record_type='FD',
|
||||
).first()
|
||||
|
||||
assert isinstance(record_fd.value, float)
|
||||
assert record_fd.value == 10.0
|
||||
assert record_fd._value == 10000
|
||||
|
||||
record_serialize = record_fd.serialize()
|
||||
assert record_serialize.get('value') == 10.0
|
||||
assert isinstance(record_serialize.get('value'), float)
|
||||
|
||||
|
||||
def test_add_ld_records(
|
||||
app, user_1, sport_1_cycling, activity_cycling_user_1
|
||||
):
|
||||
record_ld = Record.query.filter_by(
|
||||
user_id=activity_cycling_user_1.user_id,
|
||||
sport_id=activity_cycling_user_1.sport_id,
|
||||
record_type='LD',
|
||||
).first()
|
||||
|
||||
assert isinstance(record_ld.value, datetime.timedelta)
|
||||
assert str(record_ld.value) == '0:17:04'
|
||||
assert record_ld._value == 1024
|
||||
|
||||
record_serialize = record_ld.serialize()
|
||||
assert record_serialize.get('value') == '0:17:04'
|
||||
assert isinstance(record_serialize.get('value'), str)
|
||||
|
||||
|
||||
def test_add_ld_records_zero(
|
||||
app, user_1, sport_1_cycling, activity_cycling_user_1
|
||||
):
|
||||
record_ld = Record.query.filter_by(
|
||||
user_id=activity_cycling_user_1.user_id,
|
||||
sport_id=activity_cycling_user_1.sport_id,
|
||||
record_type='LD',
|
||||
).first()
|
||||
record_ld.value = datetime.timedelta(seconds=0)
|
||||
|
||||
assert isinstance(record_ld.value, datetime.timedelta)
|
||||
assert str(record_ld.value) == '0:00:00'
|
||||
assert record_ld._value == 0
|
||||
|
||||
record_serialize = record_ld.serialize()
|
||||
assert record_serialize.get('value') == '0:00:00'
|
||||
assert isinstance(record_serialize.get('value'), str)
|
||||
|
||||
|
||||
def test_add_ms_records_no_value(
|
||||
app, user_1, sport_1_cycling, activity_cycling_user_1
|
||||
):
|
||||
record_ms = Record.query.filter_by(
|
||||
user_id=activity_cycling_user_1.user_id,
|
||||
sport_id=activity_cycling_user_1.sport_id,
|
||||
record_type='MS',
|
||||
).first()
|
||||
|
||||
assert isinstance(record_ms.value, float)
|
||||
assert record_ms.value == 10.0
|
||||
assert record_ms._value == 1000
|
||||
|
||||
record_serialize = record_ms.serialize()
|
||||
assert record_serialize.get('value') == 10.0
|
||||
assert isinstance(record_serialize.get('value'), float)
|
404
fittrackee_api/fittrackee_api/tests/test_sports_api.py
Normal file
404
fittrackee_api/fittrackee_api/tests/test_sports_api.py
Normal file
@ -0,0 +1,404 @@
|
||||
import json
|
||||
|
||||
expected_sport_1_cycling_result = {
|
||||
'id': 1,
|
||||
'label': 'Cycling',
|
||||
'img': None,
|
||||
'_can_be_deleted': True
|
||||
}
|
||||
|
||||
expected_sport_2_running_result = {
|
||||
'id': 2,
|
||||
'label': 'Running',
|
||||
'img': None,
|
||||
'_can_be_deleted': True
|
||||
}
|
||||
|
||||
|
||||
def test_get_all_sports(app, user_1, sport_1_cycling, sport_2_running):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
'/api/sports',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
|
||||
assert len(data['data']['sports']) == 2
|
||||
assert data['data']['sports'][0] == expected_sport_1_cycling_result
|
||||
assert data['data']['sports'][1] == expected_sport_2_running_result
|
||||
|
||||
|
||||
def test_get_a_sport(app, user_1, sport_1_cycling):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
'/api/sports/1',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
|
||||
assert len(data['data']['sports']) == 1
|
||||
assert data['data']['sports'][0] == expected_sport_1_cycling_result
|
||||
|
||||
|
||||
def test_get_a_sport_invalid(app, user_1):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
'/api/sports/1',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 404
|
||||
assert 'not found' in data['status']
|
||||
assert len(data['data']['sports']) == 0
|
||||
|
||||
|
||||
def test_add_a_sport(app, user_1_admin):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='admin@example.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/sports',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
label='Cycling'
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 201
|
||||
assert 'created' in data['status']
|
||||
|
||||
assert len(data['data']['sports']) == 1
|
||||
assert data['data']['sports'][0] == expected_sport_1_cycling_result
|
||||
|
||||
|
||||
def test_add_a_sport_not_admin(app, user_1):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/sports',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
label='surfing'
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 403
|
||||
assert 'created' not in data['status']
|
||||
assert 'error' in data['status']
|
||||
assert 'You do not have permissions.' in data['message']
|
||||
|
||||
|
||||
def test_add_a_sport_invalid_payload(app, user_1_admin):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='admin@example.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.post(
|
||||
'/api/sports',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict()),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 400
|
||||
assert 'error' in data['status']
|
||||
assert 'Invalid payload.' in data['message']
|
||||
|
||||
|
||||
def test_update_a_sport(app, user_1_admin, sport_1_cycling):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='admin@example.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.patch(
|
||||
'/api/sports/1',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
label='cycling updated'
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
|
||||
assert len(data['data']['sports']) == 1
|
||||
assert 'cycling updated' in data['data']['sports'][0]['label']
|
||||
|
||||
|
||||
def test_update_a_sport_not_admin(app, user_1, sport_1_cycling):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.patch(
|
||||
'/api/sports/1',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
label='cycling updated'
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 403
|
||||
assert 'success' not in data['status']
|
||||
assert 'error' in data['status']
|
||||
assert 'You do not have permissions.' in data['message']
|
||||
|
||||
|
||||
def test_update_a_sport_invalid_payload(app, user_1_admin):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='admin@example.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.patch(
|
||||
'/api/sports/1',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict()),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 400
|
||||
assert 'error' in data['status']
|
||||
assert 'Invalid payload.' in data['message']
|
||||
|
||||
|
||||
def test_update_a_sport_invalid_id(app, user_1_admin):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='admin@example.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.patch(
|
||||
'/api/sports/1',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict(
|
||||
label='cycling updated'
|
||||
)),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 404
|
||||
assert 'not found' in data['status']
|
||||
assert len(data['data']['sports']) == 0
|
||||
|
||||
|
||||
def test_delete_a_sport(app, user_1_admin, sport_1_cycling):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='admin@example.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.delete(
|
||||
'/api/sports/1',
|
||||
content_type='application/json',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
assert response.status_code == 204
|
||||
|
||||
|
||||
def test_delete_a_sport_not_admin(app, user_1, sport_1_cycling):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.delete(
|
||||
'/api/sports/1',
|
||||
content_type='application/json',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
|
||||
data = json.loads(response.data.decode())
|
||||
assert response.status_code == 403
|
||||
assert 'error' in data['status']
|
||||
assert 'You do not have permissions.' in data['message']
|
||||
|
||||
|
||||
def test_delete_a_sport_invalid_id(app, user_1_admin):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='admin@example.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.delete(
|
||||
'/api/sports/1',
|
||||
content_type='application/json',
|
||||
data=json.dumps(dict()),
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 404
|
||||
assert 'not found' in data['status']
|
||||
assert len(data['data']['sports']) == 0
|
||||
|
||||
|
||||
def test_delete_a_sport_with_an_activity(
|
||||
app, user_1_admin, sport_1_cycling, activity_cycling_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='admin@example.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.delete(
|
||||
'/api/sports/1',
|
||||
content_type='application/json',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 500
|
||||
assert 'error' in data['status']
|
||||
assert 'Error. Associated activities exist.' in data['message']
|
22
fittrackee_api/fittrackee_api/tests/test_sports_model.py
Normal file
22
fittrackee_api/fittrackee_api/tests/test_sports_model.py
Normal file
@ -0,0 +1,22 @@
|
||||
def test_sport_model(app, sport_1_cycling):
|
||||
assert 1 == sport_1_cycling.id
|
||||
assert 'Cycling' == sport_1_cycling.label
|
||||
assert '<Sport \'Cycling\'>' == str(sport_1_cycling)
|
||||
|
||||
serialized_sport = sport_1_cycling.serialize()
|
||||
assert 1 == serialized_sport['id']
|
||||
assert 'Cycling' == serialized_sport['label']
|
||||
assert serialized_sport['_can_be_deleted'] is True
|
||||
|
||||
|
||||
def test_sport_model_with_activity(
|
||||
app, sport_1_cycling, user_1, activity_cycling_user_1
|
||||
):
|
||||
assert 1 == sport_1_cycling.id
|
||||
assert 'Cycling' == sport_1_cycling.label
|
||||
assert '<Sport \'Cycling\'>' == str(sport_1_cycling)
|
||||
|
||||
serialized_sport = sport_1_cycling.serialize()
|
||||
assert 1 == serialized_sport['id']
|
||||
assert 'Cycling' == serialized_sport['label']
|
||||
assert serialized_sport['_can_be_deleted'] is False
|
874
fittrackee_api/fittrackee_api/tests/test_stats_api.py
Normal file
874
fittrackee_api/fittrackee_api/tests/test_stats_api.py
Normal file
@ -0,0 +1,874 @@
|
||||
import json
|
||||
|
||||
|
||||
def test_get_stats_by_time_no_activities(app, user_1):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
f'/api/stats/{user_1.id}/by_time',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert data['data']['statistics'] == {}
|
||||
|
||||
|
||||
def test_get_stats_by_time_no_user(app, user_1):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
f'/api/stats/1000/by_time',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 404
|
||||
assert 'not found' in data['status']
|
||||
assert 'User does not exist.' in data['message']
|
||||
|
||||
|
||||
def test_get_stats_by_time_all_activities_error(
|
||||
app, user_1, sport_1_cycling, sport_2_running,
|
||||
seven_activities_user_1, activity_running_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
f'/api/stats/{user_1.id}/by_time?from="2018-04-01&to=2018-04-30',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 500
|
||||
assert 'error' in data['status']
|
||||
assert 'Error. Please try again or contact the administrator.' \
|
||||
in data['message']
|
||||
|
||||
|
||||
def test_get_stats_by_time_all_activities_invalid_period(
|
||||
app, user_1, sport_1_cycling, sport_2_running,
|
||||
seven_activities_user_1, activity_running_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
f'/api/stats/{user_1.id}/by_time?from=2018-04-01&to=2018-04-30&time=day', # noqa
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 400
|
||||
assert 'fail' in data['status']
|
||||
assert 'Invalid time period.' in data['message']
|
||||
|
||||
|
||||
def test_get_stats_by_time_all_activities(
|
||||
app, user_1, sport_1_cycling, sport_2_running,
|
||||
seven_activities_user_1, activity_running_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
f'/api/stats/{user_1.id}/by_time',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert data['data']['statistics'] == \
|
||||
{
|
||||
'2017':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 2,
|
||||
'total_distance': 15.0,
|
||||
'total_duration': 4480
|
||||
}
|
||||
},
|
||||
'2018':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 5,
|
||||
'total_distance': 39.0,
|
||||
'total_duration': 11624
|
||||
},
|
||||
'2':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 12.0,
|
||||
'total_duration': 6000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_get_stats_by_time_all_activities_april_2018(
|
||||
app, user_1, sport_1_cycling, sport_2_running,
|
||||
seven_activities_user_1, activity_running_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
f'/api/stats/{user_1.id}/by_time?from=2018-04-01&to=2018-04-30',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert data['data']['statistics'] == \
|
||||
{
|
||||
'2018':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 8.0,
|
||||
'total_duration': 6000
|
||||
},
|
||||
'2':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 12.0,
|
||||
'total_duration': 6000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_get_stats_by_year_all_activities(
|
||||
app, user_1, sport_1_cycling, sport_2_running,
|
||||
seven_activities_user_1, activity_running_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
f'/api/stats/{user_1.id}/by_time?time=year',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert data['data']['statistics'] == \
|
||||
{
|
||||
'2017':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 2,
|
||||
'total_distance': 15.0,
|
||||
'total_duration': 4480
|
||||
}
|
||||
},
|
||||
'2018':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 5,
|
||||
'total_distance': 39.0,
|
||||
'total_duration': 11624
|
||||
},
|
||||
'2':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 12.0,
|
||||
'total_duration': 6000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_get_stats_by_year_all_activities_april_2018(
|
||||
app, user_1, sport_1_cycling, sport_2_running,
|
||||
seven_activities_user_1, activity_running_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
f'/api/stats/{user_1.id}/by_time?from=2018-04-01&to=2018-04-30&time=year', # noqa
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert data['data']['statistics'] == \
|
||||
{
|
||||
'2018':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 8.0,
|
||||
'total_duration': 6000
|
||||
},
|
||||
'2':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 12.0,
|
||||
'total_duration': 6000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_get_stats_by_month_all_activities(
|
||||
app, user_1, sport_1_cycling, sport_2_running,
|
||||
seven_activities_user_1, activity_running_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
f'/api/stats/{user_1.id}/by_time?time=month',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert data['data']['statistics'] == \
|
||||
{
|
||||
'2017-03':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 5.0,
|
||||
'total_duration': 1024
|
||||
}
|
||||
},
|
||||
'2017-06':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 10.0,
|
||||
'total_duration': 3456
|
||||
}
|
||||
},
|
||||
'2018-01':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 10.0,
|
||||
'total_duration': 1024
|
||||
}
|
||||
},
|
||||
'2018-02':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 2,
|
||||
'total_distance': 11.0,
|
||||
'total_duration': 1600
|
||||
}
|
||||
},
|
||||
'2018-04':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 8.0,
|
||||
'total_duration': 6000
|
||||
},
|
||||
'2':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 12.0,
|
||||
'total_duration': 6000
|
||||
}
|
||||
},
|
||||
'2018-05':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 10.0,
|
||||
'total_duration': 3000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_get_stats_by_month_all_activities_april_2018(
|
||||
app, user_1, sport_1_cycling, sport_2_running,
|
||||
seven_activities_user_1, activity_running_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
f'/api/stats/{user_1.id}/by_time?from=2018-04-01&to=2018-04-30&time=month', # noqa
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert data['data']['statistics'] == \
|
||||
{
|
||||
'2018-04':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 8.0,
|
||||
'total_duration': 6000
|
||||
},
|
||||
'2':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 12.0,
|
||||
'total_duration': 6000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_get_stats_by_week_all_activities(
|
||||
app, user_1, sport_1_cycling, sport_2_running,
|
||||
seven_activities_user_1, activity_running_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
f'/api/stats/{user_1.id}/by_time?time=week',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert data['data']['statistics'] == \
|
||||
{
|
||||
'2017-03-19':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 5.0,
|
||||
'total_duration': 1024
|
||||
}
|
||||
},
|
||||
'2017-05-28':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 10.0,
|
||||
'total_duration': 3456
|
||||
}
|
||||
},
|
||||
'2017-12-31':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 10.0,
|
||||
'total_duration': 1024
|
||||
}
|
||||
},
|
||||
'2018-02-18':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 2,
|
||||
'total_distance': 11.0,
|
||||
'total_duration': 1600
|
||||
}
|
||||
},
|
||||
'2018-03-25':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 8.0,
|
||||
'total_duration': 6000
|
||||
},
|
||||
'2':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 12.0,
|
||||
'total_duration': 6000
|
||||
}
|
||||
},
|
||||
'2018-05-06':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 10.0,
|
||||
'total_duration': 3000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_get_stats_by_week_all_activities_week_13(
|
||||
app, user_1, sport_1_cycling, sport_2_running,
|
||||
seven_activities_user_1, activity_running_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
f'/api/stats/{user_1.id}/by_time?from=2018-04-01&to=2018-04-30&time=week', # noqa
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert data['data']['statistics'] == \
|
||||
{
|
||||
'2018-03-25':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 8.0,
|
||||
'total_duration': 6000
|
||||
},
|
||||
'2':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 12.0,
|
||||
'total_duration': 6000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_get_stats_by_weekm_all_activities(
|
||||
app, user_1, sport_1_cycling, sport_2_running,
|
||||
seven_activities_user_1, activity_running_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
f'/api/stats/{user_1.id}/by_time?time=weekm',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert data['data']['statistics'] == \
|
||||
{
|
||||
'2017-03-20':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 5.0,
|
||||
'total_duration': 1024
|
||||
}
|
||||
},
|
||||
'2017-05-29':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 10.0,
|
||||
'total_duration': 3456
|
||||
}
|
||||
},
|
||||
'2018-01-01':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 10.0,
|
||||
'total_duration': 1024
|
||||
}
|
||||
},
|
||||
'2018-02-19':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 2,
|
||||
'total_distance': 11.0,
|
||||
'total_duration': 1600
|
||||
}
|
||||
},
|
||||
'2018-03-26':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 8.0,
|
||||
'total_duration': 6000
|
||||
},
|
||||
'2':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 12.0,
|
||||
'total_duration': 6000
|
||||
}
|
||||
},
|
||||
'2018-05-07':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 10.0,
|
||||
'total_duration': 3000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_get_stats_by_weekm_all_activities_week_13(
|
||||
app, user_1, sport_1_cycling, sport_2_running,
|
||||
seven_activities_user_1, activity_running_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
f'/api/stats/{user_1.id}/by_time?from=2018-04-01&to=2018-04-30&time=weekm', # noqa
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert data['data']['statistics'] == \
|
||||
{
|
||||
'2018-03-26':
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 8.0,
|
||||
'total_duration': 6000
|
||||
},
|
||||
'2':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 12.0,
|
||||
'total_duration': 6000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_get_stats_by_sport_all_activities(
|
||||
app, user_1, sport_1_cycling, sport_2_running,
|
||||
seven_activities_user_1, activity_running_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
f'/api/stats/{user_1.id}/by_sport',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert data['data']['statistics'] == \
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 7,
|
||||
'total_distance': 54.0,
|
||||
'total_duration': 16104
|
||||
},
|
||||
'2':
|
||||
{
|
||||
'nb_activities': 1,
|
||||
'total_distance': 12.0,
|
||||
'total_duration': 6000
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_get_stats_by_sport_all_activities_sport_1(
|
||||
app, user_1, sport_1_cycling, sport_2_running,
|
||||
seven_activities_user_1, activity_running_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
f'/api/stats/{user_1.id}/by_sport?sport_id=1',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
assert data['data']['statistics'] == \
|
||||
{
|
||||
'1':
|
||||
{
|
||||
'nb_activities': 7,
|
||||
'total_distance': 54.0,
|
||||
'total_duration': 16104
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_get_stats_by_sport_all_activities_invalid_user(
|
||||
app, user_1, sport_1_cycling, sport_2_running,
|
||||
seven_activities_user_1, activity_running_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
f'/api/stats/1000/by_sport?sport_id=1',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 404
|
||||
assert 'not found' in data['status']
|
||||
assert 'User does not exist.' in data['message']
|
||||
|
||||
|
||||
def test_get_stats_by_sport_all_activities_invalid_sport(
|
||||
app, user_1, sport_1_cycling, sport_2_running,
|
||||
seven_activities_user_1, activity_running_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
f'/api/stats/{user_1.id}/by_sport?sport_id=999',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 404
|
||||
assert 'not found' in data['status']
|
||||
assert 'Sport does not exist.' in data['message']
|
||||
|
||||
|
||||
def test_get_stats_by_sport_all_activities_error(
|
||||
app, user_1, sport_1_cycling, sport_2_running,
|
||||
seven_activities_user_1, activity_running_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
'/api/auth/login',
|
||||
data=json.dumps(dict(
|
||||
email='test@test.com',
|
||||
password='12345678'
|
||||
)),
|
||||
content_type='application/json'
|
||||
)
|
||||
response = client.get(
|
||||
f'/api/stats/{user_1.id}/by_sport?sport_id="999',
|
||||
headers=dict(
|
||||
Authorization='Bearer ' + json.loads(
|
||||
resp_login.data.decode()
|
||||
)['auth_token']
|
||||
)
|
||||
)
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 500
|
||||
assert 'error' in data['status']
|
||||
assert 'Error. Please try again or contact the administrator.' \
|
||||
in data['message']
|
127
fittrackee_api/fittrackee_api/tests/test_users_api.py
Normal file
127
fittrackee_api/fittrackee_api/tests/test_users_api.py
Normal file
@ -0,0 +1,127 @@
|
||||
import json
|
||||
|
||||
from fittrackee_api.users.models import User
|
||||
|
||||
|
||||
def test_ping(app):
|
||||
""" => Ensure the /ping route behaves correctly."""
|
||||
client = app.test_client()
|
||||
response = client.get('/api/ping')
|
||||
data = json.loads(response.data.decode())
|
||||
assert response.status_code == 200
|
||||
assert 'pong' in data['message']
|
||||
assert 'success' in data['status']
|
||||
|
||||
|
||||
def test_single_user(app, user_1):
|
||||
"""=> Get single user details"""
|
||||
client = app.test_client()
|
||||
|
||||
response = client.get(f'/api/users/{user_1.id}')
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert data['status'] == 'success'
|
||||
assert data['data'] is not None
|
||||
assert data['data']['username'] == 'test'
|
||||
assert data['data']['email'] == 'test@test.com'
|
||||
assert data['data']['created_at']
|
||||
assert not data['data']['admin']
|
||||
assert data['data']['first_name'] is None
|
||||
assert data['data']['last_name'] is None
|
||||
assert data['data']['birth_date'] is None
|
||||
assert data['data']['bio'] is None
|
||||
assert data['data']['location'] is None
|
||||
assert data['data']['nb_activities'] == 0
|
||||
assert data['data']['nb_sports'] == 0
|
||||
assert data['data']['total_distance'] == 0
|
||||
assert data['data']['total_duration'] == '0:00:00'
|
||||
|
||||
|
||||
def test_single_user_with_activities(
|
||||
app, user_1, sport_1_cycling, sport_2_running,
|
||||
activity_cycling_user_1, activity_running_user_1
|
||||
):
|
||||
"""=> Get single user details"""
|
||||
client = app.test_client()
|
||||
|
||||
response = client.get(f'/api/users/{user_1.id}')
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert data['status'] == 'success'
|
||||
assert data['data'] is not None
|
||||
assert data['data']['username'] == 'test'
|
||||
assert data['data']['email'] == 'test@test.com'
|
||||
assert data['data']['created_at']
|
||||
assert not data['data']['admin']
|
||||
assert data['data']['first_name'] is None
|
||||
assert data['data']['last_name'] is None
|
||||
assert data['data']['birth_date'] is None
|
||||
assert data['data']['bio'] is None
|
||||
assert data['data']['location'] is None
|
||||
assert data['data']['nb_activities'] == 2
|
||||
assert data['data']['nb_sports'] == 2
|
||||
assert data['data']['total_distance'] == 22
|
||||
assert data['data']['total_duration'] == '1:57:04'
|
||||
|
||||
|
||||
def test_single_user_no_id(app):
|
||||
"""=> Ensure error is thrown if an id is not provided."""
|
||||
client = app.test_client()
|
||||
response = client.get(f'/api/users/blah')
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 404
|
||||
assert 'fail' in data['status']
|
||||
assert 'User does not exist' in data['message']
|
||||
|
||||
|
||||
def test_single_user_wrong_id(app):
|
||||
"""=> Ensure error is thrown if the id does not exist."""
|
||||
client = app.test_client()
|
||||
response = client.get(f'/api/users/99999999999')
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 404
|
||||
assert 'fail' in data['status']
|
||||
assert 'User does not exist' in data['message']
|
||||
|
||||
|
||||
def test_users_list(app, user_1, user_2):
|
||||
"""=> Ensure get single user behaves correctly."""
|
||||
|
||||
client = app.test_client()
|
||||
response = client.get('/api/users')
|
||||
data = json.loads(response.data.decode())
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'success' in data['status']
|
||||
|
||||
assert len(data['data']['users']) == 2
|
||||
assert 'created_at' in data['data']['users'][0]
|
||||
assert 'created_at' in data['data']['users'][1]
|
||||
assert 'test' in data['data']['users'][0]['username']
|
||||
assert 'toto' in data['data']['users'][1]['username']
|
||||
assert 'test@test.com' in data['data']['users'][0]['email']
|
||||
assert 'toto@toto.com' in data['data']['users'][1]['email']
|
||||
assert data['data']['users'][0]['nb_activities'] == 0
|
||||
assert data['data']['users'][0]['nb_sports'] == 0
|
||||
assert data['data']['users'][0]['total_distance'] == 0
|
||||
assert data['data']['users'][0]['total_duration'] == '0:00:00'
|
||||
assert data['data']['users'][1]['nb_activities'] == 0
|
||||
assert data['data']['users'][1]['nb_sports'] == 0
|
||||
assert data['data']['users'][1]['total_distance'] == 0
|
||||
assert data['data']['users'][1]['total_duration'] == '0:00:00'
|
||||
|
||||
|
||||
def test_encode_auth_token(app, user_1):
|
||||
"""=> Ensure correct auth token generation"""
|
||||
auth_token = user_1.encode_auth_token(user_1.id)
|
||||
assert isinstance(auth_token, bytes)
|
||||
|
||||
|
||||
def test_decode_auth_token(app, user_1):
|
||||
auth_token = user_1.encode_auth_token(user_1.id)
|
||||
assert isinstance(auth_token, bytes)
|
||||
assert User.decode_auth_token(auth_token) == user_1.id
|
18
fittrackee_api/fittrackee_api/tests/test_users_model.py
Normal file
18
fittrackee_api/fittrackee_api/tests/test_users_model.py
Normal file
@ -0,0 +1,18 @@
|
||||
def test_user_model(app, user_1):
|
||||
assert '<User \'test\'>' == str(user_1)
|
||||
|
||||
serialized_user = user_1.serialize()
|
||||
assert 1 == serialized_user['id']
|
||||
assert 'test' == serialized_user['username']
|
||||
assert 'created_at' in serialized_user
|
||||
assert serialized_user['admin'] is False
|
||||
assert serialized_user['first_name'] is None
|
||||
assert serialized_user['last_name'] is None
|
||||
assert serialized_user['bio'] is None
|
||||
assert serialized_user['location'] is None
|
||||
assert serialized_user['birth_date'] is None
|
||||
assert serialized_user['picture'] is False
|
||||
assert serialized_user['nb_activities'] == 0
|
||||
assert serialized_user['nb_sports'] == 0
|
||||
assert serialized_user['total_distance'] == 0
|
||||
assert serialized_user['total_duration'] == '0:00:00'
|
0
fittrackee_api/fittrackee_api/users/__init__.py
Normal file
0
fittrackee_api/fittrackee_api/users/__init__.py
Normal file
299
fittrackee_api/fittrackee_api/users/auth.py
Normal file
299
fittrackee_api/fittrackee_api/users/auth.py
Normal file
@ -0,0 +1,299 @@
|
||||
import datetime
|
||||
import os
|
||||
|
||||
from fittrackee_api import appLog, bcrypt, db
|
||||
from flask import Blueprint, current_app, jsonify, request
|
||||
from sqlalchemy import exc, or_
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from .models import User
|
||||
from .utils import authenticate, register_controls, verify_extension
|
||||
|
||||
auth_blueprint = Blueprint('auth', __name__)
|
||||
|
||||
|
||||
@auth_blueprint.route('/auth/register', methods=['POST'])
|
||||
def register_user():
|
||||
# get post data
|
||||
post_data = request.get_json()
|
||||
if not post_data or post_data.get('username') is None \
|
||||
or post_data.get('email') is None \
|
||||
or post_data.get('password') is None \
|
||||
or post_data.get('password_conf') is None:
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Invalid payload.'
|
||||
}
|
||||
return jsonify(response_object), 400
|
||||
username = post_data.get('username')
|
||||
email = post_data.get('email')
|
||||
password = post_data.get('password')
|
||||
password_conf = post_data.get('password_conf')
|
||||
|
||||
try:
|
||||
ret = register_controls(username, email, password, password_conf)
|
||||
except TypeError as e:
|
||||
db.session.rollback()
|
||||
appLog.error(e)
|
||||
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Error. Please try again or contact the administrator.'
|
||||
}
|
||||
return jsonify(response_object), 500
|
||||
if ret != '':
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Errors: ' + ret
|
||||
}
|
||||
return jsonify(response_object), 400
|
||||
|
||||
try:
|
||||
# check for existing user
|
||||
user = User.query.filter(
|
||||
or_(User.username == username, User.email == email)).first()
|
||||
if not user:
|
||||
# add new user to db
|
||||
new_user = User(
|
||||
username=username,
|
||||
email=email,
|
||||
password=password
|
||||
)
|
||||
db.session.add(new_user)
|
||||
db.session.commit()
|
||||
# generate auth token
|
||||
auth_token = new_user.encode_auth_token(new_user.id)
|
||||
response_object = {
|
||||
'status': 'success',
|
||||
'message': 'Successfully registered.',
|
||||
'auth_token': auth_token.decode()
|
||||
}
|
||||
return jsonify(response_object), 201
|
||||
else:
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Sorry. That user already exists.'
|
||||
}
|
||||
return jsonify(response_object), 400
|
||||
# handler errors
|
||||
except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
|
||||
db.session.rollback()
|
||||
appLog.error(e)
|
||||
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Error. Please try again or contact the administrator.'
|
||||
}
|
||||
return jsonify(response_object), 500
|
||||
|
||||
|
||||
@auth_blueprint.route('/auth/login', methods=['POST'])
|
||||
def login_user():
|
||||
# get post data
|
||||
post_data = request.get_json()
|
||||
if not post_data:
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Invalid payload.'
|
||||
}
|
||||
return jsonify(response_object), 400
|
||||
email = post_data.get('email')
|
||||
password = post_data.get('password')
|
||||
try:
|
||||
# check for existing user
|
||||
user = User.query.filter(User.email == email).first()
|
||||
if user and bcrypt.check_password_hash(user.password, password):
|
||||
# generate auth token
|
||||
auth_token = user.encode_auth_token(user.id)
|
||||
response_object = {
|
||||
'status': 'success',
|
||||
'message': 'Successfully logged in.',
|
||||
'auth_token': auth_token.decode()
|
||||
}
|
||||
return jsonify(response_object), 200
|
||||
else:
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Invalid credentials.'
|
||||
}
|
||||
return jsonify(response_object), 404
|
||||
# handler errors
|
||||
except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
|
||||
db.session.rollback()
|
||||
appLog.error(e)
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Error. Please try again or contact the administrator.'
|
||||
}
|
||||
return jsonify(response_object), 500
|
||||
|
||||
|
||||
@auth_blueprint.route('/auth/logout', methods=['GET'])
|
||||
@authenticate
|
||||
def logout_user(user_id):
|
||||
# get auth token
|
||||
auth_header = request.headers.get('Authorization')
|
||||
if auth_header:
|
||||
auth_token = auth_header.split(" ")[1]
|
||||
resp = User.decode_auth_token(auth_token)
|
||||
if not isinstance(user_id, str):
|
||||
response_object = {
|
||||
'status': 'success',
|
||||
'message': 'Successfully logged out.'
|
||||
}
|
||||
return jsonify(response_object), 200
|
||||
else:
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': resp
|
||||
}
|
||||
return jsonify(response_object), 401
|
||||
else:
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Provide a valid auth token.'
|
||||
}
|
||||
return jsonify(response_object), 401
|
||||
|
||||
|
||||
@auth_blueprint.route('/auth/profile', methods=['GET'])
|
||||
@authenticate
|
||||
def get_user_status(user_id):
|
||||
user = User.query.filter_by(id=user_id).first()
|
||||
response_object = {
|
||||
'status': 'success',
|
||||
'data': user.serialize()
|
||||
}
|
||||
return jsonify(response_object), 200
|
||||
|
||||
|
||||
@auth_blueprint.route('/auth/profile/edit', methods=['POST'])
|
||||
@authenticate
|
||||
def edit_user(user_id):
|
||||
# get post data
|
||||
post_data = request.get_json()
|
||||
if not post_data:
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Invalid payload.'
|
||||
}
|
||||
return jsonify(response_object), 400
|
||||
first_name = post_data.get('first_name')
|
||||
last_name = post_data.get('last_name')
|
||||
bio = post_data.get('bio')
|
||||
birth_date = post_data.get('birth_date')
|
||||
location = post_data.get('location')
|
||||
password = post_data.get('password')
|
||||
password_conf = post_data.get('password_conf')
|
||||
|
||||
if password is not None and password != '':
|
||||
if password_conf != password:
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Password and password confirmation don\'t match.\n'
|
||||
}
|
||||
return jsonify(response_object), 400
|
||||
else:
|
||||
password = bcrypt.generate_password_hash(
|
||||
password, current_app.config.get('BCRYPT_LOG_ROUNDS')
|
||||
).decode()
|
||||
|
||||
try:
|
||||
user = User.query.filter_by(id=user_id).first()
|
||||
user.first_name = first_name
|
||||
user.last_name = last_name
|
||||
user.bio = bio
|
||||
user.location = location
|
||||
user.birth_date = (
|
||||
datetime.datetime.strptime(birth_date, '%Y-%m-%d')
|
||||
if birth_date
|
||||
else None
|
||||
)
|
||||
if password is not None and password != '':
|
||||
user.password = password
|
||||
db.session.commit()
|
||||
|
||||
response_object = {
|
||||
'status': 'success',
|
||||
'message': 'User profile updated.'
|
||||
}
|
||||
return jsonify(response_object), 200
|
||||
|
||||
# handler errors
|
||||
except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
|
||||
db.session.rollback()
|
||||
appLog.error(e)
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Error. Please try again or contact the administrator.'
|
||||
}
|
||||
return jsonify(response_object), 500
|
||||
|
||||
|
||||
@auth_blueprint.route('/auth/picture', methods=['POST'])
|
||||
@authenticate
|
||||
def edit_picture(user_id):
|
||||
code = 400
|
||||
response_object = verify_extension('picture', request)
|
||||
if response_object['status'] != 'success':
|
||||
return jsonify(response_object), code
|
||||
|
||||
file = request.files['file']
|
||||
filename = secure_filename(file.filename)
|
||||
dirpath = os.path.join(
|
||||
current_app.config['UPLOAD_FOLDER'],
|
||||
'pictures',
|
||||
str(user_id)
|
||||
)
|
||||
if not os.path.exists(dirpath):
|
||||
os.makedirs(dirpath)
|
||||
filepath = os.path.join(dirpath, filename)
|
||||
|
||||
try:
|
||||
user = User.query.filter_by(id=user_id).first()
|
||||
if user.picture is not None and os.path.isfile(user.picture):
|
||||
os.remove(user.picture)
|
||||
file.save(filepath)
|
||||
user.picture = filepath
|
||||
db.session.commit()
|
||||
|
||||
response_object = {
|
||||
'status': 'success',
|
||||
'message': 'User picture updated.'
|
||||
}
|
||||
return jsonify(response_object), 200
|
||||
|
||||
except (exc.IntegrityError, ValueError) as e:
|
||||
db.session.rollback()
|
||||
appLog.error(e)
|
||||
response_object = {
|
||||
'status': 'fail',
|
||||
'message': 'Error during picture update.'
|
||||
}
|
||||
return jsonify(response_object), 500
|
||||
|
||||
|
||||
@auth_blueprint.route('/auth/picture', methods=['DELETE'])
|
||||
@authenticate
|
||||
def del_picture(user_id):
|
||||
try:
|
||||
user = User.query.filter_by(id=user_id).first()
|
||||
if os.path.isfile(user.picture):
|
||||
os.remove(user.picture)
|
||||
user.picture = None
|
||||
db.session.commit()
|
||||
|
||||
response_object = {
|
||||
'status': 'success',
|
||||
'message': 'User picture delete.'
|
||||
}
|
||||
return jsonify(response_object), 200
|
||||
|
||||
except (exc.IntegrityError, ValueError) as e:
|
||||
db.session.rollback()
|
||||
appLog.error(e)
|
||||
response_object = {
|
||||
'status': 'fail',
|
||||
'message': 'Error during picture deletion.'
|
||||
}
|
||||
return jsonify(response_object), 500
|
115
fittrackee_api/fittrackee_api/users/models.py
Normal file
115
fittrackee_api/fittrackee_api/users/models.py
Normal file
@ -0,0 +1,115 @@
|
||||
import datetime
|
||||
|
||||
import jwt
|
||||
from fittrackee_api import bcrypt, db
|
||||
from flask import current_app
|
||||
from sqlalchemy import func
|
||||
|
||||
from ..activities.models import Activity
|
||||
|
||||
|
||||
class User(db.Model):
|
||||
__tablename__ = "users"
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
username = db.Column(db.String(20), unique=True, nullable=False)
|
||||
email = db.Column(db.String(120), unique=True, nullable=False)
|
||||
password = db.Column(db.String(255), nullable=False)
|
||||
created_at = db.Column(db.DateTime, nullable=False)
|
||||
admin = db.Column(db.Boolean, default=False, nullable=False)
|
||||
first_name = db.Column(db.String(80), nullable=True)
|
||||
last_name = db.Column(db.String(80), nullable=True)
|
||||
birth_date = db.Column(db.DateTime, nullable=True)
|
||||
location = db.Column(db.String(80), nullable=True)
|
||||
bio = db.Column(db.String(200), nullable=True)
|
||||
picture = db.Column(db.String(255), nullable=True)
|
||||
activities = db.relationship('Activity',
|
||||
lazy=True,
|
||||
backref=db.backref('users', lazy='joined'))
|
||||
records = db.relationship('Record',
|
||||
lazy=True,
|
||||
backref=db.backref('users', lazy='joined'))
|
||||
|
||||
def __repr__(self):
|
||||
return f'<User {self.username!r}>'
|
||||
|
||||
def __init__(
|
||||
self, username, email, password,
|
||||
created_at=datetime.datetime.utcnow()):
|
||||
self.username = username
|
||||
self.email = email
|
||||
self.password = bcrypt.generate_password_hash(
|
||||
password, current_app.config.get('BCRYPT_LOG_ROUNDS')
|
||||
).decode()
|
||||
self.created_at = created_at
|
||||
|
||||
@staticmethod
|
||||
def encode_auth_token(user_id):
|
||||
"""
|
||||
Generates the auth token
|
||||
:param user_id: -
|
||||
:return: JWToken
|
||||
"""
|
||||
try:
|
||||
payload = {
|
||||
'exp': datetime.datetime.utcnow() + datetime.timedelta(
|
||||
days=current_app.config.get('TOKEN_EXPIRATION_DAYS'),
|
||||
seconds=current_app.config.get('TOKEN_EXPIRATION_SECONDS')
|
||||
),
|
||||
'iat': datetime.datetime.utcnow(),
|
||||
'sub': user_id
|
||||
}
|
||||
return jwt.encode(
|
||||
payload,
|
||||
current_app.config.get('SECRET_KEY'),
|
||||
algorithm='HS256'
|
||||
)
|
||||
except Exception as e:
|
||||
return e
|
||||
|
||||
@staticmethod
|
||||
def decode_auth_token(auth_token):
|
||||
"""
|
||||
Decodes the auth token
|
||||
:param auth_token: -
|
||||
:return: integer|string
|
||||
"""
|
||||
try:
|
||||
payload = jwt.decode(
|
||||
auth_token,
|
||||
current_app.config.get('SECRET_KEY'))
|
||||
return payload['sub']
|
||||
except jwt.ExpiredSignatureError:
|
||||
return 'Signature expired. Please log in again.'
|
||||
except jwt.InvalidTokenError:
|
||||
return 'Invalid token. Please log in again.'
|
||||
|
||||
def serialize(self):
|
||||
nb_activity = Activity.query.filter(
|
||||
Activity.user_id == self.id
|
||||
).count()
|
||||
sports = db.session.query(
|
||||
func.count(Activity.sport_id)
|
||||
).group_by(
|
||||
Activity.sport_id
|
||||
).all()
|
||||
total = db.session.query(
|
||||
func.sum(Activity.distance),
|
||||
func.sum(Activity.duration)
|
||||
).first()
|
||||
return {
|
||||
'id': self.id,
|
||||
'username': self.username,
|
||||
'email': self.email,
|
||||
'created_at': self.created_at,
|
||||
'admin': self.admin,
|
||||
'first_name': self.first_name,
|
||||
'last_name': self.last_name,
|
||||
'bio': self.bio,
|
||||
'location': self.location,
|
||||
'birth_date': self.birth_date,
|
||||
'picture': self.picture is not None,
|
||||
'nb_activities': nb_activity,
|
||||
'nb_sports': len(sports),
|
||||
'total_distance': float(total[0]) if total[0] else 0,
|
||||
'total_duration': str(total[1]) if total[1] else "0:00:00",
|
||||
}
|
63
fittrackee_api/fittrackee_api/users/users.py
Normal file
63
fittrackee_api/fittrackee_api/users/users.py
Normal file
@ -0,0 +1,63 @@
|
||||
from flask import Blueprint, jsonify, send_file
|
||||
|
||||
from .models import User
|
||||
|
||||
users_blueprint = Blueprint('users', __name__)
|
||||
|
||||
|
||||
@users_blueprint.route('/users', methods=['GET'])
|
||||
def get_users():
|
||||
"""Get all users"""
|
||||
users = User.query.all()
|
||||
response_object = {
|
||||
'status': 'success',
|
||||
'data': {
|
||||
'users': [user.serialize() for user in users]
|
||||
}
|
||||
}
|
||||
return jsonify(response_object), 200
|
||||
|
||||
|
||||
@users_blueprint.route('/users/<user_id>', methods=['GET'])
|
||||
def get_single_user(user_id):
|
||||
"""Get single user details"""
|
||||
response_object = {
|
||||
'status': 'fail',
|
||||
'message': 'User does not exist'
|
||||
}
|
||||
try:
|
||||
user = User.query.filter_by(id=int(user_id)).first()
|
||||
if not user:
|
||||
return jsonify(response_object), 404
|
||||
else:
|
||||
response_object = {
|
||||
'status': 'success',
|
||||
'data': user.serialize()
|
||||
}
|
||||
return jsonify(response_object), 200
|
||||
except ValueError:
|
||||
return jsonify(response_object), 404
|
||||
|
||||
|
||||
@users_blueprint.route('/users/<user_id>/picture', methods=['GET'])
|
||||
def get_picture(user_id):
|
||||
response_object = {
|
||||
'status': 'fail',
|
||||
'message': 'User does not exist'
|
||||
}
|
||||
try:
|
||||
user = User.query.filter_by(id=int(user_id)).first()
|
||||
if not user:
|
||||
return jsonify(response_object), 404
|
||||
else:
|
||||
return send_file(user.picture)
|
||||
except ValueError:
|
||||
return jsonify(response_object), 404
|
||||
|
||||
|
||||
@users_blueprint.route('/ping', methods=['GET'])
|
||||
def ping_pong():
|
||||
return jsonify({
|
||||
'status': 'success',
|
||||
'message': 'pong!'
|
||||
})
|
106
fittrackee_api/fittrackee_api/users/utils.py
Normal file
106
fittrackee_api/fittrackee_api/users/utils.py
Normal file
@ -0,0 +1,106 @@
|
||||
import re
|
||||
from functools import wraps
|
||||
|
||||
from flask import current_app, jsonify, request
|
||||
|
||||
from .models import User
|
||||
|
||||
|
||||
def is_admin(user_id):
|
||||
user = User.query.filter_by(id=user_id).first()
|
||||
return user.admin
|
||||
|
||||
|
||||
def is_valid_email(email):
|
||||
mail_pattern = r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"
|
||||
return re.match(mail_pattern, email) is not None
|
||||
|
||||
|
||||
def register_controls(username, email, password, password_conf):
|
||||
ret = ''
|
||||
if not 2 < len(username) < 13:
|
||||
ret += 'Username: 3 to 12 characters required.\n'
|
||||
if not is_valid_email(email):
|
||||
ret += 'Valid email must be provided.\n'
|
||||
if password != password_conf:
|
||||
ret += 'Password and password confirmation don\'t match.\n'
|
||||
if len(password) < 8:
|
||||
ret += 'Password: 8 characters required.\n'
|
||||
return ret
|
||||
|
||||
|
||||
def verify_extension(file_type, req):
|
||||
response_object = {'status': 'success'}
|
||||
|
||||
if 'file' not in req.files:
|
||||
response_object = {'status': 'fail', 'message': 'No file part.'}
|
||||
return response_object
|
||||
|
||||
file = req.files['file']
|
||||
if file.filename == '':
|
||||
response_object = {'status': 'fail', 'message': 'No selected file.'}
|
||||
return response_object
|
||||
|
||||
allowed_extensions = (
|
||||
'ACTIVITY_ALLOWED_EXTENSIONS'
|
||||
if file_type == 'activity'
|
||||
else 'PICTURE_ALLOWED_EXTENSIONS'
|
||||
)
|
||||
|
||||
if not ('.' in file.filename and
|
||||
file.filename.rsplit('.', 1)[1].lower() in
|
||||
current_app.config.get(allowed_extensions)):
|
||||
response_object = {
|
||||
'status': 'fail',
|
||||
'message': 'File extension not allowed.'
|
||||
}
|
||||
|
||||
return response_object
|
||||
|
||||
|
||||
def verify_user(current_request, verify_admin):
|
||||
response_object = {
|
||||
'status': 'error',
|
||||
'message': 'Something went wrong. Please contact us.'
|
||||
}
|
||||
code = 401
|
||||
auth_header = current_request.headers.get('Authorization')
|
||||
if not auth_header:
|
||||
response_object['message'] = 'Provide a valid auth token.'
|
||||
return response_object, code, None
|
||||
auth_token = auth_header.split(" ")[1]
|
||||
resp = User.decode_auth_token(auth_token)
|
||||
if isinstance(resp, str):
|
||||
response_object['message'] = resp
|
||||
return response_object, code, None
|
||||
user = User.query.filter_by(id=resp).first()
|
||||
if not user:
|
||||
return response_object, code, None
|
||||
if verify_admin and not is_admin(resp):
|
||||
response_object['message'] = 'You do not have permissions.'
|
||||
return response_object, 403, None
|
||||
return None, None, resp
|
||||
|
||||
|
||||
def authenticate(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
verify_admin = False
|
||||
response_object, code, resp = verify_user(request, verify_admin)
|
||||
if response_object:
|
||||
return jsonify(response_object), code
|
||||
return f(resp, *args, **kwargs)
|
||||
|
||||
return decorated_function
|
||||
|
||||
|
||||
def authenticate_as_admin(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
verify_admin = True
|
||||
response_object, code, resp = verify_user(request, verify_admin)
|
||||
if response_object:
|
||||
return jsonify(response_object), code
|
||||
return f(resp, *args, **kwargs)
|
||||
|
||||
return decorated_function
|
1
fittrackee_api/migrations/README
Executable file
1
fittrackee_api/migrations/README
Executable file
@ -0,0 +1 @@
|
||||
Generic single-database configuration.
|
45
fittrackee_api/migrations/alembic.ini
Normal file
45
fittrackee_api/migrations/alembic.ini
Normal file
@ -0,0 +1,45 @@
|
||||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# template used to generate migration files
|
||||
# file_template = %%(rev)s_%%(slug)s
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
# the 'revision' command, regardless of autogenerate
|
||||
# revision_environment = false
|
||||
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARN
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
[logger_alembic]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = alembic
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||
datefmt = %H:%M:%S
|
88
fittrackee_api/migrations/env.py
Executable file
88
fittrackee_api/migrations/env.py
Executable file
@ -0,0 +1,88 @@
|
||||
from __future__ import with_statement
|
||||
from alembic import context
|
||||
from sqlalchemy import engine_from_config, pool
|
||||
from logging.config import fileConfig
|
||||
import logging
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
config = context.config
|
||||
|
||||
# Interpret the config file for Python logging.
|
||||
# This line sets up loggers basically.
|
||||
fileConfig(config.config_file_name)
|
||||
logger = logging.getLogger('alembic.env')
|
||||
|
||||
# add your model's MetaData object here
|
||||
# for 'autogenerate' support
|
||||
# from myapp import mymodel
|
||||
# target_metadata = mymodel.Base.metadata
|
||||
from flask import current_app
|
||||
config.set_main_option('sqlalchemy.url',
|
||||
current_app.config.get('SQLALCHEMY_DATABASE_URI'))
|
||||
target_metadata = current_app.extensions['migrate'].db.metadata
|
||||
|
||||
# other values from the config, defined by the needs of env.py,
|
||||
# can be acquired:
|
||||
# my_important_option = config.get_main_option("my_important_option")
|
||||
# ... etc.
|
||||
|
||||
|
||||
def run_migrations_offline():
|
||||
"""Run migrations in 'offline' mode.
|
||||
|
||||
This configures the context with just a URL
|
||||
and not an Engine, though an Engine is acceptable
|
||||
here as well. By skipping the Engine creation
|
||||
we don't even need a DBAPI to be available.
|
||||
|
||||
Calls to context.execute() here emit the given string to the
|
||||
script output.
|
||||
|
||||
"""
|
||||
url = config.get_main_option("sqlalchemy.url")
|
||||
context.configure(url=url)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def run_migrations_online():
|
||||
"""Run migrations in 'online' mode.
|
||||
|
||||
In this scenario we need to create an Engine
|
||||
and associate a connection with the context.
|
||||
|
||||
"""
|
||||
|
||||
# this callback is used to prevent an auto-migration from being generated
|
||||
# when there are no changes to the schema
|
||||
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
|
||||
def process_revision_directives(context, revision, directives):
|
||||
if getattr(config.cmd_opts, 'autogenerate', False):
|
||||
script = directives[0]
|
||||
if script.upgrade_ops.is_empty():
|
||||
directives[:] = []
|
||||
logger.info('No changes in schema detected.')
|
||||
|
||||
engine = engine_from_config(config.get_section(config.config_ini_section),
|
||||
prefix='sqlalchemy.',
|
||||
poolclass=pool.NullPool)
|
||||
|
||||
connection = engine.connect()
|
||||
context.configure(connection=connection,
|
||||
target_metadata=target_metadata,
|
||||
process_revision_directives=process_revision_directives,
|
||||
**current_app.extensions['migrate'].configure_args)
|
||||
|
||||
try:
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
24
fittrackee_api/migrations/script.py.mako
Executable file
24
fittrackee_api/migrations/script.py.mako
Executable file
@ -0,0 +1,24 @@
|
||||
"""${message}
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision | comma,n}
|
||||
Create Date: ${create_date}
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = ${repr(up_revision)}
|
||||
down_revision = ${repr(down_revision)}
|
||||
branch_labels = ${repr(branch_labels)}
|
||||
depends_on = ${repr(depends_on)}
|
||||
|
||||
|
||||
def upgrade():
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade():
|
||||
${downgrades if downgrades else "pass"}
|
30
fittrackee_api/migrations/versions/5a42db64e872_.py
Normal file
30
fittrackee_api/migrations/versions/5a42db64e872_.py
Normal file
@ -0,0 +1,30 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: 5a42db64e872
|
||||
Revises: 92adde6ac0d0
|
||||
Create Date: 2018-05-30 10:52:33.433687
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '5a42db64e872'
|
||||
down_revision = '92adde6ac0d0'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('activities', sa.Column('map', sa.String(length=255), nullable=True))
|
||||
op.create_unique_constraint(None, 'sports', ['img'])
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint(None, 'sports', type_='unique')
|
||||
op.drop_column('activities', 'map')
|
||||
# ### end Alembic commands ###
|
28
fittrackee_api/migrations/versions/92adde6ac0d0_.py
Normal file
28
fittrackee_api/migrations/versions/92adde6ac0d0_.py
Normal file
@ -0,0 +1,28 @@
|
||||
"""add 'bounds' column to 'Activity' table
|
||||
|
||||
Revision ID: 92adde6ac0d0
|
||||
Revises: dd73d23a7a3d
|
||||
Create Date: 2018-05-14 14:25:04.889189
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '92adde6ac0d0'
|
||||
down_revision = 'dd73d23a7a3d'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('activities', sa.Column('bounds', postgresql.ARRAY(sa.Float()), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('activities', 'bounds')
|
||||
# ### end Alembic commands ###
|
44
fittrackee_api/migrations/versions/9741fc7834da_.py
Normal file
44
fittrackee_api/migrations/versions/9741fc7834da_.py
Normal file
@ -0,0 +1,44 @@
|
||||
"""create User table
|
||||
|
||||
Revision ID: 9741fc7834da
|
||||
Revises:
|
||||
Create Date: 2018-01-20 18:59:49.200032
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '9741fc7834da'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('users',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('username', sa.String(length=20), nullable=False),
|
||||
sa.Column('email', sa.String(length=120), nullable=False),
|
||||
sa.Column('password', sa.String(length=255), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('admin', sa.Boolean(), nullable=False),
|
||||
sa.Column('first_name', sa.String(length=80), nullable=True),
|
||||
sa.Column('last_name', sa.String(length=80), nullable=True),
|
||||
sa.Column('birth_date', sa.DateTime(), nullable=True),
|
||||
sa.Column('location', sa.String(length=80), nullable=True),
|
||||
sa.Column('bio', sa.String(length=200), nullable=True),
|
||||
sa.Column('picture', sa.String(length=255), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('email'),
|
||||
sa.UniqueConstraint('username')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('users')
|
||||
# ### end Alembic commands ###
|
28
fittrackee_api/migrations/versions/9f8c9c37da44_.py
Normal file
28
fittrackee_api/migrations/versions/9f8c9c37da44_.py
Normal file
@ -0,0 +1,28 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: 9f8c9c37da44
|
||||
Revises: 5a42db64e872
|
||||
Create Date: 2018-05-30 12:48:11.714627
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '9f8c9c37da44'
|
||||
down_revision = '5a42db64e872'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('activities', sa.Column('map_id', sa.String(length=50), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('activities', 'map_id')
|
||||
# ### end Alembic commands ###
|
59
fittrackee_api/migrations/versions/b7cfe0c17708_.py
Normal file
59
fittrackee_api/migrations/versions/b7cfe0c17708_.py
Normal file
@ -0,0 +1,59 @@
|
||||
"""create Activity & Sport tables
|
||||
|
||||
Revision ID: b7cfe0c17708
|
||||
Revises: 9741fc7834da
|
||||
Create Date: 2018-01-21 17:24:52.587814
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'b7cfe0c17708'
|
||||
down_revision = '9741fc7834da'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('sports',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('label', sa.String(length=50), nullable=False),
|
||||
sa.Column('img', sa.String(length=255), nullable=True),
|
||||
sa.Column('is_default', sa.Boolean(), default=False, nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('label')
|
||||
)
|
||||
op.create_table('activities',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||
sa.Column('sport_id', sa.Integer(), nullable=False),
|
||||
sa.Column('title', sa.String(length=255), nullable=True),
|
||||
sa.Column('gpx', sa.String(length=255), nullable=True),
|
||||
sa.Column('creation_date', sa.DateTime(), nullable=True),
|
||||
sa.Column('modification_date', sa.DateTime(), nullable=True),
|
||||
sa.Column('activity_date', sa.DateTime(), nullable=False),
|
||||
sa.Column('duration', sa.Interval(), nullable=False),
|
||||
sa.Column('pauses', sa.Interval(), nullable=True),
|
||||
sa.Column('moving', sa.Interval(), nullable=True),
|
||||
sa.Column('distance', sa.Numeric(precision=6, scale=3), nullable=True),
|
||||
sa.Column('min_alt', sa.Numeric(precision=6, scale=2), nullable=True),
|
||||
sa.Column('max_alt', sa.Numeric(precision=6, scale=2), nullable=True),
|
||||
sa.Column('descent', sa.Numeric(precision=7, scale=2), nullable=True),
|
||||
sa.Column('ascent', sa.Numeric(precision=7, scale=2), nullable=True),
|
||||
sa.Column('max_speed', sa.Numeric(precision=6, scale=2), nullable=True),
|
||||
sa.Column('ave_speed', sa.Numeric(precision=6, scale=2), nullable=True),
|
||||
sa.ForeignKeyConstraint(['sport_id'], ['sports.id'], ),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('activities')
|
||||
op.drop_table('sports')
|
||||
# ### end Alembic commands ###
|
41
fittrackee_api/migrations/versions/caf0e0dc621a_.py
Normal file
41
fittrackee_api/migrations/versions/caf0e0dc621a_.py
Normal file
@ -0,0 +1,41 @@
|
||||
"""create Record table
|
||||
|
||||
Revision ID: caf0e0dc621a
|
||||
Revises: b7cfe0c17708
|
||||
Create Date: 2018-05-11 22:33:08.267488
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'caf0e0dc621a'
|
||||
down_revision = 'b7cfe0c17708'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('records',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||
sa.Column('sport_id', sa.Integer(), nullable=False),
|
||||
sa.Column('activity_id', sa.Integer(), nullable=False),
|
||||
sa.Column('record_type', sa.Enum('AS', 'FD', 'LD', 'MS', name='record_types'), nullable=True),
|
||||
sa.Column('activity_date', sa.DateTime(), nullable=False),
|
||||
sa.Column('value', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['activity_id'], ['activities.id'], ),
|
||||
sa.ForeignKeyConstraint(['sport_id'], ['sports.id'], ),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('user_id', 'sport_id', 'record_type', name='user_sports_records')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('records')
|
||||
# ### end Alembic commands ###
|
43
fittrackee_api/migrations/versions/dd73d23a7a3d_.py
Normal file
43
fittrackee_api/migrations/versions/dd73d23a7a3d_.py
Normal file
@ -0,0 +1,43 @@
|
||||
"""create Activities Segments table
|
||||
|
||||
Revision ID: dd73d23a7a3d
|
||||
Revises: caf0e0dc621a
|
||||
Create Date: 2018-05-14 12:12:57.299200
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'dd73d23a7a3d'
|
||||
down_revision = 'caf0e0dc621a'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('activity_segments',
|
||||
sa.Column('activity_id', sa.Integer(), nullable=False),
|
||||
sa.Column('segment_id', sa.Integer(), nullable=False),
|
||||
sa.Column('duration', sa.Interval(), nullable=False),
|
||||
sa.Column('pauses', sa.Interval(), nullable=True),
|
||||
sa.Column('moving', sa.Interval(), nullable=True),
|
||||
sa.Column('distance', sa.Numeric(precision=6, scale=3), nullable=True),
|
||||
sa.Column('min_alt', sa.Numeric(precision=6, scale=2), nullable=True),
|
||||
sa.Column('max_alt', sa.Numeric(precision=6, scale=2), nullable=True),
|
||||
sa.Column('descent', sa.Numeric(precision=7, scale=2), nullable=True),
|
||||
sa.Column('ascent', sa.Numeric(precision=7, scale=2), nullable=True),
|
||||
sa.Column('max_speed', sa.Numeric(precision=6, scale=2), nullable=True),
|
||||
sa.Column('ave_speed', sa.Numeric(precision=6, scale=2), nullable=True),
|
||||
sa.ForeignKeyConstraint(['activity_id'], ['activities.id'], ),
|
||||
sa.PrimaryKeyConstraint('activity_id', 'segment_id')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('activity_segments')
|
||||
# ### end Alembic commands ###
|
52
fittrackee_api/requirements.txt
Normal file
52
fittrackee_api/requirements.txt
Normal file
@ -0,0 +1,52 @@
|
||||
alembic==0.9.9
|
||||
apipkg==1.4
|
||||
atomicwrites==1.1.5
|
||||
attrs==17.4.0
|
||||
bcrypt==3.1.4
|
||||
certifi==2018.4.16
|
||||
cffi==1.11.2
|
||||
chardet==3.0.4
|
||||
click==6.7
|
||||
codacy-coverage==1.3.11
|
||||
coverage==4.5.1
|
||||
execnet==1.5.0
|
||||
flake8==3.5.0
|
||||
flake8-isort==2.2.2
|
||||
flake8-polyfill==1.0.1
|
||||
Flask==1.0.2
|
||||
Flask-Bcrypt==0.7.1
|
||||
Flask-Migrate==2.1.1
|
||||
Flask-SQLAlchemy==2.3.2
|
||||
gpxpy==1.2.0
|
||||
idna==2.6
|
||||
isort==4.2.15
|
||||
itsdangerous==0.24
|
||||
Jinja2==2.10
|
||||
Mako==1.0.7
|
||||
MarkupSafe==1.0
|
||||
mccabe==0.6.1
|
||||
more-itertools==4.2.0
|
||||
mysqlclient==1.3.12
|
||||
Pillow==5.1.0
|
||||
pluggy==0.6.0
|
||||
psycopg2==2.7.3.2
|
||||
py==1.5.3
|
||||
pycodestyle==2.3.1
|
||||
pycparser==2.18
|
||||
pyflakes==1.6.0
|
||||
PyJWT==1.6.1
|
||||
pytest==3.6.0
|
||||
pytest-cache==1.0
|
||||
pytest-cov==2.5.1
|
||||
pytest-flake8==0.9.1
|
||||
pytest-isort==0.1.0
|
||||
pytest-runner==3.0
|
||||
python-dateutil==2.7.2
|
||||
python-editor==1.0.3
|
||||
requests==2.18.4
|
||||
six==1.11.0
|
||||
SQLAlchemy==1.2.7
|
||||
staticmap==0.5.3
|
||||
testfixtures==5.3.1
|
||||
urllib3==1.22
|
||||
Werkzeug==0.14.1
|
59
fittrackee_api/server.py
Normal file
59
fittrackee_api/server.py
Normal file
@ -0,0 +1,59 @@
|
||||
import shutil
|
||||
|
||||
from fittrackee_api import create_app, db
|
||||
from fittrackee_api.activities.models import Sport
|
||||
from fittrackee_api.users.models import User
|
||||
|
||||
app = create_app()
|
||||
|
||||
|
||||
@app.cli.command()
|
||||
def drop_db():
|
||||
"""Empty database for dev environments."""
|
||||
db.engine.execute("DROP TABLE IF EXISTS alembic_version;")
|
||||
db.drop_all()
|
||||
db.session.commit()
|
||||
print('Database dropped.')
|
||||
shutil.rmtree(app.config['UPLOAD_FOLDER'], ignore_errors=True)
|
||||
print('Uploaded files deleted.')
|
||||
|
||||
|
||||
@app.cli.command()
|
||||
def init_data():
|
||||
"""Init the database."""
|
||||
admin = User(
|
||||
username='admin',
|
||||
email='admin@example.com',
|
||||
password='mpwoadmin')
|
||||
admin.admin = True
|
||||
db.session.add(admin)
|
||||
sport = Sport(label='Cycling (Sport)')
|
||||
sport.img = '/img/sports/cycling-sport.png'
|
||||
sport.is_default = True
|
||||
db.session.add(sport)
|
||||
sport = Sport(label='Cycling (Transport)')
|
||||
sport.img = '/img/sports/cycling-transport.png'
|
||||
sport.is_default = True
|
||||
db.session.add(sport)
|
||||
sport = Sport(label='Hiking')
|
||||
sport.img = '/img/sports/hiking.png'
|
||||
sport.is_default = True
|
||||
db.session.add(sport)
|
||||
sport = Sport(label='Mountain Biking')
|
||||
sport.img = '/img/sports/mountain-biking.png'
|
||||
sport.is_default = True
|
||||
db.session.add(sport)
|
||||
sport = Sport(label='Running')
|
||||
sport.img = '/img/sports/running.png'
|
||||
sport.is_default = True
|
||||
db.session.add(sport)
|
||||
sport = Sport(label='Walking')
|
||||
sport.img = '/img/sports/walking.png'
|
||||
sport.is_default = True
|
||||
db.session.add(sport)
|
||||
db.session.commit()
|
||||
print('Initial data stored in database.')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run()
|
Reference in New Issue
Block a user