Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

adding pytest coverage #117

Merged
merged 3 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ cryptography = "^43.0.3"
ipykernel = "^6.29.4"
freezegun = "^1.5.1"
pytest = "^8.3.2"
pytest-cov = "^6.0.0"
28 changes: 24 additions & 4 deletions src/apn.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,28 @@ def send_push_notification(device_token: str, title: str, body: str):
:param device_token: User's device token
:param title: Notification title
:param body: Notification body
:raises ValueError: If device token is invalid
"""
# Validate device token format
clean_token = device_token.strip().replace(" ", "")
if (
not clean_token
or len(clean_token) != 64
or not all(c in "0123456789abcdefABCDEF" for c in clean_token)
):
raise ValueError(f"Invalid device token format: {device_token}")

logging.info(
f"Sending push notification with token: {clean_token[:8]}..."
) # Log first 8 chars for debugging

auth_token = generate_jwt_token(
key_id=os.environ["APN_KEY_ID"],
team_id=os.environ["APN_TEAM_ID"],
private_key=base64.b64decode(os.environ["APN_PRIVATE_KEY"]).decode(),
)

base_url = "api.push.apple.com"
clean_token = device_token.strip().replace(" ", "")
url = f"https://{base_url}/3/device/{clean_token}"
headers = {
"Authorization": f"Bearer {auth_token}",
Expand Down Expand Up @@ -78,12 +91,19 @@ def send_push_notification(device_token: str, title: str, body: str):


def send_push_notif_wrapper(user: UserRow):
"""Send push notification to user if they have a valid device token."""
user_auth = get_user_auth(user.athlete_id)
if user_auth.device_token:
if not user_auth.device_token:
logger.info(f"No device token for user {user.athlete_id}")
return

try:
send_push_notification(
device_token=user_auth.device_token,
title="TrackFlow 🏃‍♂️🎯",
body="Your training week has been updated!",
)
else:
logger.info(f"Skipping push notification for {user.athlete_id=}")
except ValueError as e:
logger.error(f"Invalid device token for user {user.athlete_id}: {e}")
except Exception as e:
logger.error(f"Failed to send push notification to user {user.athlete_id}: {e}")
7 changes: 2 additions & 5 deletions src/auth_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,14 +147,11 @@ def authenticate_with_code(code: str) -> UserAuthRow:

def signup(user_auth: UserAuthRow, email: Optional[str] = None) -> dict:
""" """
preferences = (
"I'm looking to improve my running performance while being smart and realistic."
)
send_alert_email(
subject="TrackFlow Alert: New Signup Attempt",
text_content=f"You have a new client {email=} attempting to signup with {preferences=}",
text_content=f"You have a new client {email=} attempting to signup",
)
upsert_user(UserRow(athlete_id=user_auth.athlete_id, preferences=preferences))
upsert_user(UserRow(athlete_id=user_auth.athlete_id))
return {"success": True, "jwt_token": user_auth.jwt_token, "is_new_user": True}


Expand Down
6 changes: 3 additions & 3 deletions src/frontend_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import jwt

from src import auth_manager
from src.activities import get_daily_activity, get_weekly_summaries
from src.activities import get_weekly_summaries
from src.auth_manager import get_strava_client
from src.supabase_client import (
get_training_week,
Expand Down Expand Up @@ -36,7 +36,7 @@ def get_profile_handler(athlete_id: str, payload: dict) -> dict:
"lastname": athlete.lastname,
"profile": athlete.profile,
"email": user.email,
"preferences": user.preferences_json.json(),
"preferences": user.preferences.json(),
},
}

Expand All @@ -45,7 +45,7 @@ def update_preferences_handler(athlete_id: str, payload: dict) -> dict:
"""Handle update_preferences request."""
if payload is None or "preferences" not in payload:
return {"success": False, "error": "Missing preferences in payload"}
update_preferences(athlete_id=athlete_id, preferences_json=payload["preferences"])
update_preferences(athlete_id=athlete_id, preferences=payload["preferences"])
return {"success": True}


Expand Down
8 changes: 4 additions & 4 deletions src/supabase_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,22 +158,22 @@ def upsert_training_week(
return response


def update_preferences(athlete_id: int, preferences_json: dict) -> APIResponse:
def update_preferences(athlete_id: int, preferences: dict) -> APIResponse:
"""
Update user's preferences

:param athlete_id: The ID of the athlete
:param preferences: json string representing a Preferences object
:param preferences: A Preferences object as a dictionary
:return: APIResponse
"""
try:
Preferences(**preferences_json)
Preferences(**preferences)
except Exception as e:
raise ValueError("Invalid preferences") from e

table = client.table("user")
response = (
table.update({"preferences_json": preferences_json})
table.update({"preferences": preferences})
.eq("athlete_id", athlete_id)
.execute()
)
Expand Down
2 changes: 1 addition & 1 deletion src/types/training_week.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class TrainingSession(BaseModel):
session_type: SessionType
distance: float = Field(description="Distance in miles")
notes: str = Field(
description="Concise notes about the session, e.g. '2x2mi @ 10k pace' or 'easy pace'"
description="Detailed yet concise notes about the session from the coach's perspective"
)
completed: bool = Field(description="Whether the session has been completed")

Expand Down
5 changes: 2 additions & 3 deletions src/types/user_row.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,11 @@ class TheoreticalTrainingSession(BaseModel):

class Preferences(BaseModel):
race_distance: Optional[RaceDistance] = None
ideal_training_week: Optional[List[TheoreticalTrainingSession]] = None
ideal_training_week: Optional[List[TheoreticalTrainingSession]] = []


class UserRow(BaseModel):
athlete_id: int
preferences: str
preferences: Optional[Preferences] = Preferences()
email: Optional[str] = None
preferences_json: Optional[Preferences] = {}
created_at: datetime = datetime.now()
4 changes: 2 additions & 2 deletions src/update_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ def update_training_week(user: UserRow, exe_type: ExeType) -> dict:
if exe_type == ExeType.NEW_WEEK:
weekly_summaries = get_weekly_summaries(strava_client)
training_week = generate_new_training_week(
sysmsg_base=f"{COACH_ROLE}\n{user.preferences}",
sysmsg_base=f"{COACH_ROLE}\nClient Preferences: {user.preferences}",
weekly_summaries=weekly_summaries,
)
else: # ExeType.MID_WEEK
current_week = get_training_week(user.athlete_id)
activity_summaries = get_activity_summaries(strava_client, num_weeks=1)
training_week = generate_mid_week_update(
sysmsg_base=f"{COACH_ROLE}\n{user.preferences}",
sysmsg_base=f"{COACH_ROLE}\nClient Preferences: {user.preferences}",
training_week=current_week,
completed_activities=activity_summaries,
)
Expand Down
Loading
Loading