Files
go-garth/garth/tests/data/test_body_battery_data.py
2025-09-07 06:38:39 -07:00

336 lines
12 KiB
Python

from datetime import date
from unittest.mock import MagicMock
import pytest
from garth import BodyBatteryData, DailyBodyBatteryStress
from garth.http import Client
@pytest.mark.vcr
def test_body_battery_data_get(authed_client: Client):
body_battery_data = BodyBatteryData.get("2023-07-20", client=authed_client)
assert isinstance(body_battery_data, list)
if body_battery_data:
# Check first event if available
event = body_battery_data[0]
assert event is not None
# Test body battery readings property
readings = event.body_battery_readings
assert isinstance(readings, list)
if readings:
# Test reading structure
reading = readings[0]
assert hasattr(reading, "timestamp")
assert hasattr(reading, "status")
assert hasattr(reading, "level")
assert hasattr(reading, "version")
# Test level properties
assert event.current_level is not None and isinstance(
event.current_level, int
)
assert event.max_level is not None and isinstance(
event.max_level, int
)
assert event.min_level is not None and isinstance(
event.min_level, int
)
@pytest.mark.vcr
def test_body_battery_data_list(authed_client: Client):
days = 3
end = date(2023, 7, 20)
body_battery_data = BodyBatteryData.list(end, days, client=authed_client)
assert isinstance(body_battery_data, list)
# Test that we get data (may be empty if no events)
assert len(body_battery_data) >= 0
@pytest.mark.vcr
def test_daily_body_battery_stress_get(authed_client: Client):
daily_data = DailyBodyBatteryStress.get("2023-07-20", client=authed_client)
if daily_data:
# Test basic structure
assert daily_data.user_profile_pk
assert daily_data.calendar_date == date(2023, 7, 20)
assert daily_data.start_timestamp_gmt
assert daily_data.end_timestamp_gmt
# Test stress data
assert isinstance(daily_data.max_stress_level, int)
assert isinstance(daily_data.avg_stress_level, int)
assert isinstance(daily_data.stress_values_array, list)
assert isinstance(daily_data.body_battery_values_array, list)
# Test stress readings property
stress_readings = daily_data.stress_readings
assert isinstance(stress_readings, list)
if stress_readings:
stress_reading = stress_readings[0]
assert hasattr(stress_reading, "timestamp")
assert hasattr(stress_reading, "stress_level")
# Test body battery readings property
bb_readings = daily_data.body_battery_readings
assert isinstance(bb_readings, list)
if bb_readings:
bb_reading = bb_readings[0]
assert hasattr(bb_reading, "timestamp")
assert hasattr(bb_reading, "status")
assert hasattr(bb_reading, "level")
assert hasattr(bb_reading, "version")
# Test computed properties
assert daily_data.current_body_battery is not None and isinstance(
daily_data.current_body_battery, int
)
assert daily_data.max_body_battery is not None and isinstance(
daily_data.max_body_battery, int
)
assert daily_data.min_body_battery is not None and isinstance(
daily_data.min_body_battery, int
)
# Test body battery change
if len(bb_readings) >= 2:
change = daily_data.body_battery_change
assert change is not None
@pytest.mark.vcr
def test_daily_body_battery_stress_get_no_data(authed_client: Client):
# Test with a date that likely has no data
daily_data = DailyBodyBatteryStress.get("2020-01-01", client=authed_client)
# Should return None if no data available
assert daily_data is None or isinstance(daily_data, DailyBodyBatteryStress)
@pytest.mark.vcr
def test_daily_body_battery_stress_list(authed_client: Client):
days = 3
end = date(2023, 7, 20)
# Use max_workers=1 to avoid VCR issues with concurrent requests
daily_data_list = DailyBodyBatteryStress.list(
end, days, client=authed_client, max_workers=1
)
assert isinstance(daily_data_list, list)
assert (
len(daily_data_list) <= days
) # May be less if some days have no data
# Test that each item is correct type
for daily_data in daily_data_list:
assert isinstance(daily_data, DailyBodyBatteryStress)
assert isinstance(daily_data.calendar_date, date)
assert daily_data.user_profile_pk
@pytest.mark.vcr
def test_body_battery_properties_edge_cases(authed_client: Client):
# Test empty data handling
daily_data = DailyBodyBatteryStress.get("2023-07-20", client=authed_client)
if daily_data:
# Test with potentially empty arrays
if not daily_data.body_battery_values_array:
assert daily_data.body_battery_readings == []
assert daily_data.current_body_battery is None
assert daily_data.max_body_battery is None
assert daily_data.min_body_battery is None
assert daily_data.body_battery_change is None
if not daily_data.stress_values_array:
assert daily_data.stress_readings == []
# Error handling tests for BodyBatteryData.get()
def test_body_battery_data_get_api_error():
"""Test handling of API errors."""
mock_client = MagicMock()
mock_client.connectapi.side_effect = Exception("API Error")
result = BodyBatteryData.get("2023-07-20", client=mock_client)
assert result == []
def test_body_battery_data_get_invalid_response():
"""Test handling of non-list responses."""
mock_client = MagicMock()
mock_client.connectapi.return_value = {"error": "Invalid response"}
result = BodyBatteryData.get("2023-07-20", client=mock_client)
assert result == []
def test_body_battery_data_get_missing_event_data():
"""Test handling of items with missing event data."""
mock_client = MagicMock()
mock_client.connectapi.return_value = [
{"activityName": "Test", "averageStress": 25} # Missing "event" key
]
result = BodyBatteryData.get("2023-07-20", client=mock_client)
assert len(result) == 1
assert result[0].event is None
def test_body_battery_data_get_missing_event_start_time():
"""Test handling of event data missing eventStartTimeGmt."""
mock_client = MagicMock()
mock_client.connectapi.return_value = [
{
"event": {"eventType": "sleep"}, # Missing eventStartTimeGmt
"activityName": "Test",
"averageStress": 25,
}
]
result = BodyBatteryData.get("2023-07-20", client=mock_client)
assert result == [] # Should skip invalid items
def test_body_battery_data_get_invalid_datetime_format():
"""Test handling of invalid datetime format."""
mock_client = MagicMock()
mock_client.connectapi.return_value = [
{
"event": {
"eventType": "sleep",
"eventStartTimeGmt": "invalid-date",
},
"activityName": "Test",
"averageStress": 25,
}
]
result = BodyBatteryData.get("2023-07-20", client=mock_client)
assert result == [] # Should skip invalid items
def test_body_battery_data_get_invalid_field_types():
"""Test handling of invalid field types."""
mock_client = MagicMock()
mock_client.connectapi.return_value = [
{
"event": {
"eventType": "sleep",
"eventStartTimeGmt": "2023-07-20T10:00:00.000Z",
"timezoneOffset": "invalid", # Should be number
"durationInMilliseconds": "invalid", # Should be number
"bodyBatteryImpact": "invalid", # Should be number
},
"activityName": "Test",
"averageStress": "invalid", # Should be number
"stressValuesArray": "invalid", # Should be list
"bodyBatteryValuesArray": "invalid", # Should be list
}
]
result = BodyBatteryData.get("2023-07-20", client=mock_client)
assert len(result) == 1
# Should handle invalid types gracefully
def test_body_battery_data_get_validation_error():
"""Test handling of validation errors during object creation."""
mock_client = MagicMock()
mock_client.connectapi.return_value = [
{
"event": {
"eventType": "sleep",
"eventStartTimeGmt": "2023-07-20T10:00:00.000Z",
# Missing required fields for BodyBatteryEvent
},
# Missing required fields for BodyBatteryData
}
]
result = BodyBatteryData.get("2023-07-20", client=mock_client)
# Should handle validation errors and continue processing
assert isinstance(result, list)
assert len(result) == 1 # Should create object with missing fields as None
assert result[0].event is not None # Event should be created
assert result[0].activity_name is None # Missing fields should be None
def test_body_battery_data_get_mixed_valid_invalid():
"""Test processing with mix of valid and invalid items."""
mock_client = MagicMock()
mock_client.connectapi.return_value = [
{
"event": {
"eventType": "sleep",
"eventStartTimeGmt": "2023-07-20T10:00:00.000Z",
"timezoneOffset": -25200000,
"durationInMilliseconds": 28800000,
"bodyBatteryImpact": 35,
"feedbackType": "good_sleep",
"shortFeedback": "Good sleep",
},
"activityName": None,
"activityType": None,
"activityId": None,
"averageStress": 15.5,
"stressValuesArray": [[1689811800000, 12]],
"bodyBatteryValuesArray": [[1689811800000, "charging", 45, 1.0]],
},
{
# Invalid - missing eventStartTimeGmt
"event": {"eventType": "sleep"},
"activityName": "Test",
},
]
result = BodyBatteryData.get("2023-07-20", client=mock_client)
# Should process valid items and skip invalid ones
assert len(result) == 1 # Only the valid item should be processed
assert result[0].event is not None
def test_body_battery_data_get_unexpected_error():
"""Test handling of unexpected errors during object creation."""
mock_client = MagicMock()
# Create a special object that raises an exception when accessed
class ExceptionRaisingDict(dict):
def get(self, key, default=None):
if key == "activityName":
raise RuntimeError("Unexpected error during object creation")
return super().get(key, default)
# Create mock data with problematic item
mock_response_item = ExceptionRaisingDict(
{
"event": {
"eventType": "sleep",
"eventStartTimeGmt": "2023-07-20T10:00:00.000Z",
"timezoneOffset": -25200000,
"durationInMilliseconds": 28800000,
"bodyBatteryImpact": 35,
"feedbackType": "good_sleep",
"shortFeedback": "Good sleep",
},
"activityName": None,
"activityType": None,
"activityId": None,
"averageStress": 15.5,
"stressValuesArray": [[1689811800000, 12]],
"bodyBatteryValuesArray": [[1689811800000, "charging", 45, 1.0]],
}
)
mock_client.connectapi.return_value = [mock_response_item]
result = BodyBatteryData.get("2023-07-20", client=mock_client)
# Should handle unexpected errors and return empty list
assert result == []