diff --git a/poetry.lock b/poetry.lock index 85231c9..71b0ed7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -480,6 +480,83 @@ traitlets = ">=4" [package.extras] test = ["pytest"] +[[package]] +name = "coverage" +version = "7.6.4" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "coverage-7.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f8ae553cba74085db385d489c7a792ad66f7f9ba2ee85bfa508aeb84cf0ba07"}, + {file = "coverage-7.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8165b796df0bd42e10527a3f493c592ba494f16ef3c8b531288e3d0d72c1f6f0"}, + {file = "coverage-7.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c8b95bf47db6d19096a5e052ffca0a05f335bc63cef281a6e8fe864d450a72"}, + {file = "coverage-7.6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ed9281d1b52628e81393f5eaee24a45cbd64965f41857559c2b7ff19385df51"}, + {file = "coverage-7.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0809082ee480bb8f7416507538243c8863ac74fd8a5d2485c46f0f7499f2b491"}, + {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d541423cdd416b78626b55f123412fcf979d22a2c39fce251b350de38c15c15b"}, + {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58809e238a8a12a625c70450b48e8767cff9eb67c62e6154a642b21ddf79baea"}, + {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c9b8e184898ed014884ca84c70562b4a82cbc63b044d366fedc68bc2b2f3394a"}, + {file = "coverage-7.6.4-cp310-cp310-win32.whl", hash = "sha256:6bd818b7ea14bc6e1f06e241e8234508b21edf1b242d49831831a9450e2f35fa"}, + {file = "coverage-7.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:06babbb8f4e74b063dbaeb74ad68dfce9186c595a15f11f5d5683f748fa1d172"}, + {file = "coverage-7.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:73d2b73584446e66ee633eaad1a56aad577c077f46c35ca3283cd687b7715b0b"}, + {file = "coverage-7.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51b44306032045b383a7a8a2c13878de375117946d68dcb54308111f39775a25"}, + {file = "coverage-7.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3fb02fe73bed561fa12d279a417b432e5b50fe03e8d663d61b3d5990f29546"}, + {file = "coverage-7.6.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed8fe9189d2beb6edc14d3ad19800626e1d9f2d975e436f84e19efb7fa19469b"}, + {file = "coverage-7.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b369ead6527d025a0fe7bd3864e46dbee3aa8f652d48df6174f8d0bac9e26e0e"}, + {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ade3ca1e5f0ff46b678b66201f7ff477e8fa11fb537f3b55c3f0568fbfe6e718"}, + {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:27fb4a050aaf18772db513091c9c13f6cb94ed40eacdef8dad8411d92d9992db"}, + {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f704f0998911abf728a7783799444fcbbe8261c4a6c166f667937ae6a8aa522"}, + {file = "coverage-7.6.4-cp311-cp311-win32.whl", hash = "sha256:29155cd511ee058e260db648b6182c419422a0d2e9a4fa44501898cf918866cf"}, + {file = "coverage-7.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:8902dd6a30173d4ef09954bfcb24b5d7b5190cf14a43170e386979651e09ba19"}, + {file = "coverage-7.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2"}, + {file = "coverage-7.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117"}, + {file = "coverage-7.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613"}, + {file = "coverage-7.6.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27"}, + {file = "coverage-7.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52"}, + {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2"}, + {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1"}, + {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5"}, + {file = "coverage-7.6.4-cp312-cp312-win32.whl", hash = "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17"}, + {file = "coverage-7.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08"}, + {file = "coverage-7.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9"}, + {file = "coverage-7.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba"}, + {file = "coverage-7.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c"}, + {file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06"}, + {file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f"}, + {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b"}, + {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21"}, + {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a"}, + {file = "coverage-7.6.4-cp313-cp313-win32.whl", hash = "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e"}, + {file = "coverage-7.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963"}, + {file = "coverage-7.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f"}, + {file = "coverage-7.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806"}, + {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11"}, + {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3"}, + {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a"}, + {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc"}, + {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70"}, + {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef"}, + {file = "coverage-7.6.4-cp313-cp313t-win32.whl", hash = "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e"}, + {file = "coverage-7.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1"}, + {file = "coverage-7.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9cb7fa111d21a6b55cbf633039f7bc2749e74932e3aa7cb7333f675a58a58bf3"}, + {file = "coverage-7.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11a223a14e91a4693d2d0755c7a043db43d96a7450b4f356d506c2562c48642c"}, + {file = "coverage-7.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a413a096c4cbac202433c850ee43fa326d2e871b24554da8327b01632673a076"}, + {file = "coverage-7.6.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00a1d69c112ff5149cabe60d2e2ee948752c975d95f1e1096742e6077affd376"}, + {file = "coverage-7.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f76846299ba5c54d12c91d776d9605ae33f8ae2b9d1d3c3703cf2db1a67f2c0"}, + {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fe439416eb6380de434886b00c859304338f8b19f6f54811984f3420a2e03858"}, + {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0294ca37f1ba500667b1aef631e48d875ced93ad5e06fa665a3295bdd1d95111"}, + {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6f01ba56b1c0e9d149f9ac85a2f999724895229eb36bd997b61e62999e9b0901"}, + {file = "coverage-7.6.4-cp39-cp39-win32.whl", hash = "sha256:bc66f0bf1d7730a17430a50163bb264ba9ded56739112368ba985ddaa9c3bd09"}, + {file = "coverage-7.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:c481b47f6b5845064c65a7bc78bc0860e635a9b055af0df46fdf1c58cebf8e8f"}, + {file = "coverage-7.6.4-pp39.pp310-none-any.whl", hash = "sha256:3c65d37f3a9ebb703e710befdc489a38683a5b152242664b973a7b7b22348a4e"}, + {file = "coverage-7.6.4.tar.gz", hash = "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + [[package]] name = "cryptography" version = "43.0.3" @@ -1702,6 +1779,24 @@ tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-cov" +version = "6.0.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.9" +files = [ + {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"}, + {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"}, +] + +[package.dependencies] +coverage = {version = ">=7.5", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" diff --git a/pyproject.toml b/pyproject.toml index 1f6c84a..2d51b5f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" diff --git a/src/apn.py b/src/apn.py index 8fb04b9..78894d1 100644 --- a/src/apn.py +++ b/src/apn.py @@ -38,7 +38,21 @@ 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"], @@ -46,7 +60,6 @@ def send_push_notification(device_token: str, title: str, body: str): ) 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}", @@ -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}") diff --git a/src/auth_manager.py b/src/auth_manager.py index bb11049..c9bdef2 100644 --- a/src/auth_manager.py +++ b/src/auth_manager.py @@ -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} diff --git a/src/frontend_router.py b/src/frontend_router.py index 2be6bdc..516393f 100644 --- a/src/frontend_router.py +++ b/src/frontend_router.py @@ -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, @@ -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(), }, } @@ -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} diff --git a/src/supabase_client.py b/src/supabase_client.py index b3800dc..f6daf4d 100644 --- a/src/supabase_client.py +++ b/src/supabase_client.py @@ -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() ) diff --git a/src/types/training_week.py b/src/types/training_week.py index 4d13244..d00489f 100644 --- a/src/types/training_week.py +++ b/src/types/training_week.py @@ -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") diff --git a/src/types/user_row.py b/src/types/user_row.py index b78ba23..53f5a86 100644 --- a/src/types/user_row.py +++ b/src/types/user_row.py @@ -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() diff --git a/src/update_pipeline.py b/src/update_pipeline.py index e7d3cab..cf072ca 100644 --- a/src/update_pipeline.py +++ b/src/update_pipeline.py @@ -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, ) diff --git a/test.ipynb b/test.ipynb index 92f38ab..7ab79cb 100644 --- a/test.ipynb +++ b/test.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -10,75 +10,76 @@ "%autoreload 2" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Trigger Lambda E2E" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "import requests\n", - "import os\n", - "\n", - "url = \"https://lwg77yq7dd.execute-api.us-east-1.amazonaws.com/prod/signup\"\n", - "\n", - "response = requests.post(url, json={\n", - " \"trigger_test_key\": os.environ[\"TRIGGER_TEST_KEY\"]\n", - "})\n", - "\n", - "response.json()" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "from src.auth_manager import get_strava_client\n", - "from src.supabase_client import get_user\n", - "from src.activities import get_activity_summaries\n", - "\n", - "user = get_user(os.environ[\"JAMIES_ATHLETE_ID\"])\n", - "strava_client = get_strava_client(user.athlete_id)\n", - "summaries = get_activity_summaries(strava_client)" - ] - }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 6, "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:httpx:HTTP Request: GET https://ehgwtyfhzmnvvxrvyije.supabase.co/rest/v1/user?select=%2A&athlete_id=eq.98390356 \"HTTP/2 200 OK\"\n", + "INFO:httpx:HTTP Request: GET https://ehgwtyfhzmnvvxrvyije.supabase.co/rest/v1/user_auth?select=%2A&athlete_id=eq.98390356 \"HTTP/2 200 OK\"\n", + "INFO:httpx:HTTP Request: GET https://ehgwtyfhzmnvvxrvyije.supabase.co/rest/v1/training_week?select=training_week&athlete_id=eq.98390356&order=created_at.desc&limit=1 \"HTTP/2 200 OK\"\n", + "INFO:stravalib.protocol.ApiV3:GET 'https://www.strava.com/api/v3/athlete/activities' with params {'before': 1730851992, 'after': 1730243592, 'page': 1, 'per_page': 200}\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "sessions=[TrainingSession(day=, session_type=, distance=8.37, notes='easy pace', completed=True), TrainingSession(day=, session_type=, distance=5.0, notes='Maintain a steady but comfortable pace.', completed=False), TrainingSession(day=, session_type=, distance=6.0, notes='2x2mi at 7m 30s pace with rest intervals.', completed=False), TrainingSession(day=, session_type=, distance=0.0, notes='Complete rest to recover.', completed=False), TrainingSession(day=, session_type=, distance=5.63, notes='Easy pace, focus on form and breathing.', completed=False), TrainingSession(day=, session_type=, distance=0.0, notes='Use this day to fully rest before the long run.', completed=False), TrainingSession(day=, session_type=, distance=13.0, notes='Maintain a comfortable pace, ensure hydration and nutrition.', completed=False)]\n", + "[ActivitySummary(date='Monday, November 04, 2024', distance_in_miles=8.37, elevation_gain_in_feet=173.88, pace_minutes_per_mile=9.66), ActivitySummary(date='Tuesday, November 05, 2024', distance_in_miles=1.02, elevation_gain_in_feet=0.0, pace_minutes_per_mile=10.02)]\n", + "You are a talented running coach with years of experience. You have been hired by a client to help them improve their running performance. Note: convert pace values where applicable e.g. 7.5 -> 7m 30s.\n", + "Client Preferences: race_distance= ideal_training_week=[TheoreticalTrainingSession(day=, session_type=), TheoreticalTrainingSession(day=, session_type=)]\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:httpx:HTTP Request: POST https://api.openai.com/v1/chat/completions \"HTTP/1.1 200 OK\"\n", + "INFO:httpx:HTTP Request: POST https://api.openai.com/v1/chat/completions \"HTTP/1.1 200 OK\"\n", + "INFO:httpx:HTTP Request: POST https://ehgwtyfhzmnvvxrvyije.supabase.co/rest/v1/training_week \"HTTP/2 201 Created\"\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "sessions=[TrainingSession(day=, session_type=, distance=8.37, notes='Completed an easy run with an elevation gain of 173.88 feet. Pace was 9m 40s per mile.', completed=True), TrainingSession(day=, session_type=, distance=1.02, notes='Short recovery run with no elevation gain. Pace was 10m 1s per mile.', completed=True), TrainingSession(day=, session_type=, distance=7.5, notes='2x2mi at 7m 30s pace with rest intervals, plus an additional 1.5 miles at an easy pace.', completed=False), TrainingSession(day=, session_type=, distance=0.0, notes='Complete rest to recover.', completed=False), TrainingSession(day=, session_type=, distance=7.11, notes='Easy pace, focus on form and breathing, including an additional 1.48 miles at an easy pace.', completed=False), TrainingSession(day=, session_type=, distance=0.0, notes='Use this day to fully rest before the long run.', completed=False), TrainingSession(day=, session_type=, distance=13.0, notes='Maintain a comfortable pace, ensure hydration and nutrition.', completed=False)]\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:httpx:HTTP Request: GET https://ehgwtyfhzmnvvxrvyije.supabase.co/rest/v1/user_auth?select=%2A&athlete_id=eq.98390356 \"HTTP/2 200 OK\"\n", + "INFO:root:Sending push notification with token: ff27dd2c...\n", + "INFO:httpx:HTTP Request: POST https://api.push.apple.com/3/device/ff27dd2c813efb25194f614c3be1829dce114f288dd03bde0e86169fae8f3d1f \"HTTP/2 200 OK\"\n" + ] + }, { "data": { "text/plain": [ - "{'success': True,\n", - " 'weekly_summaries': ['{\"year\": 2024, \"week_of_year\": 38, \"week_start_date\": \"2024-09-16\", \"longest_run\": 0.0, \"total_distance\": 0.0}',\n", - " '{\"year\": 2024, \"week_of_year\": 39, \"week_start_date\": \"2024-09-23\", \"longest_run\": 9.01, \"total_distance\": 20.38}',\n", - " '{\"year\": 2024, \"week_of_year\": 40, \"week_start_date\": \"2024-09-30\", \"longest_run\": 18.01, \"total_distance\": 45.05}',\n", - " '{\"year\": 2024, \"week_of_year\": 41, \"week_start_date\": \"2024-10-07\", \"longest_run\": 20.03, \"total_distance\": 50.1}',\n", - " '{\"year\": 2024, \"week_of_year\": 42, \"week_start_date\": \"2024-10-14\", \"longest_run\": 20.02, \"total_distance\": 44.66}',\n", - " '{\"year\": 2024, \"week_of_year\": 43, \"week_start_date\": \"2024-10-21\", \"longest_run\": 20.02, \"total_distance\": 41.38}',\n", - " '{\"year\": 2024, \"week_of_year\": 44, \"week_start_date\": \"2024-10-28\", \"longest_run\": 15.81, \"total_distance\": 40.08}',\n", - " '{\"year\": 2024, \"week_of_year\": 45, \"week_start_date\": \"2024-11-04\", \"longest_run\": 8.37, \"total_distance\": 8.37}']}" + "{'success': True}" ] }, - "execution_count": 4, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "from src.frontend_router import get_weekly_summaries_handler\n", + "import os\n", + "\n", + "from src.update_pipeline import update_training_week\n", + "from src.types.update_pipeline import ExeType\n", + "from src.supabase_client import get_user\n", "\n", - "get_weekly_summaries_handler(user.athlete_id, {})" + "USER = get_user(os.environ[\"JAMIES_ATHLETE_ID\"])\n", + "update_training_week(USER, ExeType.MID_WEEK)" ] }, { diff --git a/tests/test_update_pipeline.py b/tests/test_update_pipeline.py index 4cfe89f..d025037 100644 --- a/tests/test_update_pipeline.py +++ b/tests/test_update_pipeline.py @@ -3,8 +3,7 @@ sys.path.append(".") import os -from unittest.mock import MagicMock, patch -from unittest.mock import ANY +from unittest.mock import ANY, patch import pytest @@ -21,12 +20,7 @@ def test_update_training_week(exe_type): "src.update_pipeline.send_push_notif_wrapper" ) as mock_send_push: - # Run the function result = update_training_week(USER, exe_type) - - # Assertions for function output and called mocks assert result == {"success": True} - mock_upsert.assert_called_with(USER.athlete_id, ANY) - mock_send_push.assert_called_once_with(USER)