Skip to content

Commit

Permalink
Merge pull request #666 from SamR1/display-user-workouts
Browse files Browse the repository at this point in the history
Display last 5 workouts in user detail
  • Loading branch information
SamR1 authored Dec 22, 2024
2 parents cde559f + 1f1dc8f commit f7e2a1d
Show file tree
Hide file tree
Showing 19 changed files with 608 additions and 53 deletions.
4 changes: 2 additions & 2 deletions fittrackee/dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
<link rel="stylesheet" href="/static/css/fork-awesome.min.css"/>
<link rel="stylesheet" href="/static/css/leaflet.css"/>
<title>FitTrackee</title>
<script type="module" crossorigin src="/static/index-DVm2A7MB.js"></script>
<script type="module" crossorigin src="/static/index-CgtlMEls.js"></script>
<link rel="modulepreload" crossorigin href="/static/charts-BDOr5tL2.js">
<link rel="modulepreload" crossorigin href="/static/maps-DkiRMund.js">
<link rel="stylesheet" crossorigin href="/static/css/maps-CIGW-MKW.css">
<link rel="stylesheet" crossorigin href="/static/css/index-D04_aUPJ.css">
<link rel="stylesheet" crossorigin href="/static/css/index-c4ggUbWJ.css">
</head>
<body>
<div id="app"></div>
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

227 changes: 226 additions & 1 deletion fittrackee/tests/users/test_users_api.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json
from datetime import datetime, timedelta
from io import BytesIO
from typing import Tuple
from typing import List, Tuple
from unittest.mock import MagicMock, patch

import pytest
Expand All @@ -22,6 +22,7 @@
)
from fittrackee.users.roles import UserRole
from fittrackee.utils import get_readable_duration
from fittrackee.visibility_levels import VisibilityLevel
from fittrackee.workouts.models import Sport, Workout

from ..mixins import ApiTestCaseMixin, ReportMixin
Expand Down Expand Up @@ -3481,3 +3482,227 @@ def test_it_does_not_return_comment_unsuspension(
data = json.loads(response.data.decode())
assert 'success' in data['status']
assert len(data['data']['sanctions']) == 0


class TestGetUserLatestWorkouts(ApiTestCaseMixin, ReportMixin, CommentMixin):
route = '/api/users/{username}/workouts'

def test_it_returns_error_when_user_does_not_exist(
self, app: Flask, user_1: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)

response = client.get(
self.route.format(username=self.random_string()),
headers=dict(Authorization=f'Bearer {auth_token}'),
)

self.assert_404_with_entity(response, 'user')

def test_it_returns_empty_list_when_user_has_no_workout(
self, app: Flask, user_1: User, user_2: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)

response = client.get(
self.route.format(username=user_2.username),
headers=dict(Authorization=f'Bearer {auth_token}'),
)

assert response.status_code == 200
data = json.loads(response.data.decode())
assert 'success' in data['status']
assert data['data']['workouts'] == []

def test_it_returns_empty_list_when_user_is_suspended(
self,
app: Flask,
user_1: User,
user_2: User,
sport_1_cycling: Sport,
workout_cycling_user_2: Workout,
) -> None:
workout_cycling_user_2.workout_visibility = VisibilityLevel.PUBLIC
user_2.suspended_at = datetime.utcnow()
db.session.commit()
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)

response = client.get(
self.route.format(username=user_2.username),
headers=dict(Authorization=f'Bearer {auth_token}'),
)

assert response.status_code == 200
data = json.loads(response.data.decode())
assert 'success' in data['status']
assert data['data']['workouts'] == []

def test_it_returns_only_public_workout_when_user_is_not_authenticated(
self,
app: Flask,
user_1: User,
sport_1_cycling: Sport,
seven_workouts_user_1: List[Workout],
) -> None:
seven_workouts_user_1[1].workout_visibility = VisibilityLevel.PUBLIC
seven_workouts_user_1[4].workout_visibility = VisibilityLevel.FOLLOWERS
db.session.commit()
client = app.test_client()

response = client.get(self.route.format(username=user_1.username))

assert response.status_code == 200
data = json.loads(response.data.decode())
assert 'success' in data['status']
assert data['data']['workouts'] == [
jsonify_dict(seven_workouts_user_1[1].serialize())
]

def test_it_returns_workouts_visible_to_authenticated_user(
self,
app: Flask,
user_1: User,
user_2: User,
sport_1_cycling: Sport,
seven_workouts_user_1: List[Workout],
workout_cycling_user_2: Workout,
) -> None:
seven_workouts_user_1[1].workout_visibility = VisibilityLevel.PUBLIC
seven_workouts_user_1[4].workout_visibility = VisibilityLevel.FOLLOWERS
db.session.commit()
client, auth_token = self.get_test_client_and_auth_token(
app, user_2.email
)

response = client.get(
self.route.format(username=user_1.username),
headers=dict(Authorization=f'Bearer {auth_token}'),
)

assert response.status_code == 200
data = json.loads(response.data.decode())
assert 'success' in data['status']
assert data['data']['workouts'] == [
jsonify_dict(seven_workouts_user_1[1].serialize(user=user_2))
]

def test_it_returns_workouts_visible_to_follower(
self,
app: Flask,
user_1: User,
user_2: User,
sport_1_cycling: Sport,
seven_workouts_user_1: List[Workout],
workout_cycling_user_2: Workout,
) -> None:
seven_workouts_user_1[1].workout_visibility = VisibilityLevel.PUBLIC
seven_workouts_user_1[4].workout_visibility = VisibilityLevel.FOLLOWERS
user_2.send_follow_request_to(user_1)
user_1.approves_follow_request_from(user_2)
client, auth_token = self.get_test_client_and_auth_token(
app, user_2.email
)

response = client.get(
self.route.format(username=user_1.username),
headers=dict(Authorization=f'Bearer {auth_token}'),
)

assert response.status_code == 200
data = json.loads(response.data.decode())
assert 'success' in data['status']
assert data['data']['workouts'] == [
jsonify_dict(seven_workouts_user_1[4].serialize(user=user_2)),
jsonify_dict(seven_workouts_user_1[1].serialize(user=user_2)),
]

def test_it_returns_last_five_workouts_visible_to_workout_owner(
self,
app: Flask,
user_1: User,
user_2: User,
sport_1_cycling: Sport,
seven_workouts_user_1: List[Workout],
workout_cycling_user_2: Workout,
) -> None:
seven_workouts_user_1[1].workout_visibility = VisibilityLevel.PUBLIC
seven_workouts_user_1[4].workout_visibility = VisibilityLevel.FOLLOWERS
db.session.commit()
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)

response = client.get(
self.route.format(username=user_1.username),
headers=dict(Authorization=f'Bearer {auth_token}'),
)

assert response.status_code == 200
data = json.loads(response.data.decode())
assert 'success' in data['status']
assert data['data']['workouts'] == [
jsonify_dict(seven_workouts_user_1[6].serialize(user=user_1)),
jsonify_dict(seven_workouts_user_1[5].serialize(user=user_1)),
jsonify_dict(seven_workouts_user_1[3].serialize(user=user_1)),
jsonify_dict(seven_workouts_user_1[4].serialize(user=user_1)),
jsonify_dict(seven_workouts_user_1[2].serialize(user=user_1)),
]

def test_it_does_not_return_suspended_workouts(
self,
app: Flask,
user_1: User,
user_2: User,
sport_1_cycling: Sport,
workout_cycling_user_1: Workout,
workout_cycling_user_2: Workout,
) -> None:
workout_cycling_user_1.suspended_at = datetime.utcnow()
db.session.commit()
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)

response = client.get(
self.route.format(username=user_1.username),
headers=dict(Authorization=f'Bearer {auth_token}'),
)

assert response.status_code == 200
data = json.loads(response.data.decode())
assert 'success' in data['status']
assert data['data']['workouts'] == []

@pytest.mark.parametrize(
'client_scope, can_access',
{**OAUTH_SCOPES, 'workouts:read': True}.items(),
)
def test_expected_scopes_are_defined(
self,
app: Flask,
user_1: User,
client_scope: str,
can_access: bool,
) -> None:
(
client,
oauth_client,
access_token,
_,
) = self.create_oauth2_client_and_issue_token(
app, user_1, scope=client_scope
)

response = client.get(
self.route.format(username=user_1.username),
content_type='application/json',
headers=dict(Authorization=f'Bearer {access_token}'),
)

self.assert_response_scope(response, can_access)
3 changes: 3 additions & 0 deletions fittrackee/users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,9 @@ def follows(self, user: 'User') -> str:
def get_following_user_ids(self) -> List:
return [following.id for following in self.following]

def get_followers_user_ids(self) -> List:
return [followers.id for followers in self.followers]

def get_user_url(self) -> str:
"""Return user url on user interface"""
return f"{current_app.config['UI_URL']}/users/{self.username}"
Expand Down
Loading

0 comments on commit f7e2a1d

Please sign in to comment.