From cc164859cda531cc69befa88a76818632239a70f Mon Sep 17 00:00:00 2001 From: Jon Stroop Date: Mon, 10 Mar 2025 15:47:44 -0700 Subject: [PATCH 1/6] Standardize on mock_response_factory in tests - Update test files to use mock_response_factory consistently - Replace direct manipulations of mock_response with factory usage - Keep documentation in DEVELOPMENT.md about the standardized approach --- docs/DEVELOPMENT.md | 23 ++++++++--- .../test_get_azm_timeseries.py | 4 +- .../test_get_body_timeseries_by_date.py | 8 ++-- .../test_get_weight_timeseries_by_date.py | 6 ++- ...est_get_weight_timeseries_by_date_range.py | 6 ++- .../resources/friends/test_get_friends.py | 34 ++++++++-------- .../test_get_heartrate_timeseries_by_date.py | 8 ++-- .../resources/nutrition/test_create_food.py | 16 +++++--- .../nutrition/test_create_food_log.py | 39 +++++++++---------- .../nutrition/test_delete_custom_food.py | 4 +- .../nutrition/test_delete_food_log.py | 4 +- .../nutrition/test_delete_water_log.py | 4 +- .../nutrition/test_get_frequent_foods.py | 8 ++-- .../nutrition/test_update_food_log.py | 14 ++++--- 14 files changed, 101 insertions(+), 77 deletions(-) diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index ad81f49..f3ef631 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -195,15 +195,28 @@ All resource mocks are in the root [conftest.py](tests/conftest.py). ### Response Mocking -The test suite uses consistent patterns for mocking API responses: +The test suite uses the `mock_response_factory` fixture to create consistent, +configurable mock responses: ```python -mock_response = Mock() -mock_response.json.return_value = {"data": "test"} -mock_response.headers = {"content-type": "application/json"} -mock_response.status_code = 200 +# Creating a mock response with status code and data +mock_response = mock_response_factory(200, {"data": "test"}) + +# Creating a mock response with additional headers +mock_response = mock_response_factory( + status_code=200, + json_data={"data": "test"}, + headers={"custom-header": "value"} +) + +# Creating a delete response with no content (204) +mock_response = mock_response_factory(204) ``` +This approach provides a clean, standardized way to create mock responses with +the desired status code, data, and headers. All test files should use this +factory method rather than manually configuring mock responses. + ## OAuth Callback Implementation The OAuth callback mechanism is implemented using two main classes: diff --git a/tests/fitbit_client/resources/active_zone_minutes/test_get_azm_timeseries.py b/tests/fitbit_client/resources/active_zone_minutes/test_get_azm_timeseries.py index 4548cce..79bfaae 100644 --- a/tests/fitbit_client/resources/active_zone_minutes/test_get_azm_timeseries.py +++ b/tests/fitbit_client/resources/active_zone_minutes/test_get_azm_timeseries.py @@ -3,9 +3,9 @@ """Tests for the get_azm_timeseries endpoint.""" -def test_get_azm_timeseries_with_today_date(azm_resource, mock_response): +def test_get_azm_timeseries_with_today_date(azm_resource, mock_response_factory): """Test using 'today' as the date parameter""" - mock_response.json.return_value = {"activities-active-zone-minutes": []} + mock_response = mock_response_factory(200, {"activities-active-zone-minutes": []}) azm_resource.oauth.request.return_value = mock_response result = azm_resource.get_azm_timeseries_by_date(date="today") assert result == mock_response.json.return_value diff --git a/tests/fitbit_client/resources/body_timeseries/test_get_body_timeseries_by_date.py b/tests/fitbit_client/resources/body_timeseries/test_get_body_timeseries_by_date.py index 766ca4f..790ac44 100644 --- a/tests/fitbit_client/resources/body_timeseries/test_get_body_timeseries_by_date.py +++ b/tests/fitbit_client/resources/body_timeseries/test_get_body_timeseries_by_date.py @@ -54,11 +54,11 @@ def test_get_body_timeseries_by_date_period_validation(body_timeseries): ) -def test_get_body_timeseries_by_date_successful_flow(body_timeseries, mock_response): +def test_get_body_timeseries_by_date_successful_flow(body_timeseries, mock_response_factory): """Test the successful flow through validation and request for get_body_timeseries_by_date.""" # Set up the mock response + mock_response = mock_response_factory(200, {"expected": "response"}) body_timeseries.oauth.request.return_value = mock_response - mock_response.json.return_value = {"expected": "response"} # Test with BMI resource type (which allows all periods) result = body_timeseries.get_body_timeseries_by_date( @@ -103,11 +103,11 @@ def test_get_body_timeseries_by_date_successful_flow(body_timeseries, mock_respo ) -def test_get_body_timeseries_by_date_makes_correct_request(body_timeseries, mock_response): +def test_get_body_timeseries_by_date_makes_correct_request(body_timeseries, mock_response_factory): """Test that the correct endpoint is called with proper parameters.""" # Set up the mock response + mock_response = mock_response_factory(200, {"expected": "response"}) body_timeseries.oauth.request.return_value = mock_response - mock_response.json.return_value = {"expected": "response"} # Call the method result = body_timeseries.get_body_timeseries_by_date( diff --git a/tests/fitbit_client/resources/body_timeseries/test_get_weight_timeseries_by_date.py b/tests/fitbit_client/resources/body_timeseries/test_get_weight_timeseries_by_date.py index eadf9c1..c96b441 100644 --- a/tests/fitbit_client/resources/body_timeseries/test_get_weight_timeseries_by_date.py +++ b/tests/fitbit_client/resources/body_timeseries/test_get_weight_timeseries_by_date.py @@ -36,11 +36,13 @@ def test_get_weight_timeseries_by_date_period_validation(body_timeseries): assert f"Period {period.value} not supported for weight" in str(exc_info.value) -def test_get_weight_timeseries_by_date_makes_correct_request(body_timeseries, mock_response): +def test_get_weight_timeseries_by_date_makes_correct_request( + body_timeseries, mock_response_factory +): """Test that the correct endpoint is called with proper parameters.""" # Set up the mock response + mock_response = mock_response_factory(200, {"expected": "response"}) body_timeseries.oauth.request.return_value = mock_response - mock_response.json.return_value = {"expected": "response"} # Call the method result = body_timeseries.get_weight_timeseries_by_date( diff --git a/tests/fitbit_client/resources/body_timeseries/test_get_weight_timeseries_by_date_range.py b/tests/fitbit_client/resources/body_timeseries/test_get_weight_timeseries_by_date_range.py index 0833a11..aa18580 100644 --- a/tests/fitbit_client/resources/body_timeseries/test_get_weight_timeseries_by_date_range.py +++ b/tests/fitbit_client/resources/body_timeseries/test_get_weight_timeseries_by_date_range.py @@ -42,11 +42,13 @@ def test_get_weight_timeseries_by_date_range_max_days(body_timeseries): assert "weight" in str(exc_info.value) -def test_get_weight_timeseries_by_date_range_makes_correct_request(body_timeseries, mock_response): +def test_get_weight_timeseries_by_date_range_makes_correct_request( + body_timeseries, mock_response_factory +): """Test that the correct endpoint is called with proper parameters.""" # Set up the mock response + mock_response = mock_response_factory(200, {"expected": "response"}) body_timeseries.oauth.request.return_value = mock_response - mock_response.json.return_value = {"expected": "response"} # Call the method with a valid date range (under 31 days) result = body_timeseries.get_weight_timeseries_by_date_range( diff --git a/tests/fitbit_client/resources/friends/test_get_friends.py b/tests/fitbit_client/resources/friends/test_get_friends.py index 8f9b079..7e716b0 100644 --- a/tests/fitbit_client/resources/friends/test_get_friends.py +++ b/tests/fitbit_client/resources/friends/test_get_friends.py @@ -3,23 +3,25 @@ """Tests for the get_friends endpoint.""" -def test_get_friends(friends_resource, mock_oauth_session, mock_response): +def test_get_friends(friends_resource, mock_oauth_session, mock_response_factory): """Test getting friends list""" - mock_response.json.return_value = { - "data": [ - { - "type": "person", - "id": "ABC123", - "attributes": { - "avatar": "http://example.com/avatar.jpg", - "child": False, - "friend": True, - "name": "Test User", - }, - } - ] - } - mock_response.headers = {"content-type": "application/json"} + mock_response = mock_response_factory( + 200, + { + "data": [ + { + "type": "person", + "id": "ABC123", + "attributes": { + "avatar": "http://example.com/avatar.jpg", + "child": False, + "friend": True, + "name": "Test User", + }, + } + ] + }, + ) mock_oauth_session.request.return_value = mock_response result = friends_resource.get_friends() assert len(result) == 1 diff --git a/tests/fitbit_client/resources/heartrate_timeseries/test_get_heartrate_timeseries_by_date.py b/tests/fitbit_client/resources/heartrate_timeseries/test_get_heartrate_timeseries_by_date.py index c600651..21f364d 100644 --- a/tests/fitbit_client/resources/heartrate_timeseries/test_get_heartrate_timeseries_by_date.py +++ b/tests/fitbit_client/resources/heartrate_timeseries/test_get_heartrate_timeseries_by_date.py @@ -15,7 +15,7 @@ def test_get_heartrate_timeseries_by_date_success( - heartrate_resource, mock_oauth_session, mock_response + heartrate_resource, mock_oauth_session, mock_response_factory ): """Test successful retrieval of heart rate data by date and period""" response_data = { @@ -38,7 +38,7 @@ def test_get_heartrate_timeseries_by_date_success( } ] } - mock_response.json.return_value = response_data + mock_response = mock_response_factory(200, response_data) mock_oauth_session.request.return_value = mock_response result = heartrate_resource.get_heartrate_timeseries_by_date( date="2024-02-10", period=Period.ONE_DAY @@ -54,9 +54,9 @@ def test_get_heartrate_timeseries_by_date_success( ) -def test_get_heartrate_timeseries_by_date_today(heartrate_resource, mock_response): +def test_get_heartrate_timeseries_by_date_today(heartrate_resource, mock_response_factory): """Test that 'today' is accepted as a valid date""" - mock_response.json.return_value = {"activities-heart": []} + mock_response = mock_response_factory(200, {"activities-heart": []}) heartrate_resource.oauth.request.return_value = mock_response result = heartrate_resource.get_heartrate_timeseries_by_date( date="today", period=Period.ONE_DAY diff --git a/tests/fitbit_client/resources/nutrition/test_create_food.py b/tests/fitbit_client/resources/nutrition/test_create_food.py index 2fa5416..f542f8f 100644 --- a/tests/fitbit_client/resources/nutrition/test_create_food.py +++ b/tests/fitbit_client/resources/nutrition/test_create_food.py @@ -11,9 +11,11 @@ from fitbit_client.resources._constants import NutritionalValue -def test_create_food_success(nutrition_resource, mock_response): +def test_create_food_success(nutrition_resource, mock_response_factory): """Test successful creation of a new food""" - mock_response.json.return_value = {"foodId": 12345, "name": "Test Food", "calories": 100} + mock_response = mock_response_factory( + 200, {"foodId": 12345, "name": "Test Food", "calories": 100} + ) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.create_food( name="Test Food", @@ -47,9 +49,9 @@ def test_create_food_success(nutrition_resource, mock_response): ) -def test_create_food_with_string_nutritional_values(nutrition_resource, mock_response): +def test_create_food_with_string_nutritional_values(nutrition_resource, mock_response_factory): """Test creating food with string nutritional value keys""" - mock_response.json.return_value = {"foodId": 12345, "name": "Test Food"} + mock_response = mock_response_factory(200, {"foodId": 12345, "name": "Test Food"}) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.create_food( name="Test Food", @@ -102,9 +104,11 @@ def test_create_food_calories_from_fat_must_be_integer(nutrition_resource): assert "Calories from fat must be an integer" in str(exc_info.value) -def test_create_food_with_calories_from_fat(nutrition_resource, mock_response): +def test_create_food_with_calories_from_fat(nutrition_resource, mock_response_factory): """Test creating food with calories from fat as an integer""" - mock_response.json.return_value = {"foodId": 12345, "name": "Test Food", "calories": 100} + mock_response = mock_response_factory( + 200, {"foodId": 12345, "name": "Test Food", "calories": 100} + ) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.create_food( diff --git a/tests/fitbit_client/resources/nutrition/test_create_food_log.py b/tests/fitbit_client/resources/nutrition/test_create_food_log.py index cdc1396..da0b24a 100644 --- a/tests/fitbit_client/resources/nutrition/test_create_food_log.py +++ b/tests/fitbit_client/resources/nutrition/test_create_food_log.py @@ -14,11 +14,11 @@ from fitbit_client.resources._constants import NutritionalValue -def test_create_food_log_with_food_id_success(nutrition_resource, mock_response): +def test_create_food_log_with_food_id_success(nutrition_resource, mock_response_factory): """Test successful creation of a food log entry using food ID""" - mock_response.json.return_value = { - "foodLog": {"logId": 12345, "loggedFood": {"foodId": 67890, "amount": 1.0}} - } + mock_response = mock_response_factory( + 200, {"foodLog": {"logId": 12345, "loggedFood": {"foodId": 67890, "amount": 1.0}}} + ) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.create_food_log( date="2025-02-08", @@ -46,11 +46,11 @@ def test_create_food_log_with_food_id_success(nutrition_resource, mock_response) ) -def test_create_food_log_with_custom_food_success(nutrition_resource, mock_response): +def test_create_food_log_with_custom_food_success(nutrition_resource, mock_response_factory): """Test successful creation of a food log entry using custom food details""" - mock_response.json.return_value = { - "foodLog": {"logId": 12345, "loggedFood": {"name": "Custom Food", "amount": 1.0}} - } + mock_response = mock_response_factory( + 200, {"foodLog": {"logId": 12345, "loggedFood": {"name": "Custom Food", "amount": 1.0}}} + ) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.create_food_log( date="2025-02-08", @@ -86,11 +86,11 @@ def test_create_food_log_with_custom_food_success(nutrition_resource, mock_respo ) -def test_create_food_log_with_favorite_flag(nutrition_resource, mock_response): +def test_create_food_log_with_favorite_flag(nutrition_resource, mock_response_factory): """Test that creating a food log with favorite=True sets the flag correctly""" - mock_response.json.return_value = { - "foodLog": {"logId": 12345, "loggedFood": {"foodId": 67890, "amount": 1.0}} - } + mock_response = mock_response_factory( + 200, {"foodLog": {"logId": 12345, "loggedFood": {"foodId": 67890, "amount": 1.0}}} + ) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.create_food_log( date="2025-02-08", @@ -142,9 +142,9 @@ def test_create_food_log_with_favorite_flag(nutrition_resource, mock_response): ) -def test_create_food_log_with_brand_name_only(nutrition_resource, mock_response): +def test_create_food_log_with_brand_name_only(nutrition_resource, mock_response_factory): """Test creating food log with only brand name (lines 172-174)""" - mock_response.json.return_value = {"foodLog": {"logId": 12345}} + mock_response = mock_response_factory(200, {"foodLog": {"logId": 12345}}) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.create_food_log( date="2025-02-08", @@ -174,9 +174,9 @@ def test_create_food_log_with_brand_name_only(nutrition_resource, mock_response) ) -def test_create_food_log_none_handling(nutrition_resource, mock_response): +def test_create_food_log_none_handling(nutrition_resource, mock_response_factory): """Test handling of None values for food_name and calories""" - mock_response.json.return_value = {"foodLog": {"logId": 12345}} + mock_response = mock_response_factory(200, {"foodLog": {"logId": 12345}}) nutrition_resource.oauth.request.return_value = mock_response # Save original method @@ -202,7 +202,7 @@ def test_method(date, meal_type_id, unit_id, amount, **kwargs): if calories is not None: params["calories"] = calories - return mock_response.json.return_value + return mock_response.json() try: # Replace with our test method @@ -260,10 +260,9 @@ def test_create_food_log_invalid_date(nutrition_resource): ) -def test_create_food_log_allows_today(nutrition_resource, mock_response): +def test_create_food_log_allows_today(nutrition_resource, mock_response_factory): """Test that 'today' is accepted as a valid date""" - mock_response.json.return_value = {"foodLog": {"logId": 12345}} - mock_response.headers = {"content-type": "application/json"} + mock_response = mock_response_factory(200, {"foodLog": {"logId": 12345}}) nutrition_resource.oauth.request.return_value = mock_response nutrition_resource.create_food_log( date="today", meal_type_id=MealType.BREAKFAST, unit_id=147, amount=100.0, food_id=67890 diff --git a/tests/fitbit_client/resources/nutrition/test_delete_custom_food.py b/tests/fitbit_client/resources/nutrition/test_delete_custom_food.py index 002d5aa..e3ee88d 100644 --- a/tests/fitbit_client/resources/nutrition/test_delete_custom_food.py +++ b/tests/fitbit_client/resources/nutrition/test_delete_custom_food.py @@ -3,9 +3,9 @@ """Tests for the delete_custom_food endpoint.""" -def test_delete_custom_food_success(nutrition_resource, mock_response): +def test_delete_custom_food_success(nutrition_resource, mock_response_factory): """Test successful deletion of a custom food""" - mock_response.status_code = 204 + mock_response = mock_response_factory(204) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.delete_custom_food(food_id=12345) assert result is None diff --git a/tests/fitbit_client/resources/nutrition/test_delete_food_log.py b/tests/fitbit_client/resources/nutrition/test_delete_food_log.py index a7cd018..bd0a88b 100644 --- a/tests/fitbit_client/resources/nutrition/test_delete_food_log.py +++ b/tests/fitbit_client/resources/nutrition/test_delete_food_log.py @@ -3,9 +3,9 @@ """Tests for the delete_food_log endpoint.""" -def test_delete_food_log_success(nutrition_resource, mock_response): +def test_delete_food_log_success(nutrition_resource, mock_response_factory): """Test successful deletion of a food log entry""" - mock_response.status_code = 204 + mock_response = mock_response_factory(204) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.delete_food_log(food_log_id=12345) assert result is None diff --git a/tests/fitbit_client/resources/nutrition/test_delete_water_log.py b/tests/fitbit_client/resources/nutrition/test_delete_water_log.py index 7434b57..180652f 100644 --- a/tests/fitbit_client/resources/nutrition/test_delete_water_log.py +++ b/tests/fitbit_client/resources/nutrition/test_delete_water_log.py @@ -3,9 +3,9 @@ """Tests for the delete_water_log endpoint.""" -def test_delete_water_log_success(nutrition_resource, mock_response): +def test_delete_water_log_success(nutrition_resource, mock_response_factory): """Test successful deletion of a water log entry""" - mock_response.status_code = 204 + mock_response = mock_response_factory(204) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.delete_water_log(water_log_id=12345) assert result is None diff --git a/tests/fitbit_client/resources/nutrition/test_get_frequent_foods.py b/tests/fitbit_client/resources/nutrition/test_get_frequent_foods.py index 2a5ba86..8f4baf5 100644 --- a/tests/fitbit_client/resources/nutrition/test_get_frequent_foods.py +++ b/tests/fitbit_client/resources/nutrition/test_get_frequent_foods.py @@ -3,11 +3,11 @@ """Tests for the get_frequent_foods endpoint.""" -def test_get_frequent_foods_success(nutrition_resource, mock_response): +def test_get_frequent_foods_success(nutrition_resource, mock_response_factory): """Test successful retrieval of frequent foods""" - mock_response.json.return_value = [ - {"foodId": 12345, "name": "Test Food", "amount": 100.0, "mealTypeId": 1} - ] + mock_response = mock_response_factory( + 200, [{"foodId": 12345, "name": "Test Food", "amount": 100.0, "mealTypeId": 1}] + ) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.get_frequent_foods() assert result == mock_response.json.return_value diff --git a/tests/fitbit_client/resources/nutrition/test_update_food_log.py b/tests/fitbit_client/resources/nutrition/test_update_food_log.py index 7c17b8d..c7cdb65 100644 --- a/tests/fitbit_client/resources/nutrition/test_update_food_log.py +++ b/tests/fitbit_client/resources/nutrition/test_update_food_log.py @@ -12,11 +12,11 @@ from fitbit_client.resources._constants import MealType -def test_update_food_log_with_unit_amount_success(nutrition_resource, mock_response): +def test_update_food_log_with_unit_amount_success(nutrition_resource, mock_response_factory): """Test successful update of a food log entry with unit and amount""" - mock_response.json.return_value = { - "foodLog": {"logId": 12345, "loggedFood": {"foodId": 67890, "amount": 200.0}} - } + mock_response = mock_response_factory( + 200, {"foodLog": {"logId": 12345, "loggedFood": {"foodId": 67890, "amount": 200.0}}} + ) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.update_food_log( food_log_id=12345, meal_type_id=MealType.LUNCH, unit_id=147, amount=200.0 @@ -32,9 +32,11 @@ def test_update_food_log_with_unit_amount_success(nutrition_resource, mock_respo ) -def test_update_food_log_with_calories_success(nutrition_resource, mock_response): +def test_update_food_log_with_calories_success(nutrition_resource, mock_response_factory): """Test successful update of a food log entry with calories""" - mock_response.json.return_value = {"foodLog": {"logId": 12345, "loggedFood": {"calories": 300}}} + mock_response = mock_response_factory( + 200, {"foodLog": {"logId": 12345, "loggedFood": {"calories": 300}}} + ) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.update_food_log( food_log_id=12345, meal_type_id=MealType.LUNCH, calories=300 From a3a4bdf76bfb77e7144d851e6c9985e6bd1b4e51 Mon Sep 17 00:00:00 2001 From: Jon Stroop Date: Mon, 10 Mar 2025 15:49:50 -0700 Subject: [PATCH 2/6] Continue standardizing on mock_response_factory - Update more test files to use mock_response_factory consistently - Replace direct manipulations of mock_response with factory usage in: - nutrition/test_get_food.py - nutrition/test_get_food_goals.py - friends/test_get_friends_leaderboard.py - nutrition/test_delete_meal.py - activity_timeseries/test_get_activity_timeseries.py - active_zone_minutes/test_get_azm_timeseries_by_date.py --- .../test_get_azm_timeseries_by_date.py | 39 +++++++++--------- .../test_get_activity_timeseries.py | 12 ++++-- .../friends/test_get_friends_leaderboard.py | 40 ++++++++++--------- .../resources/nutrition/test_delete_meal.py | 4 +- .../resources/nutrition/test_get_food.py | 16 ++++++-- .../nutrition/test_get_food_goals.py | 9 ++--- 6 files changed, 68 insertions(+), 52 deletions(-) diff --git a/tests/fitbit_client/resources/active_zone_minutes/test_get_azm_timeseries_by_date.py b/tests/fitbit_client/resources/active_zone_minutes/test_get_azm_timeseries_by_date.py index 6882d6f..031f7c3 100644 --- a/tests/fitbit_client/resources/active_zone_minutes/test_get_azm_timeseries_by_date.py +++ b/tests/fitbit_client/resources/active_zone_minutes/test_get_azm_timeseries_by_date.py @@ -13,21 +13,24 @@ from fitbit_client.resources._constants import Period -def test_get_azm_timeseries_by_date_success(azm_resource, mock_response): +def test_get_azm_timeseries_by_date_success(azm_resource, mock_response_factory): """Test successful retrieval of AZM time series by date with default period""" - mock_response.json.return_value = { - "activities-active-zone-minutes": [ - { - "dateTime": "2025-02-01", - "value": { - "activeZoneMinutes": 102, - "fatBurnActiveZoneMinutes": 90, - "cardioActiveZoneMinutes": 8, - "peakActiveZoneMinutes": 4, - }, - } - ] - } + mock_response = mock_response_factory( + 200, + { + "activities-active-zone-minutes": [ + { + "dateTime": "2025-02-01", + "value": { + "activeZoneMinutes": 102, + "fatBurnActiveZoneMinutes": 90, + "cardioActiveZoneMinutes": 8, + "peakActiveZoneMinutes": 4, + }, + } + ] + }, + ) azm_resource.oauth.request.return_value = mock_response result = azm_resource.get_azm_timeseries_by_date(date="2025-02-01") assert result == mock_response.json.return_value @@ -41,9 +44,9 @@ def test_get_azm_timeseries_by_date_success(azm_resource, mock_response): ) -def test_get_azm_timeseries_by_date_explicit_period(azm_resource, mock_response): +def test_get_azm_timeseries_by_date_explicit_period(azm_resource, mock_response_factory): """Test successful retrieval of AZM time series by date with explicit ONE_DAY period""" - mock_response.json.return_value = {"activities-active-zone-minutes": []} + mock_response = mock_response_factory(200, {"activities-active-zone-minutes": []}) azm_resource.oauth.request.return_value = mock_response result = azm_resource.get_azm_timeseries_by_date(date="2025-02-01", period=Period.ONE_DAY) assert result == mock_response.json.return_value @@ -57,9 +60,9 @@ def test_get_azm_timeseries_by_date_explicit_period(azm_resource, mock_response) ) -def test_get_azm_timeseries_by_date_with_user_id(azm_resource, mock_response): +def test_get_azm_timeseries_by_date_with_user_id(azm_resource, mock_response_factory): """Test getting AZM time series for a specific user""" - mock_response.json.return_value = {"activities-active-zone-minutes": []} + mock_response = mock_response_factory(200, {"activities-active-zone-minutes": []}) azm_resource.oauth.request.return_value = mock_response result = azm_resource.get_azm_timeseries_by_date(date="2025-02-01", user_id="123ABC") assert result == mock_response.json.return_value diff --git a/tests/fitbit_client/resources/activity_timeseries/test_get_activity_timeseries.py b/tests/fitbit_client/resources/activity_timeseries/test_get_activity_timeseries.py index 9995eba..e89181c 100644 --- a/tests/fitbit_client/resources/activity_timeseries/test_get_activity_timeseries.py +++ b/tests/fitbit_client/resources/activity_timeseries/test_get_activity_timeseries.py @@ -9,9 +9,11 @@ from fitbit_client.resources._constants import Period -def test_get_activity_timeseries_with_today_date(activity_timeseries_resource, mock_response): +def test_get_activity_timeseries_with_today_date( + activity_timeseries_resource, mock_response_factory +): """Test using 'today' as the date parameter""" - mock_response.json.return_value = {"activities-steps": []} + mock_response = mock_response_factory(200, {"activities-steps": []}) activity_timeseries_resource.oauth.request.return_value = mock_response activity_timeseries_resource.get_activity_timeseries_by_date( resource_path=ActivityTimeSeriesPath.STEPS, date="today", period=Period.ONE_DAY @@ -26,9 +28,11 @@ def test_get_activity_timeseries_with_today_date(activity_timeseries_resource, m ) -def test_get_activity_timeseries_different_periods(activity_timeseries_resource, mock_response): +def test_get_activity_timeseries_different_periods( + activity_timeseries_resource, mock_response_factory +): """Test getting time series with different period values""" - mock_response.json.return_value = {"activities-steps": []} + mock_response = mock_response_factory(200, {"activities-steps": []}) activity_timeseries_resource.oauth.request.return_value = mock_response periods = [ Period.ONE_DAY, diff --git a/tests/fitbit_client/resources/friends/test_get_friends_leaderboard.py b/tests/fitbit_client/resources/friends/test_get_friends_leaderboard.py index bec034a..30c0cf1 100644 --- a/tests/fitbit_client/resources/friends/test_get_friends_leaderboard.py +++ b/tests/fitbit_client/resources/friends/test_get_friends_leaderboard.py @@ -3,26 +3,28 @@ """Tests for the get_friends_leaderboard endpoint.""" -def test_get_friends_leaderboard(friends_resource, mock_oauth_session, mock_response): +def test_get_friends_leaderboard(friends_resource, mock_oauth_session, mock_response_factory): """Test getting friends leaderboard""" - mock_response.json.return_value = { - "data": [ - { - "type": "ranked-user", - "id": "ABC123", - "attributes": {"step-rank": 1, "step-summary": 50000}, - } - ], - "included": [ - { - "avatar": "http://example.com/avatar.jpg", - "child": False, - "friend": True, - "name": "Test User", - } - ], - } - mock_response.headers = {"content-type": "application/json"} + mock_response = mock_response_factory( + 200, + { + "data": [ + { + "type": "ranked-user", + "id": "ABC123", + "attributes": {"step-rank": 1, "step-summary": 50000}, + } + ], + "included": [ + { + "avatar": "http://example.com/avatar.jpg", + "child": False, + "friend": True, + "name": "Test User", + } + ], + }, + ) mock_oauth_session.request.return_value = mock_response result = friends_resource.get_friends_leaderboard() assert "data" in result diff --git a/tests/fitbit_client/resources/nutrition/test_delete_meal.py b/tests/fitbit_client/resources/nutrition/test_delete_meal.py index 8f5ddee..e859fb5 100644 --- a/tests/fitbit_client/resources/nutrition/test_delete_meal.py +++ b/tests/fitbit_client/resources/nutrition/test_delete_meal.py @@ -3,9 +3,9 @@ """Tests for the delete_meal endpoint.""" -def test_delete_meal_success(nutrition_resource, mock_response): +def test_delete_meal_success(nutrition_resource, mock_response_factory): """Test successful deletion of a meal""" - mock_response.status_code = 204 + mock_response = mock_response_factory(204) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.delete_meal(meal_id=12345) assert result is None diff --git a/tests/fitbit_client/resources/nutrition/test_get_food.py b/tests/fitbit_client/resources/nutrition/test_get_food.py index f096cfd..3475cb3 100644 --- a/tests/fitbit_client/resources/nutrition/test_get_food.py +++ b/tests/fitbit_client/resources/nutrition/test_get_food.py @@ -3,11 +3,19 @@ """Tests for the get_food endpoint.""" -def test_get_food_success(nutrition_resource, mock_response): +def test_get_food_success(nutrition_resource, mock_response_factory): """Test successful retrieval of food details""" - mock_response.json.return_value = { - "food": {"foodId": 12345, "name": "Test Food", "calories": 100, "defaultServingSize": 100.0} - } + mock_response = mock_response_factory( + 200, + { + "food": { + "foodId": 12345, + "name": "Test Food", + "calories": 100, + "defaultServingSize": 100.0, + } + }, + ) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.get_food(food_id=12345) assert result == mock_response.json.return_value diff --git a/tests/fitbit_client/resources/nutrition/test_get_food_goals.py b/tests/fitbit_client/resources/nutrition/test_get_food_goals.py index f67f17e..cf90fc6 100644 --- a/tests/fitbit_client/resources/nutrition/test_get_food_goals.py +++ b/tests/fitbit_client/resources/nutrition/test_get_food_goals.py @@ -3,12 +3,11 @@ """Tests for the get_food_goals endpoint.""" -def test_get_food_goals_success(nutrition_resource, mock_response): +def test_get_food_goals_success(nutrition_resource, mock_response_factory): """Test successful retrieval of food goals""" - mock_response.json.return_value = { - "goals": {"calories": 2000}, - "foodPlan": {"intensity": "MAINTENANCE"}, - } + mock_response = mock_response_factory( + 200, {"goals": {"calories": 2000}, "foodPlan": {"intensity": "MAINTENANCE"}} + ) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.get_food_goals() assert result == mock_response.json.return_value From 27e9a55fa4e18ce78733a49b53a489821844b9cb Mon Sep 17 00:00:00 2001 From: Jon Stroop Date: Mon, 10 Mar 2025 15:57:43 -0700 Subject: [PATCH 3/6] Continue standardizing on mock_response_factory - Update more test files to consistently use mock_response_factory: - nutrition/test_get_food_locales.py - nutrition/test_get_food_units.py - body_timeseries/test_get_body_timeseries_by_date_range.py - nutrition/test_get_recent_foods.py - nutrition/test_get_favorite_foods.py - activity_timeseries/test_get_activity_timeseries_by_date_range.py - active_zone_minutes/test_get_azm_timeseries_by_interval.py - nutrition/test_get_meal.py - nutrition/test_update_meal.py This continues the standardization effort to ensure consistent usage of mock_response_factory throughout the test suite. --- .../test_get_azm_timeseries_by_interval.py | 47 ++++++++++--------- .../test_get_activity_timeseries_by_date.py | 31 +++++++----- ...t_get_activity_timeseries_by_date_range.py | 23 +++++---- .../test_get_body_timeseries_by_date_range.py | 6 ++- .../test_get_bodyfat_timeseries_by_date.py | 6 ++- ...st_get_bodyfat_timeseries_by_date_range.py | 6 ++- ..._get_heartrate_timeseries_by_date_range.py | 8 ++-- .../nutrition/test_get_favorite_foods.py | 8 ++-- .../nutrition/test_get_food_locales.py | 13 +++-- .../resources/nutrition/test_get_food_log.py | 18 +++---- .../nutrition/test_get_food_units.py | 13 +++-- .../resources/nutrition/test_get_meal.py | 21 +++++---- .../resources/nutrition/test_get_meals.py | 25 +++++----- .../nutrition/test_get_recent_foods.py | 9 ++-- .../resources/nutrition/test_get_water_log.py | 14 +++--- .../resources/nutrition/test_search_foods.py | 9 ++-- .../resources/nutrition/test_update_meal.py | 21 +++++---- .../nutrition/test_update_water_log.py | 8 ++-- 18 files changed, 162 insertions(+), 124 deletions(-) diff --git a/tests/fitbit_client/resources/active_zone_minutes/test_get_azm_timeseries_by_interval.py b/tests/fitbit_client/resources/active_zone_minutes/test_get_azm_timeseries_by_interval.py index 1d97f67..0285a17 100644 --- a/tests/fitbit_client/resources/active_zone_minutes/test_get_azm_timeseries_by_interval.py +++ b/tests/fitbit_client/resources/active_zone_minutes/test_get_azm_timeseries_by_interval.py @@ -16,29 +16,32 @@ from fitbit_client.exceptions import InvalidDateRangeException -def test_get_azm_timeseries_by_interval_success(azm_resource, mock_response): +def test_get_azm_timeseries_by_interval_success(azm_resource, mock_response_factory): """Test successful retrieval of AZM time series by date range""" - mock_response.json.return_value = { - "activities-active-zone-minutes": [ - { - "dateTime": "2025-02-01", - "value": { - "activeZoneMinutes": 102, - "fatBurnActiveZoneMinutes": 90, - "cardioActiveZoneMinutes": 8, - "peakActiveZoneMinutes": 4, + mock_response = mock_response_factory( + 200, + { + "activities-active-zone-minutes": [ + { + "dateTime": "2025-02-01", + "value": { + "activeZoneMinutes": 102, + "fatBurnActiveZoneMinutes": 90, + "cardioActiveZoneMinutes": 8, + "peakActiveZoneMinutes": 4, + }, }, - }, - { - "dateTime": "2025-02-02", - "value": { - "activeZoneMinutes": 47, - "fatBurnActiveZoneMinutes": 43, - "cardioActiveZoneMinutes": 4, + { + "dateTime": "2025-02-02", + "value": { + "activeZoneMinutes": 47, + "fatBurnActiveZoneMinutes": 43, + "cardioActiveZoneMinutes": 4, + }, }, - }, - ] - } + ] + }, + ) azm_resource.oauth.request.return_value = mock_response result = azm_resource.get_azm_timeseries_by_interval( start_date="2025-02-01", end_date="2025-02-02" @@ -54,9 +57,9 @@ def test_get_azm_timeseries_by_interval_success(azm_resource, mock_response): ) -def test_get_azm_timeseries_by_interval_with_user_id(azm_resource, mock_response): +def test_get_azm_timeseries_by_interval_with_user_id(azm_resource, mock_response_factory): """Test getting AZM time series by date range for a specific user""" - mock_response.json.return_value = {"activities-active-zone-minutes": []} + mock_response = mock_response_factory(200, {"activities-active-zone-minutes": []}) azm_resource.oauth.request.return_value = mock_response result = azm_resource.get_azm_timeseries_by_interval( start_date="2025-02-01", end_date="2025-02-02", user_id="123ABC" diff --git a/tests/fitbit_client/resources/activity_timeseries/test_get_activity_timeseries_by_date.py b/tests/fitbit_client/resources/activity_timeseries/test_get_activity_timeseries_by_date.py index 4ad2b79..0435236 100644 --- a/tests/fitbit_client/resources/activity_timeseries/test_get_activity_timeseries_by_date.py +++ b/tests/fitbit_client/resources/activity_timeseries/test_get_activity_timeseries_by_date.py @@ -13,11 +13,13 @@ from fitbit_client.resources._constants import Period -def test_get_activity_timeseries_by_date_success(activity_timeseries_resource, mock_response): +def test_get_activity_timeseries_by_date_success( + activity_timeseries_resource, mock_response_factory +): """Test successful retrieval of activity time series by date""" - mock_response.json.return_value = { - "activities-steps": [{"dateTime": "2024-02-01", "value": "10000"}] - } + mock_response = mock_response_factory( + 200, {"activities-steps": [{"dateTime": "2024-02-01", "value": "10000"}]} + ) activity_timeseries_resource.oauth.request.return_value = mock_response result = activity_timeseries_resource.get_activity_timeseries_by_date( resource_path=ActivityTimeSeriesPath.STEPS, date="2024-02-01", period=Period.ONE_DAY @@ -33,9 +35,11 @@ def test_get_activity_timeseries_by_date_success(activity_timeseries_resource, m ) -def test_get_activity_timeseries_by_date_with_user_id(activity_timeseries_resource, mock_response): +def test_get_activity_timeseries_by_date_with_user_id( + activity_timeseries_resource, mock_response_factory +): """Test getting time series for a specific user""" - mock_response.json.return_value = {"activities-steps": []} + mock_response = mock_response_factory(200, {"activities-steps": []}) activity_timeseries_resource.oauth.request.return_value = mock_response result = activity_timeseries_resource.get_activity_timeseries_by_date( resource_path=ActivityTimeSeriesPath.STEPS, @@ -68,13 +72,16 @@ def test_get_activity_timeseries_by_date_invalid_date(activity_timeseries_resour # Local imports -def test_calories_variants(activity_timeseries_resource, mock_response): +def test_calories_variants(activity_timeseries_resource, mock_response_factory): """Test different calorie measurement types return expected data""" - mock_response.json.return_value = { - "activities-activityCalories": [{"dateTime": "2024-02-01", "value": "300"}], - "activities-calories": [{"dateTime": "2024-02-01", "value": "2000"}], - "activities-caloriesBMR": [{"dateTime": "2024-02-01", "value": "1700"}], - } + mock_response = mock_response_factory( + 200, + { + "activities-activityCalories": [{"dateTime": "2024-02-01", "value": "300"}], + "activities-calories": [{"dateTime": "2024-02-01", "value": "2000"}], + "activities-caloriesBMR": [{"dateTime": "2024-02-01", "value": "1700"}], + }, + ) activity_timeseries_resource.oauth.request.return_value = mock_response calorie_types = [ ActivityTimeSeriesPath.ACTIVITY_CALORIES, diff --git a/tests/fitbit_client/resources/activity_timeseries/test_get_activity_timeseries_by_date_range.py b/tests/fitbit_client/resources/activity_timeseries/test_get_activity_timeseries_by_date_range.py index b7db8a1..d55f5d1 100644 --- a/tests/fitbit_client/resources/activity_timeseries/test_get_activity_timeseries_by_date_range.py +++ b/tests/fitbit_client/resources/activity_timeseries/test_get_activity_timeseries_by_date_range.py @@ -14,14 +14,19 @@ from fitbit_client.resources._constants import ActivityTimeSeriesPath -def test_get_activity_timeseries_by_date_range_success(activity_timeseries_resource, mock_response): +def test_get_activity_timeseries_by_date_range_success( + activity_timeseries_resource, mock_response_factory +): """Test successful retrieval of activity time series by date range""" - mock_response.json.return_value = { - "activities-steps": [ - {"dateTime": "2024-02-01", "value": "10000"}, - {"dateTime": "2024-02-02", "value": "12000"}, - ] - } + mock_response = mock_response_factory( + 200, + { + "activities-steps": [ + {"dateTime": "2024-02-01", "value": "10000"}, + {"dateTime": "2024-02-02", "value": "12000"}, + ] + }, + ) activity_timeseries_resource.oauth.request.return_value = mock_response result = activity_timeseries_resource.get_activity_timeseries_by_date_range( resource_path=ActivityTimeSeriesPath.STEPS, start_date="2024-02-01", end_date="2024-02-02" @@ -43,10 +48,10 @@ def test_get_activity_timeseries_by_date_range_success(activity_timeseries_resou def test_get_activity_timeseries_by_date_range_with_user_id( - activity_timeseries_resource, mock_response + activity_timeseries_resource, mock_response_factory ): """Test getting time series by date range for a specific user""" - mock_response.json.return_value = {"activities-steps": []} + mock_response = mock_response_factory(200, {"activities-steps": []}) activity_timeseries_resource.oauth.request.return_value = mock_response result = activity_timeseries_resource.get_activity_timeseries_by_date_range( resource_path=ActivityTimeSeriesPath.STEPS, diff --git a/tests/fitbit_client/resources/body_timeseries/test_get_body_timeseries_by_date_range.py b/tests/fitbit_client/resources/body_timeseries/test_get_body_timeseries_by_date_range.py index 3fee224..35374cc 100644 --- a/tests/fitbit_client/resources/body_timeseries/test_get_body_timeseries_by_date_range.py +++ b/tests/fitbit_client/resources/body_timeseries/test_get_body_timeseries_by_date_range.py @@ -54,11 +54,13 @@ def test_get_body_timeseries_by_date_range_max_days(body_timeseries): ) -def test_get_body_timeseries_by_date_range_makes_correct_request(body_timeseries, mock_response): +def test_get_body_timeseries_by_date_range_makes_correct_request( + body_timeseries, mock_response_factory +): """Test that the correct endpoint is called with proper parameters.""" # Set up the mock response + mock_response = mock_response_factory(200, {"expected": "response"}) body_timeseries.oauth.request.return_value = mock_response - mock_response.json.return_value = {"expected": "response"} # Call the method with valid parameters that don't exceed range limits result = body_timeseries.get_body_timeseries_by_date_range( diff --git a/tests/fitbit_client/resources/body_timeseries/test_get_bodyfat_timeseries_by_date.py b/tests/fitbit_client/resources/body_timeseries/test_get_bodyfat_timeseries_by_date.py index e4dbe09..37b818e 100644 --- a/tests/fitbit_client/resources/body_timeseries/test_get_bodyfat_timeseries_by_date.py +++ b/tests/fitbit_client/resources/body_timeseries/test_get_bodyfat_timeseries_by_date.py @@ -36,11 +36,13 @@ def test_get_bodyfat_timeseries_by_date_period_validation(body_timeseries): assert f"Period {period.value} not supported for body fat" in str(exc_info.value) -def test_get_bodyfat_timeseries_by_date_makes_correct_request(body_timeseries, mock_response): +def test_get_bodyfat_timeseries_by_date_makes_correct_request( + body_timeseries, mock_response_factory +): """Test that the correct endpoint is called with proper parameters.""" # Set up the mock response + mock_response = mock_response_factory(200, {"expected": "response"}) body_timeseries.oauth.request.return_value = mock_response - mock_response.json.return_value = {"expected": "response"} # Call the method result = body_timeseries.get_bodyfat_timeseries_by_date( diff --git a/tests/fitbit_client/resources/body_timeseries/test_get_bodyfat_timeseries_by_date_range.py b/tests/fitbit_client/resources/body_timeseries/test_get_bodyfat_timeseries_by_date_range.py index 2bb9945..48cb9e6 100644 --- a/tests/fitbit_client/resources/body_timeseries/test_get_bodyfat_timeseries_by_date_range.py +++ b/tests/fitbit_client/resources/body_timeseries/test_get_bodyfat_timeseries_by_date_range.py @@ -42,11 +42,13 @@ def test_get_bodyfat_timeseries_by_date_range_max_days(body_timeseries): assert "body fat" in str(exc_info.value) -def test_get_bodyfat_timeseries_by_date_range_makes_correct_request(body_timeseries, mock_response): +def test_get_bodyfat_timeseries_by_date_range_makes_correct_request( + body_timeseries, mock_response_factory +): """Test that the correct endpoint is called with proper parameters.""" # Set up the mock response + mock_response = mock_response_factory(200, {"expected": "response"}) body_timeseries.oauth.request.return_value = mock_response - mock_response.json.return_value = {"expected": "response"} # Call the method with a valid date range (under 30 days) result = body_timeseries.get_bodyfat_timeseries_by_date_range( diff --git a/tests/fitbit_client/resources/heartrate_timeseries/test_get_heartrate_timeseries_by_date_range.py b/tests/fitbit_client/resources/heartrate_timeseries/test_get_heartrate_timeseries_by_date_range.py index 41faa7b..ee054be 100644 --- a/tests/fitbit_client/resources/heartrate_timeseries/test_get_heartrate_timeseries_by_date_range.py +++ b/tests/fitbit_client/resources/heartrate_timeseries/test_get_heartrate_timeseries_by_date_range.py @@ -13,7 +13,7 @@ from fitbit_client.exceptions import ParameterValidationException -def test_get_heartrate_timeseries_by_date_range_success(heartrate_resource, mock_response): +def test_get_heartrate_timeseries_by_date_range_success(heartrate_resource, mock_response_factory): """Test successful retrieval of heart rate data by date range""" response_data = { "activities-heart": [ @@ -21,7 +21,7 @@ def test_get_heartrate_timeseries_by_date_range_success(heartrate_resource, mock {"dateTime": "2024-02-11", "value": {"restingHeartRate": 68, "heartRateZones": []}}, ] } - mock_response.json.return_value = response_data + mock_response = mock_response_factory(200, response_data) heartrate_resource.oauth.request.return_value = mock_response result = heartrate_resource.get_heartrate_timeseries_by_date_range( start_date="2024-02-10", end_date="2024-02-11" @@ -37,9 +37,9 @@ def test_get_heartrate_timeseries_by_date_range_success(heartrate_resource, mock ) -def test_get_heartrate_timeseries_by_date_range_today(heartrate_resource, mock_response): +def test_get_heartrate_timeseries_by_date_range_today(heartrate_resource, mock_response_factory): """Test that 'today' is accepted in date range""" - mock_response.json.return_value = {"activities-heart": []} + mock_response = mock_response_factory(200, {"activities-heart": []}) heartrate_resource.oauth.request.return_value = mock_response result = heartrate_resource.get_heartrate_timeseries_by_date_range( start_date="today", end_date="today" diff --git a/tests/fitbit_client/resources/nutrition/test_get_favorite_foods.py b/tests/fitbit_client/resources/nutrition/test_get_favorite_foods.py index e5920ab..93ccc71 100644 --- a/tests/fitbit_client/resources/nutrition/test_get_favorite_foods.py +++ b/tests/fitbit_client/resources/nutrition/test_get_favorite_foods.py @@ -3,11 +3,11 @@ """Tests for the get_favorite_foods endpoint.""" -def test_get_favorite_foods_success(nutrition_resource, mock_response): +def test_get_favorite_foods_success(nutrition_resource, mock_response_factory): """Test successful retrieval of favorite foods""" - mock_response.json.return_value = [ - {"foodId": 12345, "name": "Test Food", "defaultServingSize": 100.0, "calories": 100} - ] + mock_response = mock_response_factory( + 200, [{"foodId": 12345, "name": "Test Food", "defaultServingSize": 100.0, "calories": 100}] + ) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.get_favorite_foods() assert result == mock_response.json.return_value diff --git a/tests/fitbit_client/resources/nutrition/test_get_food_locales.py b/tests/fitbit_client/resources/nutrition/test_get_food_locales.py index ddee935..54d0d7b 100644 --- a/tests/fitbit_client/resources/nutrition/test_get_food_locales.py +++ b/tests/fitbit_client/resources/nutrition/test_get_food_locales.py @@ -3,12 +3,15 @@ """Tests for the get_food_locales endpoint.""" -def test_get_food_locales_success(nutrition_resource, mock_response): +def test_get_food_locales_success(nutrition_resource, mock_response_factory): """Test successful retrieval of food locales""" - mock_response.json.return_value = [ - {"value": "en_US", "label": "United States"}, - {"value": "en_GB", "label": "United Kingdom"}, - ] + mock_response = mock_response_factory( + 200, + [ + {"value": "en_US", "label": "United States"}, + {"value": "en_GB", "label": "United Kingdom"}, + ], + ) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.get_food_locales() assert result == mock_response.json.return_value diff --git a/tests/fitbit_client/resources/nutrition/test_get_food_log.py b/tests/fitbit_client/resources/nutrition/test_get_food_log.py index bbee982..82514a3 100644 --- a/tests/fitbit_client/resources/nutrition/test_get_food_log.py +++ b/tests/fitbit_client/resources/nutrition/test_get_food_log.py @@ -11,12 +11,15 @@ from fitbit_client.exceptions import InvalidDateException -def test_get_food_log_success(nutrition_resource, mock_response): +def test_get_food_log_success(nutrition_resource, mock_response_factory): """Test successful retrieval of food log entries""" - mock_response.json.return_value = { - "foods": [{"logId": 12345, "loggedFood": {"foodId": 67890, "amount": 100.0}}], - "summary": {"calories": 500}, - } + mock_response = mock_response_factory( + 200, + { + "foods": [{"logId": 12345, "loggedFood": {"foodId": 67890, "amount": 100.0}}], + "summary": {"calories": 500}, + }, + ) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.get_food_log(date="2025-02-08") assert result == mock_response.json.return_value @@ -36,9 +39,8 @@ def test_get_food_log_invalid_date(nutrition_resource): nutrition_resource.get_food_log("invalid-date") -def test_get_food_log_allows_today(nutrition_resource, mock_response): +def test_get_food_log_allows_today(nutrition_resource, mock_response_factory): """Test that 'today' is accepted as a valid date""" - mock_response.json.return_value = {"foods": []} - mock_response.headers = {"content-type": "application/json"} + mock_response = mock_response_factory(200, {"foods": []}) nutrition_resource.oauth.request.return_value = mock_response nutrition_resource.get_food_log("today") diff --git a/tests/fitbit_client/resources/nutrition/test_get_food_units.py b/tests/fitbit_client/resources/nutrition/test_get_food_units.py index d083384..9249bb4 100644 --- a/tests/fitbit_client/resources/nutrition/test_get_food_units.py +++ b/tests/fitbit_client/resources/nutrition/test_get_food_units.py @@ -3,12 +3,15 @@ """Tests for the get_food_units endpoint.""" -def test_get_food_units_success(nutrition_resource, mock_response): +def test_get_food_units_success(nutrition_resource, mock_response_factory): """Test successful retrieval of food units""" - mock_response.json.return_value = [ - {"id": 147, "name": "gram", "plural": "grams"}, - {"id": 204, "name": "medium", "plural": "mediums"}, - ] + mock_response = mock_response_factory( + 200, + [ + {"id": 147, "name": "gram", "plural": "grams"}, + {"id": 204, "name": "medium", "plural": "mediums"}, + ], + ) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.get_food_units() assert result == mock_response.json.return_value diff --git a/tests/fitbit_client/resources/nutrition/test_get_meal.py b/tests/fitbit_client/resources/nutrition/test_get_meal.py index 08be1fe..ace034e 100644 --- a/tests/fitbit_client/resources/nutrition/test_get_meal.py +++ b/tests/fitbit_client/resources/nutrition/test_get_meal.py @@ -3,16 +3,19 @@ """Tests for the get_meal endpoint.""" -def test_get_meal_success(nutrition_resource, mock_response): +def test_get_meal_success(nutrition_resource, mock_response_factory): """Test successful retrieval of a meal""" - mock_response.json.return_value = { - "meal": { - "id": 12345, - "name": "Test Meal", - "description": "Test meal description", - "mealFoods": [{"foodId": 67890, "amount": 100.0, "unitId": 147}], - } - } + mock_response = mock_response_factory( + 200, + { + "meal": { + "id": 12345, + "name": "Test Meal", + "description": "Test meal description", + "mealFoods": [{"foodId": 67890, "amount": 100.0, "unitId": 147}], + } + }, + ) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.get_meal(meal_id=12345) assert result == mock_response.json.return_value diff --git a/tests/fitbit_client/resources/nutrition/test_get_meals.py b/tests/fitbit_client/resources/nutrition/test_get_meals.py index 9c95a77..8adc6d0 100644 --- a/tests/fitbit_client/resources/nutrition/test_get_meals.py +++ b/tests/fitbit_client/resources/nutrition/test_get_meals.py @@ -3,18 +3,21 @@ """Tests for the get_meals endpoint.""" -def test_get_meals_success(nutrition_resource, mock_response): +def test_get_meals_success(nutrition_resource, mock_response_factory): """Test successful retrieval of all meals""" - mock_response.json.return_value = { - "meals": [ - { - "id": 12345, - "name": "Test Meal", - "description": "Test meal description", - "mealFoods": [{"foodId": 67890, "amount": 100.0, "unitId": 147}], - } - ] - } + mock_response = mock_response_factory( + 200, + { + "meals": [ + { + "id": 12345, + "name": "Test Meal", + "description": "Test meal description", + "mealFoods": [{"foodId": 67890, "amount": 100.0, "unitId": 147}], + } + ] + }, + ) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.get_meals() assert result == mock_response.json.return_value diff --git a/tests/fitbit_client/resources/nutrition/test_get_recent_foods.py b/tests/fitbit_client/resources/nutrition/test_get_recent_foods.py index d48168f..e72f676 100644 --- a/tests/fitbit_client/resources/nutrition/test_get_recent_foods.py +++ b/tests/fitbit_client/resources/nutrition/test_get_recent_foods.py @@ -3,11 +3,12 @@ """Tests for the get_recent_foods endpoint.""" -def test_get_recent_foods_success(nutrition_resource, mock_response): +def test_get_recent_foods_success(nutrition_resource, mock_response_factory): """Test successful retrieval of recent foods""" - mock_response.json.return_value = [ - {"foodId": 12345, "name": "Test Food", "amount": 100.0, "dateLastEaten": "2025-02-08"} - ] + mock_response = mock_response_factory( + 200, + [{"foodId": 12345, "name": "Test Food", "amount": 100.0, "dateLastEaten": "2025-02-08"}], + ) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.get_recent_foods() assert result == mock_response.json.return_value diff --git a/tests/fitbit_client/resources/nutrition/test_get_water_log.py b/tests/fitbit_client/resources/nutrition/test_get_water_log.py index 65dd8d2..4cd4fc5 100644 --- a/tests/fitbit_client/resources/nutrition/test_get_water_log.py +++ b/tests/fitbit_client/resources/nutrition/test_get_water_log.py @@ -11,12 +11,11 @@ from fitbit_client.exceptions import InvalidDateException -def test_get_water_log_success(nutrition_resource, mock_response): +def test_get_water_log_success(nutrition_resource, mock_response_factory): """Test successful retrieval of water log entries""" - mock_response.json.return_value = { - "water": [{"logId": 12345, "amount": 500.0}], - "summary": {"water": 500.0}, - } + mock_response = mock_response_factory( + 200, {"water": [{"logId": 12345, "amount": 500.0}], "summary": {"water": 500.0}} + ) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.get_water_log(date="2025-02-08") assert result == mock_response.json.return_value @@ -36,9 +35,8 @@ def test_get_water_log_invalid_date(nutrition_resource): nutrition_resource.get_water_log("invalid-date") -def test_get_water_log_allows_today(nutrition_resource, mock_response): +def test_get_water_log_allows_today(nutrition_resource, mock_response_factory): """Test that 'today' is accepted as a valid date""" - mock_response.json.return_value = {"water": []} - mock_response.headers = {"content-type": "application/json"} + mock_response = mock_response_factory(200, {"water": []}) nutrition_resource.oauth.request.return_value = mock_response nutrition_resource.get_water_log("today") diff --git a/tests/fitbit_client/resources/nutrition/test_search_foods.py b/tests/fitbit_client/resources/nutrition/test_search_foods.py index d1494bc..e0deea2 100644 --- a/tests/fitbit_client/resources/nutrition/test_search_foods.py +++ b/tests/fitbit_client/resources/nutrition/test_search_foods.py @@ -3,11 +3,12 @@ """Tests for the search_foods endpoint.""" -def test_search_foods_success(nutrition_resource, mock_response): +def test_search_foods_success(nutrition_resource, mock_response_factory): """Test successful food search""" - mock_response.json.return_value = { - "foods": [{"foodId": 12345, "name": "Test Food", "brand": "Test Brand", "calories": 100}] - } + mock_response = mock_response_factory( + 200, + {"foods": [{"foodId": 12345, "name": "Test Food", "brand": "Test Brand", "calories": 100}]}, + ) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.search_foods(query="test food") assert result == mock_response.json.return_value diff --git a/tests/fitbit_client/resources/nutrition/test_update_meal.py b/tests/fitbit_client/resources/nutrition/test_update_meal.py index 36ae33d..7450746 100644 --- a/tests/fitbit_client/resources/nutrition/test_update_meal.py +++ b/tests/fitbit_client/resources/nutrition/test_update_meal.py @@ -3,16 +3,19 @@ """Tests for the update_meal endpoint.""" -def test_update_meal_success(nutrition_resource, mock_response): +def test_update_meal_success(nutrition_resource, mock_response_factory): """Test successful update of a meal""" - mock_response.json.return_value = { - "meal": { - "id": 12345, - "name": "Updated Meal", - "description": "Updated description", - "mealFoods": [{"foodId": 67890, "amount": 200.0, "unitId": 147}], - } - } + mock_response = mock_response_factory( + 200, + { + "meal": { + "id": 12345, + "name": "Updated Meal", + "description": "Updated description", + "mealFoods": [{"foodId": 67890, "amount": 200.0, "unitId": 147}], + } + }, + ) nutrition_resource.oauth.request.return_value = mock_response foods = [{"food_id": 67890, "amount": 200.0, "unit_id": 147}] result = nutrition_resource.update_meal( diff --git a/tests/fitbit_client/resources/nutrition/test_update_water_log.py b/tests/fitbit_client/resources/nutrition/test_update_water_log.py index e472197..8eda88a 100644 --- a/tests/fitbit_client/resources/nutrition/test_update_water_log.py +++ b/tests/fitbit_client/resources/nutrition/test_update_water_log.py @@ -8,9 +8,9 @@ from fitbit_client.resources._constants import WaterUnit -def test_update_water_log_success(nutrition_resource, mock_response): +def test_update_water_log_success(nutrition_resource, mock_response_factory): """Test successful update of a water log entry""" - mock_response.json.return_value = {"waterLog": {"logId": 12345, "amount": 1000.0}} + mock_response = mock_response_factory(200, {"waterLog": {"logId": 12345, "amount": 1000.0}}) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.update_water_log( water_log_id=12345, amount=1000.0, unit=WaterUnit.MILLILITERS @@ -26,9 +26,9 @@ def test_update_water_log_success(nutrition_resource, mock_response): ) -def test_update_water_log_without_unit(nutrition_resource, mock_response): +def test_update_water_log_without_unit(nutrition_resource, mock_response_factory): """Test updating water log without specifying unit (lines 733-735)""" - mock_response.json.return_value = {"waterLog": {"logId": 12345, "amount": 1000.0}} + mock_response = mock_response_factory(200, {"waterLog": {"logId": 12345, "amount": 1000.0}}) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.update_water_log(water_log_id=12345, amount=1000.0) assert result == mock_response.json.return_value From 5d4664aa0e47ba8f34ff3a62dc5e7435658bcf6b Mon Sep 17 00:00:00 2001 From: Jon Stroop Date: Mon, 10 Mar 2025 16:24:25 -0700 Subject: [PATCH 4/6] Standardize mock_response_factory usage in nutrition tests --- standardize_mock_responses.py | 192 ++++++++++++++++++ .../nutrition/test_add_favorite_foods.py | 4 +- .../nutrition/test_create_food_goal.py | 15 +- .../test_create_food_goal_intensity.py | 6 +- .../test_create_food_log_custom_minimal.py | 6 +- .../resources/nutrition/test_create_meal.py | 21 +- .../nutrition/test_create_water_goal.py | 6 +- .../nutrition/test_create_water_log.py | 9 +- .../nutrition/test_custom_user_id.py | 4 +- .../nutrition/test_delete_favorite_foods.py | 4 +- .../nutrition/test_get_water_goal.py | 6 +- 11 files changed, 232 insertions(+), 41 deletions(-) create mode 100755 standardize_mock_responses.py diff --git a/standardize_mock_responses.py b/standardize_mock_responses.py new file mode 100755 index 0000000..e0e1d44 --- /dev/null +++ b/standardize_mock_responses.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +""" +Script to standardize mock_response usage in test files. + +This script finds test files that use direct mock_response manipulation and +converts them to use mock_response_factory instead. +""" + +# Standard library imports +import os +from pathlib import Path +import re +import sys +from typing import List +from typing import Tuple + + +def find_pattern_in_file(file_path: str, pattern: str) -> List[str]: + """Find all matches of pattern in the given file.""" + with open(file_path, "r") as f: + content = f.read() + return re.findall(pattern, content, re.MULTILINE) + + +def transform_file(file_path: str, dry_run: bool = False) -> bool: + """Transform a file to use mock_response_factory.""" + with open(file_path, "r") as f: + content = f.read() + + # Backup original content + original_content = content + + # Pattern 1: def test_*(mock_response) -> def test_*(mock_response_factory) + # Find all test function definitions + test_pattern = r"(def\s+test_\w+\([^)]*)(,\s*mock_response)(\s*[,)].*)" + + def param_replacement(match): + before = match.group(1) + param = match.group(2).replace("mock_response", "mock_response_factory") + after = match.group(3) + return f"{before}{param}{after}" + + content = re.sub(test_pattern, param_replacement, content) + + # Pattern 2: mock_response.json.return_value = {...} + pattern2 = r"([ \t]*)mock_response\.json\.return_value\s*=\s*({[^;]*}|\[[^;]*\])" + + def replacement2(match): + indent = match.group(1) + data = match.group(2).strip() + # Ensure data has balanced braces + open_braces = data.count("{") - data.count("}") + open_brackets = data.count("[") - data.count("]") + + if open_braces != 0 or open_brackets != 0: + # Skip this match as it has unbalanced braces or brackets + return match.group(0) + + return f"{indent}mock_response = mock_response_factory(\n{indent} 200, \n{indent} {data}\n{indent})" + + content = re.sub(pattern2, replacement2, content) + + # Pattern 3: mock_response.status_code = 204 + pattern3 = r"([ \t]*)mock_response\.status_code\s*=\s*(\d+)" + + def replacement3(match): + indent = match.group(1) + status_code = match.group(2) + return f"{indent}mock_response = mock_response_factory({status_code})" + + content = re.sub(pattern3, replacement3, content) + + # Pattern 4: We're disabling this pattern for now as it was causing issues + # The goal was to change order of assignments (response assignment should come before oauth assignment) + # but it was causing syntax errors + """ + pattern4 = r'([ \t]*)(.*?)\.oauth(?:\.|\w+\.)*request\.return_value\s*=\s*mock_response\n([ \t]*)mock_response\s*=\s*mock_response_factory' + + def replacement4(match): + indent1 = match.group(1) + obj = match.group(2) + indent2 = match.group(3) + return f"{indent2}mock_response = mock_response_factory\n{indent1}{obj}.oauth.request.return_value = mock_response" + + content = re.sub(pattern4, replacement4, content) + """ + + # Pattern 5: Fix cases where test was updated to use mock_response_factory as parameter + # but still uses mock_response in the body + pattern5 = r"def\s+test_\w+\([^)]*mock_response_factory[^)]*\).*?(?=\n\s*def|\Z)" + + def fix_mock_response_usage(match): + test_func = match.group(0) + # If the function uses mock_response_factory but also has direct mock_response usage + if ( + "mock_response.json.return_value =" in test_func + or "mock_response.status_code =" in test_func + ): + # Add a mock_response declaration at the beginning of the function body + # Find the first indented line after the function def + lines = test_func.split("\n") + for i, line in enumerate(lines): + if i > 0 and line.strip() and not line.strip().startswith("#"): + indent = re.match(r"(\s*)", line).group(1) + # Insert the mock_response assignment after docstring (if any) + for j in range(i, len(lines)): + if not lines[j].strip().startswith('"""') and not lines[ + j + ].strip().startswith("'''"): + lines.insert(j, f"{indent}mock_response = mock_response_factory(200)") + break + break + return "\n".join(lines) + return test_func + + content = re.sub(pattern5, fix_mock_response_usage, content, flags=re.DOTALL) + + # If no changes were made, return False + if content == original_content: + return False + + # Write the changes back to the file + if not dry_run: + with open(file_path, "w") as f: + f.write(content) + + return True + + +def find_files_to_transform() -> List[Tuple[str, bool]]: + """Find all test files that need to be transformed.""" + test_dir = Path("tests") + result = [] + + for root, _, files in os.walk(test_dir): + for file in files: + if file.endswith(".py") and file.startswith("test_"): + file_path = os.path.join(root, file) + + # Check if file uses mock_response directly + uses_mock_response = bool( + find_pattern_in_file( + file_path, r"def\s+test_\w+\([^)]*,\s*mock_response\s*[,)]" + ) + ) + + # Check if file directly manipulates mock_response + manipulates_mock_response = bool( + find_pattern_in_file( + file_path, r"mock_response\.(json\.return_value|status_code)\s*=" + ) + ) + + # Check if file uses mock_response_factory + uses_factory = bool( + find_pattern_in_file(file_path, r"mock_response\s*=\s*mock_response_factory") + ) + + # Determine if file needs transformation + needs_transform = ( + uses_mock_response or manipulates_mock_response + ) and not uses_factory + + if needs_transform: + result.append((file_path, needs_transform)) + + return result + + +def main(): + """Main entry point.""" + dry_run = "--dry-run" in sys.argv + files = find_files_to_transform() + + print(f"Found {len(files)} files that need to be transformed.") + if dry_run: + print("Running in dry-run mode. No files will be modified.") + + for file_path, _ in files: + print(f"Transforming {file_path}...", end="") + transformed = transform_file(file_path, dry_run) + print(" TRANSFORMED" if transformed else " SKIPPED (no changes)") + + print("\nDone!") + print(f"Transformed {len(files)} files.") + + if dry_run: + print("\nRun without --dry-run to apply changes.") + + +if __name__ == "__main__": + main() diff --git a/tests/fitbit_client/resources/nutrition/test_add_favorite_foods.py b/tests/fitbit_client/resources/nutrition/test_add_favorite_foods.py index c8952c0..2800c7c 100644 --- a/tests/fitbit_client/resources/nutrition/test_add_favorite_foods.py +++ b/tests/fitbit_client/resources/nutrition/test_add_favorite_foods.py @@ -3,10 +3,10 @@ """Tests for the add_favorite_foods endpoint.""" -def test_add_favorite_foods_success(nutrition_resource, mock_response): +def test_add_favorite_foods_success(nutrition_resource, mock_response_factory): """Test successful addition of a food to favorites""" food_id = 12345 - mock_response.json.return_value = {"success": True} + mock_response = mock_response_factory(200, {"success": True}) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.add_favorite_foods(food_id=food_id) assert result == mock_response.json.return_value diff --git a/tests/fitbit_client/resources/nutrition/test_create_food_goal.py b/tests/fitbit_client/resources/nutrition/test_create_food_goal.py index 6eb01ad..e6a3a73 100644 --- a/tests/fitbit_client/resources/nutrition/test_create_food_goal.py +++ b/tests/fitbit_client/resources/nutrition/test_create_food_goal.py @@ -2,8 +2,6 @@ """Tests for the create_food_goal endpoint.""" -# Third party imports - # Third party imports from pytest import raises @@ -12,9 +10,9 @@ from fitbit_client.resources._constants import FoodPlanIntensity -def test_create_food_goal_with_calories_success(nutrition_resource, mock_response): +def test_create_food_goal_with_calories_success(nutrition_resource, mock_response_factory): """Test successful creation of a food goal using calories""" - mock_response.json.return_value = {"goals": {"calories": 2000}} + mock_response = mock_response_factory(200, {"goals": {"calories": 2000}}) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.create_food_goal(calories=2000) assert result == mock_response.json.return_value @@ -28,12 +26,11 @@ def test_create_food_goal_with_calories_success(nutrition_resource, mock_respons ) -def test_create_food_goal_with_intensity_success(nutrition_resource, mock_response): +def test_create_food_goal_with_intensity_success(nutrition_resource, mock_response_factory): """Test successful creation of a food goal using intensity""" - mock_response.json.return_value = { - "foodPlan": {"intensity": "EASIER"}, - "goals": {"calories": 2200}, - } + mock_response = mock_response_factory( + 200, {"foodPlan": {"intensity": "EASIER"}, "goals": {"calories": 2200}} + ) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.create_food_goal( intensity=FoodPlanIntensity.EASIER, personalized=True diff --git a/tests/fitbit_client/resources/nutrition/test_create_food_goal_intensity.py b/tests/fitbit_client/resources/nutrition/test_create_food_goal_intensity.py index 967b89e..b955416 100644 --- a/tests/fitbit_client/resources/nutrition/test_create_food_goal_intensity.py +++ b/tests/fitbit_client/resources/nutrition/test_create_food_goal_intensity.py @@ -2,15 +2,13 @@ """Tests for the create_food_goal_intensity endpoint.""" -# Local imports - # Local imports from fitbit_client.resources._constants import FoodPlanIntensity -def test_create_food_goal_intensity_without_personalized(nutrition_resource, mock_response): +def test_create_food_goal_intensity_without_personalized(nutrition_resource, mock_response_factory): """Test creating food goal with intensity but without personalized flag (lines 217-220)""" - mock_response.json.return_value = {"foodPlan": {"intensity": "EASIER"}} + mock_response = mock_response_factory(200, {"foodPlan": {"intensity": "EASIER"}}) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.create_food_goal(intensity=FoodPlanIntensity.EASIER) assert result == mock_response.json.return_value diff --git a/tests/fitbit_client/resources/nutrition/test_create_food_log_custom_minimal.py b/tests/fitbit_client/resources/nutrition/test_create_food_log_custom_minimal.py index 814902d..193de9a 100644 --- a/tests/fitbit_client/resources/nutrition/test_create_food_log_custom_minimal.py +++ b/tests/fitbit_client/resources/nutrition/test_create_food_log_custom_minimal.py @@ -2,15 +2,13 @@ """Tests for the create_food_log_custom_minimal endpoint.""" -# Local imports - # Local imports from fitbit_client.resources._constants import MealType -def test_create_food_log_custom_minimal(nutrition_resource, mock_response): +def test_create_food_log_custom_minimal(nutrition_resource, mock_response_factory): """Test creating custom food log with minimal parameters (no brand or nutritional values)""" - mock_response.json.return_value = {"foodLog": {"logId": 12345}} + mock_response = mock_response_factory(200, {"foodLog": {"logId": 12345}}) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.create_food_log( date="2025-02-08", diff --git a/tests/fitbit_client/resources/nutrition/test_create_meal.py b/tests/fitbit_client/resources/nutrition/test_create_meal.py index 4b1fe03..cd49866 100644 --- a/tests/fitbit_client/resources/nutrition/test_create_meal.py +++ b/tests/fitbit_client/resources/nutrition/test_create_meal.py @@ -3,16 +3,19 @@ """Tests for the create_meal endpoint.""" -def test_create_meal_success(nutrition_resource, mock_response): +def test_create_meal_success(nutrition_resource, mock_response_factory): """Test successful creation of a meal""" - mock_response.json.return_value = { - "meal": { - "id": 12345, - "name": "Test Meal", - "description": "Test meal description", - "mealFoods": [{"foodId": 67890, "amount": 100.0, "unitId": 147}], - } - } + mock_response = mock_response_factory( + 200, + { + "meal": { + "id": 12345, + "name": "Test Meal", + "description": "Test meal description", + "mealFoods": [{"foodId": 67890, "amount": 100.0, "unitId": 147}], + } + }, + ) nutrition_resource.oauth.request.return_value = mock_response foods = [{"food_id": 67890, "amount": 100.0, "unit_id": 147}] result = nutrition_resource.create_meal( diff --git a/tests/fitbit_client/resources/nutrition/test_create_water_goal.py b/tests/fitbit_client/resources/nutrition/test_create_water_goal.py index b04f9d7..149005f 100644 --- a/tests/fitbit_client/resources/nutrition/test_create_water_goal.py +++ b/tests/fitbit_client/resources/nutrition/test_create_water_goal.py @@ -3,9 +3,11 @@ """Tests for the create_water_goal endpoint.""" -def test_create_water_goal_success(nutrition_resource, mock_response): +def test_create_water_goal_success(nutrition_resource, mock_response_factory): """Test successful creation of a water goal""" - mock_response.json.return_value = {"goal": {"goal": 2000.0, "startDate": "2025-02-08"}} + mock_response = mock_response_factory( + 200, {"goal": {"goal": 2000.0, "startDate": "2025-02-08"}} + ) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.create_water_goal(target=2000.0) assert result == mock_response.json.return_value diff --git a/tests/fitbit_client/resources/nutrition/test_create_water_log.py b/tests/fitbit_client/resources/nutrition/test_create_water_log.py index 5f589aa..c96e4cc 100644 --- a/tests/fitbit_client/resources/nutrition/test_create_water_log.py +++ b/tests/fitbit_client/resources/nutrition/test_create_water_log.py @@ -2,8 +2,6 @@ """Tests for the create_water_log endpoint.""" -# Third party imports - # Third party imports from pytest import raises @@ -12,9 +10,9 @@ from fitbit_client.resources._constants import WaterUnit -def test_create_water_log_success(nutrition_resource, mock_response): +def test_create_water_log_success(nutrition_resource, mock_response_factory): """Test successful creation of a water log entry""" - mock_response.json.return_value = {"waterLog": {"logId": 12345, "amount": 500.0}} + mock_response = mock_response_factory(200, {"waterLog": {"logId": 12345, "amount": 500.0}}) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.create_water_log( amount=500.0, date="2025-02-08", unit=WaterUnit.MILLILITERS @@ -36,8 +34,9 @@ def test_create_water_log_invalid_date(nutrition_resource): nutrition_resource.create_water_log(amount=500.0, date="invalid-date") -def test_create_water_log_allows_today(nutrition_resource, mock_response): +def test_create_water_log_allows_today(nutrition_resource, mock_response_factory): """Test that 'today' is accepted as a valid date""" + mock_response = mock_response_factory(200) mock_response.json.return_value = {"waterLog": {"logId": 12345}} mock_response.headers = {"content-type": "application/json"} nutrition_resource.oauth.request.return_value = mock_response diff --git a/tests/fitbit_client/resources/nutrition/test_custom_user_id.py b/tests/fitbit_client/resources/nutrition/test_custom_user_id.py index fb40804..e1615ef 100644 --- a/tests/fitbit_client/resources/nutrition/test_custom_user_id.py +++ b/tests/fitbit_client/resources/nutrition/test_custom_user_id.py @@ -8,7 +8,7 @@ from fitbit_client.resources._constants import MealType -def test_custom_user_id(nutrition_resource, mock_response): +def test_custom_user_id(nutrition_resource, mock_response_factory): """Test that endpoints correctly handle custom user IDs""" custom_user_id = "123ABC" test_cases = [ @@ -35,7 +35,7 @@ def test_custom_user_id(nutrition_resource, mock_response): f"https://api.fitbit.com/1/user/{custom_user_id}/foods/log/water/date/2025-02-08.json", ), ] - mock_response.json.return_value = {"success": True} + mock_response = mock_response_factory(200, {"success": True}) nutrition_resource.oauth.request.return_value = mock_response for method, params, expected_url in test_cases: result = method(**params) diff --git a/tests/fitbit_client/resources/nutrition/test_delete_favorite_foods.py b/tests/fitbit_client/resources/nutrition/test_delete_favorite_foods.py index ca75a83..1dbaaad 100644 --- a/tests/fitbit_client/resources/nutrition/test_delete_favorite_foods.py +++ b/tests/fitbit_client/resources/nutrition/test_delete_favorite_foods.py @@ -3,9 +3,9 @@ """Tests for the delete_favorite_foods endpoint.""" -def test_delete_favorite_food_success(nutrition_resource, mock_response): +def test_delete_favorite_food_success(nutrition_resource, mock_response_factory): """Test successful deletion of a favorite food""" - mock_response.status_code = 204 + mock_response = mock_response_factory(204) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.delete_favorite_food(food_id=12345) assert result is None diff --git a/tests/fitbit_client/resources/nutrition/test_get_water_goal.py b/tests/fitbit_client/resources/nutrition/test_get_water_goal.py index 284633d..ba0be99 100644 --- a/tests/fitbit_client/resources/nutrition/test_get_water_goal.py +++ b/tests/fitbit_client/resources/nutrition/test_get_water_goal.py @@ -3,9 +3,11 @@ """Tests for the get_water_goal endpoint.""" -def test_get_water_goal_success(nutrition_resource, mock_response): +def test_get_water_goal_success(nutrition_resource, mock_response_factory): """Test successful retrieval of water goal""" - mock_response.json.return_value = {"goal": {"goal": 2000.0, "startDate": "2025-02-08"}} + mock_response = mock_response_factory( + 200, {"goal": {"goal": 2000.0, "startDate": "2025-02-08"}} + ) nutrition_resource.oauth.request.return_value = mock_response result = nutrition_resource.get_water_goal() assert result == mock_response.json.return_value From ed7fb8900e8be3d0c9c3283cf809e6496cf73daa Mon Sep 17 00:00:00 2001 From: Jon Stroop Date: Mon, 10 Mar 2025 16:36:26 -0700 Subject: [PATCH 5/6] Standardize mock_response_factory usage in active_zone_minutes tests --- standardize_mock_responses.py | 60 +++++++++++++++---- .../test_get_azm_timeseries.py | 5 +- .../test_get_azm_timeseries_by_date.py | 42 ++++++------- .../test_get_azm_timeseries_by_interval.py | 49 ++++++++------- 4 files changed, 98 insertions(+), 58 deletions(-) diff --git a/standardize_mock_responses.py b/standardize_mock_responses.py index e0e1d44..d99d402 100755 --- a/standardize_mock_responses.py +++ b/standardize_mock_responses.py @@ -114,6 +114,15 @@ def fix_mock_response_usage(match): return test_func content = re.sub(pattern5, fix_mock_response_usage, content, flags=re.DOTALL) + + # Pattern 6: Replace assert result == mock_response.json.return_value with assert result == expected_data + pattern6 = r'([ \t]*assert\s+.*?==\s*)mock_response\.json\.return_value' + + def fix_assertions(match): + before_part = match.group(1) + return f"{before_part}mock_response.json()" + + content = re.sub(pattern6, fix_assertions, content) # If no changes were made, return False if content == original_content: @@ -130,37 +139,68 @@ def fix_mock_response_usage(match): def find_files_to_transform() -> List[Tuple[str, bool]]: """Find all test files that need to be transformed.""" test_dir = Path("tests") + # Allow specifying a subdirectory + if len(sys.argv) > 1 and not sys.argv[1].startswith("--"): + test_dir = Path(sys.argv[1]) + print(f"Searching in directory: {test_dir}") + result = [] - + verbose = "--verbose" in sys.argv + for root, _, files in os.walk(test_dir): for file in files: if file.endswith(".py") and file.startswith("test_"): file_path = os.path.join(root, file) - - # Check if file uses mock_response directly + + # First check for any mention of mock_response + has_mock_response = bool(find_pattern_in_file(file_path, r"mock_response")) + + if not has_mock_response: + continue + + # More detailed patterns + # Check if file uses mock_response directly as parameter uses_mock_response = bool( find_pattern_in_file( file_path, r"def\s+test_\w+\([^)]*,\s*mock_response\s*[,)]" ) ) - + # Check if file directly manipulates mock_response manipulates_mock_response = bool( find_pattern_in_file( file_path, r"mock_response\.(json\.return_value|status_code)\s*=" ) ) - + + # Check if file uses mock_response.json.return_value in assertions + uses_return_value_in_assertions = bool( + find_pattern_in_file( + file_path, r"assert\s+.*?=.*?mock_response\.json\.return_value" + ) + ) + # Check if file uses mock_response_factory uses_factory = bool( find_pattern_in_file(file_path, r"mock_response\s*=\s*mock_response_factory") ) - + # Determine if file needs transformation - needs_transform = ( - uses_mock_response or manipulates_mock_response - ) and not uses_factory - + needs_transform = (uses_mock_response or manipulates_mock_response) and not uses_factory + + # Also flag files that use factory but still use mock_response.json.return_value in assertions + if uses_factory and uses_return_value_in_assertions and not needs_transform: + needs_transform = True + + if verbose or needs_transform: + print(f"File: {file_path}") + print(f" Has mock_response: {has_mock_response}") + print(f" Uses as parameter: {uses_mock_response}") + print(f" Manipulates: {manipulates_mock_response}") + print(f" Uses in assertions: {uses_return_value_in_assertions}") + print(f" Uses factory: {uses_factory}") + print(f" Needs transform: {needs_transform}") + if needs_transform: result.append((file_path, needs_transform)) diff --git a/tests/fitbit_client/resources/active_zone_minutes/test_get_azm_timeseries.py b/tests/fitbit_client/resources/active_zone_minutes/test_get_azm_timeseries.py index 79bfaae..b043c24 100644 --- a/tests/fitbit_client/resources/active_zone_minutes/test_get_azm_timeseries.py +++ b/tests/fitbit_client/resources/active_zone_minutes/test_get_azm_timeseries.py @@ -5,10 +5,11 @@ def test_get_azm_timeseries_with_today_date(azm_resource, mock_response_factory): """Test using 'today' as the date parameter""" - mock_response = mock_response_factory(200, {"activities-active-zone-minutes": []}) + expected_data = {"activities-active-zone-minutes": []} + mock_response = mock_response_factory(200, expected_data) azm_resource.oauth.request.return_value = mock_response result = azm_resource.get_azm_timeseries_by_date(date="today") - assert result == mock_response.json.return_value + assert result == expected_data azm_resource.oauth.request.assert_called_once_with( "GET", "https://api.fitbit.com/1/user/-/activities/active-zone-minutes/date/today/1d.json", diff --git a/tests/fitbit_client/resources/active_zone_minutes/test_get_azm_timeseries_by_date.py b/tests/fitbit_client/resources/active_zone_minutes/test_get_azm_timeseries_by_date.py index 031f7c3..c55804f 100644 --- a/tests/fitbit_client/resources/active_zone_minutes/test_get_azm_timeseries_by_date.py +++ b/tests/fitbit_client/resources/active_zone_minutes/test_get_azm_timeseries_by_date.py @@ -15,25 +15,23 @@ def test_get_azm_timeseries_by_date_success(azm_resource, mock_response_factory): """Test successful retrieval of AZM time series by date with default period""" - mock_response = mock_response_factory( - 200, - { - "activities-active-zone-minutes": [ - { - "dateTime": "2025-02-01", - "value": { - "activeZoneMinutes": 102, - "fatBurnActiveZoneMinutes": 90, - "cardioActiveZoneMinutes": 8, - "peakActiveZoneMinutes": 4, - }, - } - ] - }, - ) + expected_data = { + "activities-active-zone-minutes": [ + { + "dateTime": "2025-02-01", + "value": { + "activeZoneMinutes": 102, + "fatBurnActiveZoneMinutes": 90, + "cardioActiveZoneMinutes": 8, + "peakActiveZoneMinutes": 4, + }, + } + ] + } + mock_response = mock_response_factory(200, expected_data) azm_resource.oauth.request.return_value = mock_response result = azm_resource.get_azm_timeseries_by_date(date="2025-02-01") - assert result == mock_response.json.return_value + assert result == expected_data azm_resource.oauth.request.assert_called_once_with( "GET", "https://api.fitbit.com/1/user/-/activities/active-zone-minutes/date/2025-02-01/1d.json", @@ -46,10 +44,11 @@ def test_get_azm_timeseries_by_date_success(azm_resource, mock_response_factory) def test_get_azm_timeseries_by_date_explicit_period(azm_resource, mock_response_factory): """Test successful retrieval of AZM time series by date with explicit ONE_DAY period""" - mock_response = mock_response_factory(200, {"activities-active-zone-minutes": []}) + expected_data = {"activities-active-zone-minutes": []} + mock_response = mock_response_factory(200, expected_data) azm_resource.oauth.request.return_value = mock_response result = azm_resource.get_azm_timeseries_by_date(date="2025-02-01", period=Period.ONE_DAY) - assert result == mock_response.json.return_value + assert result == expected_data azm_resource.oauth.request.assert_called_once_with( "GET", "https://api.fitbit.com/1/user/-/activities/active-zone-minutes/date/2025-02-01/1d.json", @@ -62,10 +61,11 @@ def test_get_azm_timeseries_by_date_explicit_period(azm_resource, mock_response_ def test_get_azm_timeseries_by_date_with_user_id(azm_resource, mock_response_factory): """Test getting AZM time series for a specific user""" - mock_response = mock_response_factory(200, {"activities-active-zone-minutes": []}) + expected_data = {"activities-active-zone-minutes": []} + mock_response = mock_response_factory(200, expected_data) azm_resource.oauth.request.return_value = mock_response result = azm_resource.get_azm_timeseries_by_date(date="2025-02-01", user_id="123ABC") - assert result == mock_response.json.return_value + assert result == expected_data azm_resource.oauth.request.assert_called_once_with( "GET", "https://api.fitbit.com/1/user/123ABC/activities/active-zone-minutes/date/2025-02-01/1d.json", diff --git a/tests/fitbit_client/resources/active_zone_minutes/test_get_azm_timeseries_by_interval.py b/tests/fitbit_client/resources/active_zone_minutes/test_get_azm_timeseries_by_interval.py index 0285a17..7f89b25 100644 --- a/tests/fitbit_client/resources/active_zone_minutes/test_get_azm_timeseries_by_interval.py +++ b/tests/fitbit_client/resources/active_zone_minutes/test_get_azm_timeseries_by_interval.py @@ -18,35 +18,33 @@ def test_get_azm_timeseries_by_interval_success(azm_resource, mock_response_factory): """Test successful retrieval of AZM time series by date range""" - mock_response = mock_response_factory( - 200, - { - "activities-active-zone-minutes": [ - { - "dateTime": "2025-02-01", - "value": { - "activeZoneMinutes": 102, - "fatBurnActiveZoneMinutes": 90, - "cardioActiveZoneMinutes": 8, - "peakActiveZoneMinutes": 4, - }, + expected_data = { + "activities-active-zone-minutes": [ + { + "dateTime": "2025-02-01", + "value": { + "activeZoneMinutes": 102, + "fatBurnActiveZoneMinutes": 90, + "cardioActiveZoneMinutes": 8, + "peakActiveZoneMinutes": 4, }, - { - "dateTime": "2025-02-02", - "value": { - "activeZoneMinutes": 47, - "fatBurnActiveZoneMinutes": 43, - "cardioActiveZoneMinutes": 4, - }, + }, + { + "dateTime": "2025-02-02", + "value": { + "activeZoneMinutes": 47, + "fatBurnActiveZoneMinutes": 43, + "cardioActiveZoneMinutes": 4, }, - ] - }, - ) + }, + ] + } + mock_response = mock_response_factory(200, expected_data) azm_resource.oauth.request.return_value = mock_response result = azm_resource.get_azm_timeseries_by_interval( start_date="2025-02-01", end_date="2025-02-02" ) - assert result == mock_response.json.return_value + assert result == expected_data azm_resource.oauth.request.assert_called_once_with( "GET", "https://api.fitbit.com/1/user/-/activities/active-zone-minutes/date/2025-02-01/2025-02-02.json", @@ -59,12 +57,13 @@ def test_get_azm_timeseries_by_interval_success(azm_resource, mock_response_fact def test_get_azm_timeseries_by_interval_with_user_id(azm_resource, mock_response_factory): """Test getting AZM time series by date range for a specific user""" - mock_response = mock_response_factory(200, {"activities-active-zone-minutes": []}) + expected_data = {"activities-active-zone-minutes": []} + mock_response = mock_response_factory(200, expected_data) azm_resource.oauth.request.return_value = mock_response result = azm_resource.get_azm_timeseries_by_interval( start_date="2025-02-01", end_date="2025-02-02", user_id="123ABC" ) - assert result == mock_response.json.return_value + assert result == expected_data azm_resource.oauth.request.assert_called_once_with( "GET", "https://api.fitbit.com/1/user/123ABC/activities/active-zone-minutes/date/2025-02-01/2025-02-02.json", From e9316e84ceaec07e08fb7c92b52d559c470399dd Mon Sep 17 00:00:00 2001 From: Jon Stroop Date: Mon, 10 Mar 2025 21:45:57 -0700 Subject: [PATCH 6/6] Standardize mock_response_factory usage in test_base.py and update documentation - Updated test_base.py to use mock_response_factory pattern - Enhanced docs/DEVELOPMENT.md with detailed examples for mock_response_factory usage - Documented both standard response pattern and parameter validation pattern --- docs/DEVELOPMENT.md | 68 +++++++++++++++++++--- standardize_mock_responses.py | 36 ++++++------ tests/fitbit_client/resources/test_base.py | 46 ++++++++------- 3 files changed, 106 insertions(+), 44 deletions(-) diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index f3ef631..cf70246 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -195,27 +195,81 @@ All resource mocks are in the root [conftest.py](tests/conftest.py). ### Response Mocking -The test suite uses the `mock_response_factory` fixture to create consistent, -configurable mock responses: +The test suite uses the `mock_response_factory` fixture from `tests/conftest.py` +to create consistent, configurable mock responses. This is the required pattern +for all tests that need to mock HTTP responses. + +#### Standard Mock Response Pattern + +```python +def test_some_endpoint(resource, mock_oauth_session, mock_response_factory): + """Test description.""" + # Define expected data first + expected_data = {"data": "test"} + + # Create mock response using the factory + mock_response = mock_response_factory(200, expected_data) + + # Assign to oauth.request.return_value + resource.oauth.request.return_value = mock_response + + # Call the method under test + result = resource.some_method() + + # Assert against expected_data, not mock_response.json.return_value + assert result == expected_data +``` + +#### Response Factory Examples ```python -# Creating a mock response with status code and data +# Success response with data mock_response = mock_response_factory(200, {"data": "test"}) -# Creating a mock response with additional headers +# Response with custom headers mock_response = mock_response_factory( status_code=200, json_data={"data": "test"}, headers={"custom-header": "value"} ) -# Creating a delete response with no content (204) +# Delete/no content response (204) mock_response = mock_response_factory(204) + +# Error response +mock_response = mock_response_factory( + 400, + {"errors": [{"errorType": "validation", "message": "Error message"}]} +) + +# Non-JSON response (XML) +mock_response = mock_response_factory( + 200, + headers={"content-type": "application/vnd.garmin.tcx+xml"}, + content_type="application/vnd.garmin.tcx+xml" +) +mock_response.text = "content" +``` + +#### Parameter Validation Pattern + +For tests that only need to verify parameter validation or endpoint construction +(not response handling), it's acceptable to use the following alternative +pattern: + +```python +def test_validation(resource): + """Test parameter validation.""" + resource._make_request = Mock() + resource.some_method(param="value") + resource._make_request.assert_called_once_with( + "endpoint/path", params={"param": "value"}, user_id="-", debug=False + ) ``` This approach provides a clean, standardized way to create mock responses with -the desired status code, data, and headers. All test files should use this -factory method rather than manually configuring mock responses. +the desired status code, data, and headers. All test files must use one of these +patterns. ## OAuth Callback Implementation diff --git a/standardize_mock_responses.py b/standardize_mock_responses.py index d99d402..bea45bd 100755 --- a/standardize_mock_responses.py +++ b/standardize_mock_responses.py @@ -114,14 +114,14 @@ def fix_mock_response_usage(match): return test_func content = re.sub(pattern5, fix_mock_response_usage, content, flags=re.DOTALL) - + # Pattern 6: Replace assert result == mock_response.json.return_value with assert result == expected_data - pattern6 = r'([ \t]*assert\s+.*?==\s*)mock_response\.json\.return_value' - + pattern6 = r"([ \t]*assert\s+.*?==\s*)mock_response\.json\.return_value" + def fix_assertions(match): before_part = match.group(1) return f"{before_part}mock_response.json()" - + content = re.sub(pattern6, fix_assertions, content) # If no changes were made, return False @@ -143,21 +143,21 @@ def find_files_to_transform() -> List[Tuple[str, bool]]: if len(sys.argv) > 1 and not sys.argv[1].startswith("--"): test_dir = Path(sys.argv[1]) print(f"Searching in directory: {test_dir}") - + result = [] verbose = "--verbose" in sys.argv - + for root, _, files in os.walk(test_dir): for file in files: if file.endswith(".py") and file.startswith("test_"): file_path = os.path.join(root, file) - + # First check for any mention of mock_response has_mock_response = bool(find_pattern_in_file(file_path, r"mock_response")) - + if not has_mock_response: continue - + # More detailed patterns # Check if file uses mock_response directly as parameter uses_mock_response = bool( @@ -165,33 +165,35 @@ def find_files_to_transform() -> List[Tuple[str, bool]]: file_path, r"def\s+test_\w+\([^)]*,\s*mock_response\s*[,)]" ) ) - + # Check if file directly manipulates mock_response manipulates_mock_response = bool( find_pattern_in_file( file_path, r"mock_response\.(json\.return_value|status_code)\s*=" ) ) - + # Check if file uses mock_response.json.return_value in assertions uses_return_value_in_assertions = bool( find_pattern_in_file( file_path, r"assert\s+.*?=.*?mock_response\.json\.return_value" ) ) - + # Check if file uses mock_response_factory uses_factory = bool( find_pattern_in_file(file_path, r"mock_response\s*=\s*mock_response_factory") ) - + # Determine if file needs transformation - needs_transform = (uses_mock_response or manipulates_mock_response) and not uses_factory - + needs_transform = ( + uses_mock_response or manipulates_mock_response + ) and not uses_factory + # Also flag files that use factory but still use mock_response.json.return_value in assertions if uses_factory and uses_return_value_in_assertions and not needs_transform: needs_transform = True - + if verbose or needs_transform: print(f"File: {file_path}") print(f" Has mock_response: {has_mock_response}") @@ -200,7 +202,7 @@ def find_files_to_transform() -> List[Tuple[str, bool]]: print(f" Uses in assertions: {uses_return_value_in_assertions}") print(f" Uses factory: {uses_factory}") print(f" Needs transform: {needs_transform}") - + if needs_transform: result.append((file_path, needs_transform)) diff --git a/tests/fitbit_client/resources/test_base.py b/tests/fitbit_client/resources/test_base.py index aee1cd0..184ff1c 100644 --- a/tests/fitbit_client/resources/test_base.py +++ b/tests/fitbit_client/resources/test_base.py @@ -258,17 +258,18 @@ def test_log_response_for_error_without_content(base_resource, mock_logger): # ----------------------------------------------------------------------------- -def test_handle_json_response(base_resource, mock_response): +def test_handle_json_response(base_resource, mock_response_factory): """Test JSON response handling""" - mock_response.json.return_value = {"data": "test"} - mock_response.status_code = 200 + expected_data = {"data": "test"} + mock_response = mock_response_factory(200, expected_data) result = base_resource._handle_json_response("test_method", "test/endpoint", mock_response) - assert result == {"data": "test"} + assert result == expected_data -def test_handle_json_response_invalid(base_resource, mock_response): +def test_handle_json_response_invalid(base_resource, mock_response_factory): """Test invalid JSON handling""" + mock_response = mock_response_factory(200) mock_response.json.side_effect = JSONDecodeError("Invalid JSON", "doc", 0) mock_response.text = "Invalid {json" @@ -295,44 +296,49 @@ def test_handle_json_response_with_invalid_json(base_resource, mock_logger): # ----------------------------------------------------------------------------- -def test_make_request_json_success(base_resource, mock_oauth_session, mock_response): +def test_make_request_json_success(base_resource, mock_oauth_session, mock_response_factory): """Test successful JSON request""" - mock_response.json.return_value = {"success": True} - mock_response.headers = {"content-type": "application/json"} - mock_response.status_code = 200 + expected_data = {"success": True} + mock_response = mock_response_factory( + 200, expected_data, headers={"content-type": "application/json"} + ) mock_oauth_session.request.return_value = mock_response result = base_resource._make_request("test/endpoint") - assert result == {"success": True} + assert result == expected_data -def test_make_request_no_content(base_resource, mock_oauth_session, mock_response): +def test_make_request_no_content(base_resource, mock_oauth_session, mock_response_factory): """Test request with no content""" - mock_response.status_code = 204 - mock_response.headers = {} - mock_response.json.return_value = {"success": True} + mock_response = mock_response_factory(204, headers={}) mock_oauth_session.request.return_value = mock_response result = base_resource._make_request("test/endpoint") assert result is None -def test_make_request_xml_response(base_resource, mock_oauth_session, mock_response): +def test_make_request_xml_response(base_resource, mock_oauth_session, mock_response_factory): """Test XML response handling""" + mock_response = mock_response_factory( + 200, + headers={"content-type": "application/vnd.garmin.tcx+xml"}, + content_type="application/vnd.garmin.tcx+xml", + ) mock_response.text = "data" - mock_response.headers = {"content-type": "application/vnd.garmin.tcx+xml"} - mock_response.status_code = 200 mock_oauth_session.request.return_value = mock_response result = base_resource._make_request("test/endpoint") assert result == "data" -def test_make_request_unexpected_content_type(base_resource, mock_oauth_session, mock_response): +def test_make_request_unexpected_content_type( + base_resource, mock_oauth_session, mock_response_factory +): """Test handling of unexpected content type""" + mock_response = mock_response_factory( + 200, headers={"content-type": "text/plain"}, content_type="text/plain" + ) mock_response.text = "some data" - mock_response.headers = {"content-type": "text/plain"} - mock_response.status_code = 200 mock_oauth_session.request.return_value = mock_response result = base_resource._make_request("test/endpoint")