colmi_r02_client/tests/test_hr.py
Wesley Ellis dccd04a212 feat: add sync command to write data to sqlite
Introduce sqlalchemy as a dep so that we can store and sync data from
the ring over time. This add a db module that contains ORM models and
functions to write data to the database

I also got sick of all the date stuff so I added date_utils as a module
to keep everything in one place.
2024-11-30 21:00:03 -05:00

452 lines
8.4 KiB
Python

from datetime import datetime, timezone
from freezegun import freeze_time
from colmi_r02_client.hr import (
HeartRateLogParser,
HeartRateLog,
NoData,
)
HEART_RATE_PACKETS = [
bytearray(b"\x15\x00\x18\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x002"),
bytearray(b"\x15\x01\x80\xad\xb6f\x00\x00\x00\x00\x00\x00\x00\x00\x00_"),
bytearray(b"\x15\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17"),
bytearray(b"\x15\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18"),
bytearray(b"\x15\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x19"),
bytearray(b"\x15\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a"),
bytearray(b"\x15\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1b"),
bytearray(b"\x15\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1c"),
bytearray(b"\x15\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1d"),
bytearray(b"\x15\t\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1e"),
bytearray(b"\x15\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f"),
bytearray(b"\x15\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 "),
bytearray(b"\x15\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!"),
bytearray(b'\x15\r\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"'),
bytearray(b"\x15\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#"),
bytearray(b"\x15\x0f\x00\x00Y\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00}"),
bytearray(b"\x15\x10\x00k\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x90"),
bytearray(b"\x15\x11`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00k\xf1"),
bytearray(b"\x15\x12\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'"),
bytearray(b"\x15\x13\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00P\x00\x00x"),
bytearray(b"\x15\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00F\x00\x00\x00o"),
bytearray(b"\x15\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00*"),
bytearray(b"\x15\x16\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+"),
bytearray(b"\x15\x17\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00,"),
]
def test_parse_return_none_until_end():
parser = HeartRateLogParser()
for p in HEART_RATE_PACKETS[:-1]:
assert parser.parse(p) is None
def test_parse_until_end():
parser = HeartRateLogParser()
for p in HEART_RATE_PACKETS[:-1]:
parser.parse(p)
result = parser.parse(HEART_RATE_PACKETS[-1])
assert isinstance(result, HeartRateLog)
assert len(result.heart_rates) == 288
expected_timestamp = datetime(
year=2024,
month=8,
day=10,
hour=0,
minute=0,
tzinfo=timezone.utc,
)
assert result.timestamp == expected_timestamp
assert result.size == 24
assert result.index == 295
assert result.range == 5
def test_parse_no_data():
parser = HeartRateLogParser()
result = parser.parse(
bytearray(b"\x15\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14"),
)
assert isinstance(result, NoData)
@freeze_time("2024-01-01")
def test_is_today_today():
parser = HeartRateLogParser()
parser._raw_heart_rates = [1] * 288
parser.timestamp = datetime(2024, 1, 1, 1, 1, 0)
assert parser.is_today()
@freeze_time("2024-01-02")
def test_is_today_not_today():
parser = HeartRateLogParser()
parser._raw_heart_rates = [1] * 288
parser.timestamp = datetime(2024, 1, 1, 1, 1, 0)
assert not parser.is_today()
def test_heart_rates_less_288():
"""Test that we pad the heart rate array to 288 with 0s if the raw data is less than 288 bytes long."""
parser = HeartRateLogParser()
parser._raw_heart_rates = [1] * 286
hr = parser.heart_rates
assert len(hr) == 288
assert hr == (([1] * 286) + [0, 0])
def test_get_heart_rate_more_288():
parser = HeartRateLogParser()
parser._raw_heart_rates = [1] * 289
hr = parser.heart_rates
assert len(hr) == 288
assert hr == ([1] * 288)
def test_get_heart_rate_288_not_today():
parser = HeartRateLogParser()
parser._raw_heart_rates = [1] * 288
parser.timestamp = datetime(2020, 1, 1, 1, 1, 0)
hr = parser.heart_rates
assert len(hr) == 288
assert hr == ([1] * 288)
@freeze_time("2024-01-01 01:00")
def test_get_heart_rate_288_today():
parser = HeartRateLogParser()
parser._raw_heart_rates = [1] * 288
parser.timestamp = datetime(2024, 1, 1, 1, 1, 0)
hr = parser.heart_rates
assert len(hr) == 288
assert hr == ([1] * 12) + ([0] * 276)
def test_with_times():
h = HeartRateLog([60] * 288, datetime(2024, 1, 1, 5), 0, 0, 5)
hr_with_ts = h.heart_rates_with_times()
assert len(hr_with_ts) == 288
assert hr_with_ts[0][1] == datetime(2024, 1, 1, 0, 0)
assert hr_with_ts[-1][1] == datetime(2024, 1, 1, 23, 55)
@freeze_time("2024-10-31 18:14:10-04:00")
def test_parse_doesnt_drop_data():
"""
We should be able to parse the response from the time it was taken
"""
parser = HeartRateLogParser()
r = None
with open("tests/captures/heart_rate_log_1730412850.bin", "rb") as f:
while data := f.read(17):
r = parser.parse(bytearray(data.strip()))
assert isinstance(r, HeartRateLog)
assert r.heart_rates == [
0,
0,
0,
0,
0,
0,
104,
0,
0,
0,
0,
0,
111,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
66,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
89,
91,
89,
74,
89,
114,
106,
89,
90,
102,
87,
88,
87,
84,
97,
92,
102,
82,
85,
95,
107,
86,
118,
93,
89,
84,
87,
98,
87,
89,
91,
75,
87,
83,
90,
102,
114,
86,
88,
99,
95,
112,
77,
89,
97,
72,
84,
96,
91,
88,
103,
86,
73,
100,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
70,
84,
113,
75,
111,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
]