mirror of
https://github.com/jwoglom/tconnectsync.git
synced 2026-08-26 10:13:24 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1f63f8858 | ||
|
|
9878e83755 | ||
|
|
d8b57fe788 | ||
|
|
664f97be35 | ||
|
|
9094976085 | ||
|
|
b592f6b464 | ||
|
|
4f2affe70f | ||
|
|
0733531f67 | ||
|
|
68dcb25911 | ||
|
|
795361c6ad | ||
|
|
c9190df50a | ||
|
|
15160537f5 | ||
|
|
1f1e755503 | ||
|
|
aea3f6bd6b | ||
|
|
2bf40cac28 |
@@ -25,9 +25,9 @@ jobs:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install flake8 pytest pipenv
|
||||
pipenv install --system
|
||||
python -m venv .venv
|
||||
.venv/bin/python -m pip install --upgrade pip
|
||||
.venv/bin/python -m pip install -e . flake8 pytest coverage
|
||||
# - name: Run pipenv check
|
||||
# run: |
|
||||
# # DDoS attacks in wheel and setuptools packages, not relevant
|
||||
@@ -50,15 +50,15 @@ jobs:
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
flake8 . --count --select=E9,F63,F7,F82 --ignore=F824 --show-source --statistics
|
||||
.venv/bin/flake8 . --exclude=.venv --count --select=E9,F63,F7,F82 --ignore=F824 --show-source --statistics
|
||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
.venv/bin/flake8 . --exclude=.venv --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
- name: Run tconnectsync --help
|
||||
run: |
|
||||
tconnectsync --help
|
||||
.venv/bin/tconnectsync --help
|
||||
- name: Test with pytest
|
||||
run: |
|
||||
pytest
|
||||
.venv/bin/pytest
|
||||
- name: Check codecov configuration
|
||||
run: |
|
||||
curl -X POST --data-binary @.codecov.yml https://codecov.io/validate
|
||||
@@ -69,8 +69,7 @@ jobs:
|
||||
fi
|
||||
- name: Generate Coverage Report
|
||||
run: |
|
||||
pip install coverage
|
||||
coverage run -m unittest
|
||||
.venv/bin/coverage run -m unittest
|
||||
- name: Upload Coverage to Codecov
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
|
||||
@@ -5,26 +5,15 @@ verify_ssl = true
|
||||
|
||||
[dev-packages]
|
||||
ptpython = "*"
|
||||
flake8 = "*"
|
||||
pytest = "*"
|
||||
coverage = "*"
|
||||
|
||||
[packages]
|
||||
tconnectsync = {path = "."}
|
||||
bs4 = "*"
|
||||
arrow = "==1.2.3"
|
||||
lxml = "*"
|
||||
python-dotenv = "==0.21.1"
|
||||
requests-mock = "*"
|
||||
pysocks = "*"
|
||||
urllib3 = "==1.26.6"
|
||||
requests = {extras = ["socks"], version = "==2.31.0"}
|
||||
requests-oidc = "*"
|
||||
PyJWT = "==2.8.0"
|
||||
cryptography = "*"
|
||||
dataclasses-json = "*"
|
||||
cffi = ">=1.15.1"
|
||||
typing-extensions = "*"
|
||||
|
||||
[scripts]
|
||||
tconnectsync = "python3 main.py"
|
||||
test = "python3 -m unittest discover -vv"
|
||||
build_events = "bash -c 'cd tconnectsync/eventparser && python3 build_events.py > events.py'"
|
||||
lint = "bash -c 'flake8 . --count --select=E9,F63,F7,F82 && flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 && echo PASS'"
|
||||
lint = "bash -c 'flake8 . --count --select=E9,F63,F7,F82 && flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 && echo PASS'"
|
||||
|
||||
Generated
+667
-431
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = tconnectsync
|
||||
version = 2.3.2
|
||||
version = 2.3.5
|
||||
author = James Woglom
|
||||
author_email = j@wogloms.net
|
||||
description = Syncs Tandem Source (formerly t:connect) insulin pump data to Nightscout for the t:slim X2
|
||||
@@ -30,11 +30,13 @@ install_requires =
|
||||
urllib3
|
||||
requests
|
||||
requests-oidc
|
||||
PyJWT
|
||||
cryptography
|
||||
PyJWT==2.8.0
|
||||
cryptography==43.0.1; python_version < "3.9"
|
||||
cryptography; python_version >= "3.9"
|
||||
dataclasses-json
|
||||
cffi
|
||||
typing-extensions
|
||||
importlib-metadata; python_version < "3.8"
|
||||
|
||||
[options.packages.find]
|
||||
where = .
|
||||
|
||||
@@ -3,13 +3,15 @@ import datetime
|
||||
import arrow
|
||||
import argparse
|
||||
import logging
|
||||
import pkg_resources
|
||||
import typing
|
||||
|
||||
# Required for cryptography lib in python 3.7
|
||||
if sys.version_info < (3, 8):
|
||||
import typing_extensions
|
||||
typing.Protocol = typing_extensions.Protocol
|
||||
from importlib_metadata import PackageNotFoundError, version
|
||||
else:
|
||||
from importlib.metadata import PackageNotFoundError, version
|
||||
|
||||
from .api import TConnectApi
|
||||
from .sync.tandemsource.autoupdate import TandemSourceAutoupdate
|
||||
@@ -37,8 +39,8 @@ except Exception as e:
|
||||
|
||||
|
||||
try:
|
||||
__version__ = pkg_resources.require("tconnectsync")[0].version
|
||||
except Exception:
|
||||
__version__ = version("tconnectsync")
|
||||
except PackageNotFoundError:
|
||||
__version__ = "UNKNOWN"
|
||||
|
||||
def parse_args(*args, **kwargs):
|
||||
@@ -126,4 +128,3 @@ def main(*args, **kwargs):
|
||||
|
||||
# return exit code 0 if processed events
|
||||
sys.exit(0 if added>0 else 1)
|
||||
|
||||
|
||||
@@ -3,11 +3,15 @@ import time
|
||||
import arrow
|
||||
import logging
|
||||
import traceback
|
||||
import pkg_resources
|
||||
import collections
|
||||
from datetime import datetime
|
||||
from pprint import pformat as pformat_base
|
||||
|
||||
if sys.version_info < (3, 8):
|
||||
from importlib_metadata import PackageNotFoundError, version
|
||||
else:
|
||||
from importlib.metadata import PackageNotFoundError, version
|
||||
|
||||
from .nightscout import NightscoutApi
|
||||
from .parser.nightscout import BASAL_EVENTTYPE, BOLUS_EVENTTYPE
|
||||
from .parser.tconnect import TConnectEntry
|
||||
@@ -15,8 +19,8 @@ from .domain.tandemsource.event_class import EventClass
|
||||
from .sync.tandemsource.choose_device import ChooseDevice
|
||||
|
||||
try:
|
||||
__version__ = pkg_resources.require("tconnectsync")[0].version
|
||||
except Exception:
|
||||
__version__ = version("tconnectsync")
|
||||
except PackageNotFoundError:
|
||||
__version__ = "UNKNOWN"
|
||||
|
||||
"""
|
||||
@@ -205,4 +209,4 @@ def run_sanitize(s, sanitizedData):
|
||||
|
||||
def pformat(*args, **kwargs):
|
||||
kwargs['width'] = 160
|
||||
return pformat_base(*args, **kwargs)
|
||||
return pformat_base(*args, **kwargs)
|
||||
|
||||
@@ -6,6 +6,7 @@ from ..secret import TIMEZONE_NAME
|
||||
from dataclasses import dataclass
|
||||
|
||||
EVENT_LEN = 26
|
||||
# Big endian
|
||||
UINT16 = '>H'
|
||||
UINT32 = '>I'
|
||||
TANDEM_EPOCH = 1199145600
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import logging
|
||||
import collections
|
||||
import arrow
|
||||
|
||||
from ...features import DEVICE_STATUS, DEFAULT_FEATURES
|
||||
from ...eventparser import events as eventtypes
|
||||
@@ -82,7 +83,9 @@ class ProcessTimeRange:
|
||||
if c.enabled():
|
||||
logger.info("%s is enabled from features %s" % (clazz, self.features))
|
||||
# Cap events_last_time at time_end to handle pump clock drift
|
||||
capped_time_end = min(events_last_time, time_end) if events_last_time else time_end
|
||||
# Ensure time_end is timezone-aware for comparison
|
||||
time_end_aware = arrow.get(time_end)
|
||||
capped_time_end = min(events_last_time, time_end_aware) if events_last_time else time_end_aware
|
||||
ns_entries = c.process(events, events_first_time, capped_time_end)
|
||||
w = c.write(ns_entries)
|
||||
if w:
|
||||
|
||||
@@ -3,6 +3,7 @@ import arrow
|
||||
|
||||
from ...features import DEFAULT_FEATURES
|
||||
from ... import features
|
||||
from ... import secret
|
||||
from ...eventparser.generic import Events, decode_raw_events, EVENT_LEN
|
||||
from ...eventparser.utils import bitmask_to_list
|
||||
from ...eventparser.raw_event import TANDEM_EPOCH
|
||||
@@ -16,12 +17,13 @@ from ...parser.nightscout import (
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ProcessCGMReading:
|
||||
def __init__(self, tconnect, nightscout, tconnect_device_id, pretend, features=DEFAULT_FEATURES):
|
||||
def __init__(self, tconnect, nightscout, tconnect_device_id, pretend, features=DEFAULT_FEATURES, timezone=None):
|
||||
self.tconnect = tconnect
|
||||
self.nightscout = nightscout
|
||||
self.tconnect_device_id = tconnect_device_id
|
||||
self.pretend = pretend
|
||||
self.features = features
|
||||
self.timezone = timezone or secret.TIMEZONE_NAME
|
||||
|
||||
def enabled(self):
|
||||
return features.CGM in self.features
|
||||
@@ -66,7 +68,7 @@ class ProcessCGMReading:
|
||||
def timestamp_for(self, event):
|
||||
# For backfills the time the event was added to the pump's event store
|
||||
# might not be the time it actually occurred, so we use the egvTimestamp
|
||||
return arrow.get(TANDEM_EPOCH + event.egvTimestamp)
|
||||
return arrow.get(TANDEM_EPOCH + event.egvTimestamp, tzinfo='UTC').replace(tzinfo=self.timezone)
|
||||
|
||||
def to_nsentry(self, event):
|
||||
return NightscoutEntry.entry(
|
||||
|
||||
@@ -0,0 +1,358 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import unittest
|
||||
import arrow
|
||||
|
||||
from tconnectsync.sync.tandemsource.process_cgm_reading import ProcessCGMReading
|
||||
from tconnectsync.eventparser import events as eventtypes
|
||||
from tconnectsync.eventparser.generic import Event
|
||||
|
||||
from ...api.fake import TConnectApi
|
||||
from ...nightscout_fake import NightscoutApi
|
||||
|
||||
# timestamp 2025-12-13T18:16:44-0500, value 80
|
||||
G7_EVENT_1 = b'\x01\x8f!\xc4+\x0f\x00\x07\xb8\r\xf7\x01\x00\x00\x00P\xc7 !\xc4+\x0c\x00\x00\x19\xe1'
|
||||
# timestamp 2025-12-13T18:21:44-0500, value 71
|
||||
G7_EVENT_2 = b'\x01\x8f!\xc4,;\x00\x07\xb8\x1a\xf4\x01\x00\x00\x00G\xc1 !\xc4,8\x00\x00\x19\xe1'
|
||||
# timestamp 2025-12-13T18:46:44-0500, value 119
|
||||
G7_EVENT_3 = b'\x01\x8f!\xc42\x17\x00\x07\xb8\xd6\x13\x01\x00\x00\x00w\xc1 !\xc42\x14\x00\x00\x19\xe1'
|
||||
# timestamp 2025-12-13T18:51:44-0500, value 105
|
||||
G7_EVENT_4 = b'\x01\x8f!\xc43C\x00\x07\xb8\xea\x13\x01\x00\x00\x00\x81\xbf !\xc43@\x00\x00\x19\xe1'
|
||||
# timestamp 2025-12-13T18:56:44-0500, value 98
|
||||
G7_EVENT_5 = b"\x01\x8f!\xc45\x9b\x00\x07\xb9\'\x11\x01\x00\x00\x00\x92\xbb !\xc45\x98\x00\x00\x19\xe1"
|
||||
|
||||
|
||||
class TestProcessCGMReadingG7(unittest.TestCase):
|
||||
"""Test with only G7 reading data"""
|
||||
maxDiff = None
|
||||
|
||||
def setUp(self):
|
||||
self.tconnect = TConnectApi()
|
||||
self.nightscout = NightscoutApi()
|
||||
self.nightscout.last_uploaded_bg_entry = lambda *args, **kwargs: None
|
||||
self.tconnect_device_id = 'abcdef'
|
||||
self.process = ProcessCGMReading(self.tconnect, self.nightscout, self.tconnect_device_id, pretend=False, timezone='America/New_York')
|
||||
|
||||
def test_single_g7_reading_no_last_uploaded(self):
|
||||
"""Test processing a single G7 CGM reading with no prior uploads"""
|
||||
events = [Event(G7_EVENT_1)]
|
||||
|
||||
self.assertEqual(type(events[0]), eventtypes.LidCgmDataG7)
|
||||
self.assertEqual(events[0].raw.timestampRaw, 566504207)
|
||||
self.assertEqual(events[0].egvTimestamp, 566504204)
|
||||
self.assertEqual(events[0].seqNum, 505869)
|
||||
self.assertEqual(events[0].currentglucosedisplayvalue, 80)
|
||||
self.assertEqual(events[0].rateRaw, -9)
|
||||
|
||||
p = self.process.process(events, time_start=None, time_end=None)
|
||||
|
||||
self.assertEqual(len(p), 1)
|
||||
self.assertIn('sgv', p[0])
|
||||
self.assertEqual(p[0]['sgv'], 80)
|
||||
self.assertEqual(p[0]['dateString'], '2025-12-13T18:16:44-0500')
|
||||
|
||||
def test_multiple_g7_readings(self):
|
||||
"""Test processing multiple G7 CGM readings"""
|
||||
events = [
|
||||
Event(G7_EVENT_1),
|
||||
Event(G7_EVENT_2),
|
||||
Event(G7_EVENT_3)
|
||||
]
|
||||
|
||||
# Verify all events are LidCgmDataG7
|
||||
for event in events:
|
||||
self.assertEqual(type(event), eventtypes.LidCgmDataG7)
|
||||
|
||||
self.assertEqual(events[0].currentglucosedisplayvalue, 80)
|
||||
self.assertEqual(events[1].currentglucosedisplayvalue, 71)
|
||||
self.assertEqual(events[2].currentglucosedisplayvalue, 119)
|
||||
|
||||
p = self.process.process(events, time_start=None, time_end=None)
|
||||
|
||||
self.assertEqual(len(p), 3)
|
||||
|
||||
# Check glucose values
|
||||
self.assertEqual(p[0]['sgv'], 80)
|
||||
self.assertEqual(p[0]['dateString'], '2025-12-13T18:16:44-0500')
|
||||
self.assertEqual(p[1]['sgv'], 71)
|
||||
self.assertEqual(p[1]['dateString'], '2025-12-13T18:21:44-0500')
|
||||
self.assertEqual(p[2]['sgv'], 119)
|
||||
self.assertEqual(p[2]['dateString'], '2025-12-13T18:46:44-0500')
|
||||
|
||||
def test_g7_reading_with_last_uploaded(self):
|
||||
"""Test that already uploaded readings are skipped"""
|
||||
events = [
|
||||
Event(G7_EVENT_1),
|
||||
Event(G7_EVENT_2),
|
||||
Event(G7_EVENT_3)
|
||||
]
|
||||
|
||||
# Set last upload time to be between event 1 and event 2
|
||||
event_1_time = self.process.timestamp_for(events[0])
|
||||
event_2_time = self.process.timestamp_for(events[1])
|
||||
|
||||
# Set last upload to event 1's timestamp
|
||||
self.nightscout.last_uploaded_bg_entry = lambda *args, **kwargs: {
|
||||
'dateString': event_1_time.format()
|
||||
}
|
||||
|
||||
p = self.process.process(events, time_start=None, time_end=None)
|
||||
|
||||
# Only events 2 and 3 should be processed (after the last upload time)
|
||||
self.assertEqual(len(p), 2)
|
||||
self.assertEqual(p[0]['sgv'], 71)
|
||||
self.assertEqual(p[0]['dateString'], '2025-12-13T18:21:44-0500')
|
||||
self.assertEqual(p[1]['sgv'], 119)
|
||||
self.assertEqual(p[1]['dateString'], '2025-12-13T18:46:44-0500')
|
||||
|
||||
|
||||
# timestamp 2022-01-28T00:01:09, value 75
|
||||
G6_EVENT_1 = b'\x01\x00\x1ay\xaf\xc5\x00\x02\x00m\xfc\x01\x00\x00\x00K\xaf\x06\x1ay\xaf\xc5\x01\x00\x01\xe1'
|
||||
|
||||
# timestamp 2022-01-28T00:06:08, value 77
|
||||
G6_EVENT_2 = b'\x01\x00\x1ay\xb0\xf0\x00\x02\x00v\xfd\x01\x00\x00\x00M\xa8\x06\x1ay\xb0\xf0\x01\x00\x01\xe1'
|
||||
|
||||
# timestamp 2022-01-28T01:51:07, value 76, BACKFILL
|
||||
G6_EVENT_3 = b'\x01\x00\x1ay\xca\xb7\x00\x02\x01>\x00\x02\x00\x00\x00L\xb5\x06\x1ay\xc9\x8b\x00\x01\x01\xe2'
|
||||
|
||||
# timestamp 2022-01-28T01:56:07, value 75
|
||||
G6_EVENT_4 = b'\x01\x00\x1ay\xca\xb7\x00\x02\x01=\x01\x01\x00\x00\x00K\xb5\x06\x1ay\xca\xb7\x02\x00\x01\xe1'
|
||||
|
||||
# timestamp 2022-01-28T02:01:06, value 74
|
||||
G6_EVENT_5 = b'\x01\x00\x1ay\xcb\xe2\x00\x02\x01G\x00\x01\x00\x00\x00J\xb5\x06\x1ay\xcb\xe2\x01\x00\x01\xe1'
|
||||
|
||||
class TestProcessCGMReadingG6(unittest.TestCase):
|
||||
"""Test with only G6 reading data"""
|
||||
maxDiff = None
|
||||
|
||||
def setUp(self):
|
||||
self.tconnect = TConnectApi()
|
||||
self.nightscout = NightscoutApi()
|
||||
self.nightscout.last_uploaded_bg_entry = lambda *args, **kwargs: None
|
||||
self.tconnect_device_id = 'abcdef'
|
||||
self.process = ProcessCGMReading(self.tconnect, self.nightscout, self.tconnect_device_id, pretend=False, timezone='America/New_York')
|
||||
|
||||
def test_single_g6_reading_no_last_uploaded(self):
|
||||
"""Test processing a single G6 CGM reading with no prior uploads"""
|
||||
events = [Event(G6_EVENT_1)]
|
||||
|
||||
# timestamp 2022-01-28T00:01:09 confirmed w/ tandem csv export
|
||||
self.assertEqual(type(events[0]), eventtypes.LidCgmDataGxb)
|
||||
self.assertEqual(events[0].raw.timestampRaw, 444182469)
|
||||
self.assertEqual(events[0].egvTimestamp, 444182469)
|
||||
self.assertEqual(events[0].currentglucosedisplayvalue, 75)
|
||||
self.assertEqual(events[0].rateRaw, -4)
|
||||
|
||||
p = self.process.process(events, time_start=None, time_end=None)
|
||||
|
||||
self.assertEqual(len(p), 1)
|
||||
self.assertIn('sgv', p[0])
|
||||
self.assertEqual(p[0]['sgv'], 75)
|
||||
self.assertEqual(p[0]['dateString'], '2022-01-28T00:01:09-0500')
|
||||
|
||||
def test_multiple_g6_readings(self):
|
||||
"""Test processing multiple G6 CGM readings"""
|
||||
events = [
|
||||
Event(G6_EVENT_1),
|
||||
Event(G6_EVENT_2),
|
||||
Event(G6_EVENT_3)
|
||||
]
|
||||
|
||||
# Verify all events are LidCgmDataGxb
|
||||
for event in events:
|
||||
self.assertEqual(type(event), eventtypes.LidCgmDataGxb)
|
||||
|
||||
self.assertEqual(events[0].currentglucosedisplayvalue, 75)
|
||||
self.assertEqual(events[1].currentglucosedisplayvalue, 77)
|
||||
self.assertEqual(events[2].currentglucosedisplayvalue, 76)
|
||||
|
||||
p = self.process.process(events, time_start=None, time_end=None)
|
||||
|
||||
self.assertEqual(len(p), 3)
|
||||
|
||||
self.assertEqual(p[0]['sgv'], 75)
|
||||
self.assertEqual(p[0]['dateString'], '2022-01-28T00:01:09-0500')
|
||||
self.assertEqual(p[1]['sgv'], 77)
|
||||
self.assertEqual(p[1]['dateString'], '2022-01-28T00:06:08-0500')
|
||||
self.assertEqual(p[2]['sgv'], 76)
|
||||
self.assertEqual(p[2]['dateString'], '2022-01-28T01:51:07-0500')
|
||||
|
||||
def test_multiple_g6_readings_with_backfill(self):
|
||||
"""Test processing multiple G6 CGM readings"""
|
||||
events = [
|
||||
Event(G6_EVENT_1),
|
||||
Event(G6_EVENT_2),
|
||||
Event(G6_EVENT_3),
|
||||
Event(G6_EVENT_4),
|
||||
Event(G6_EVENT_5)
|
||||
]
|
||||
|
||||
# Verify all events are LidCgmDataGxb
|
||||
for event in events:
|
||||
self.assertEqual(type(event), eventtypes.LidCgmDataGxb)
|
||||
|
||||
self.assertEqual(events[0].currentglucosedisplayvalue, 75)
|
||||
self.assertEqual(events[1].currentglucosedisplayvalue, 77)
|
||||
self.assertEqual(events[2].currentglucosedisplayvalue, 76) # backfill
|
||||
self.assertEqual(events[3].currentglucosedisplayvalue, 75)
|
||||
self.assertEqual(events[4].currentglucosedisplayvalue, 74)
|
||||
|
||||
self.assertEqual(events[0].raw.timestampRaw, 444182469)
|
||||
self.assertEqual(events[1].raw.timestampRaw, 444182768)
|
||||
self.assertEqual(events[2].raw.timestampRaw, 444189367)
|
||||
self.assertEqual(events[3].raw.timestampRaw, 444189367)
|
||||
self.assertEqual(events[4].raw.timestampRaw, 444189666)
|
||||
|
||||
self.assertEqual(events[0].egvTimestamp, 444182469)
|
||||
self.assertEqual(events[1].egvTimestamp, 444182768)
|
||||
self.assertEqual(events[2].egvTimestamp, 444189067)
|
||||
self.assertEqual(events[3].egvTimestamp, 444189367)
|
||||
self.assertEqual(events[4].egvTimestamp, 444189666)
|
||||
|
||||
|
||||
p = self.process.process(events, time_start=None, time_end=None)
|
||||
|
||||
self.assertEqual(len(p), 5)
|
||||
|
||||
self.assertEqual(p[0]['sgv'], 75)
|
||||
self.assertEqual(p[0]['dateString'], '2022-01-28T00:01:09-0500')
|
||||
self.assertEqual(p[1]['sgv'], 77)
|
||||
self.assertEqual(p[1]['dateString'], '2022-01-28T00:06:08-0500')
|
||||
self.assertEqual(p[2]['sgv'], 76)
|
||||
self.assertEqual(p[2]['dateString'], '2022-01-28T01:51:07-0500')
|
||||
self.assertEqual(p[3]['sgv'], 75)
|
||||
self.assertEqual(p[3]['dateString'], '2022-01-28T01:56:07-0500')
|
||||
self.assertEqual(p[4]['sgv'], 74)
|
||||
self.assertEqual(p[4]['dateString'], '2022-01-28T02:01:06-0500')
|
||||
|
||||
|
||||
def test_g6_reading_with_last_uploaded(self):
|
||||
"""Test that already uploaded G6 readings are skipped"""
|
||||
events = [
|
||||
Event(G6_EVENT_1),
|
||||
Event(G6_EVENT_2),
|
||||
Event(G6_EVENT_3)
|
||||
]
|
||||
|
||||
# Set last upload time using 'date' field (milliseconds)
|
||||
event_1_time = self.process.timestamp_for(events[0])
|
||||
self.nightscout.last_uploaded_bg_entry = lambda *args, **kwargs: {
|
||||
'date': event_1_time.timestamp() * 1000
|
||||
}
|
||||
|
||||
p = self.process.process(events, time_start=None, time_end=None)
|
||||
|
||||
# Only events 2 and 3 should be processed
|
||||
self.assertEqual(len(p), 2)
|
||||
self.assertEqual(p[0]['sgv'], 77)
|
||||
self.assertEqual(p[1]['sgv'], 76)
|
||||
|
||||
|
||||
class TestProcessCGMReadingWrite(unittest.TestCase):
|
||||
"""Tests for writing CGM readings to Nightscout"""
|
||||
maxDiff = None
|
||||
|
||||
def setUp(self):
|
||||
self.tconnect = TConnectApi()
|
||||
self.nightscout = NightscoutApi()
|
||||
self.nightscout.last_uploaded_bg_entry = lambda *args, **kwargs: None
|
||||
self.tconnect_device_id = 'abcdef'
|
||||
|
||||
def test_write_entries_pretend_mode(self):
|
||||
"""Test that pretend mode doesn't actually upload"""
|
||||
process = ProcessCGMReading(self.tconnect, self.nightscout, self.tconnect_device_id, pretend=True, timezone='America/New_York')
|
||||
|
||||
events = [Event(G7_EVENT_1)]
|
||||
|
||||
ns_entries = process.process(events, time_start=None, time_end=None)
|
||||
count = process.write(ns_entries)
|
||||
|
||||
self.assertEqual(count, 1)
|
||||
# In pretend mode, nothing should be uploaded
|
||||
self.assertEqual(len(self.nightscout.uploaded_entries.get('entries', [])), 0)
|
||||
|
||||
def test_write_entries_real_mode(self):
|
||||
"""Test that real mode uploads entries"""
|
||||
process = ProcessCGMReading(self.tconnect, self.nightscout, self.tconnect_device_id, pretend=False, timezone='America/New_York')
|
||||
|
||||
events = [Event(G7_EVENT_1)]
|
||||
|
||||
ns_entries = process.process(events, time_start=None, time_end=None)
|
||||
count = process.write(ns_entries)
|
||||
|
||||
self.assertEqual(count, 1)
|
||||
# In real mode, entries should be uploaded
|
||||
self.assertEqual(len(self.nightscout.uploaded_entries['entries']), 1)
|
||||
self.assertEqual(self.nightscout.uploaded_entries['entries'][0]['sgv'], 80)
|
||||
self.assertEqual(self.nightscout.uploaded_entries['entries'][0]['dateString'], '2025-12-13T18:16:44-0500')
|
||||
|
||||
def test_write_mixed_g6_and_g7(self):
|
||||
"""Test writing a mix of G6 and G7 readings"""
|
||||
process = ProcessCGMReading(self.tconnect, self.nightscout, self.tconnect_device_id, pretend=False, timezone='America/New_York')
|
||||
|
||||
events = [
|
||||
Event(G6_EVENT_1),
|
||||
Event(G6_EVENT_2),
|
||||
Event(G7_EVENT_1),
|
||||
Event(G7_EVENT_2)
|
||||
]
|
||||
|
||||
ns_entries = process.process(events, time_start=None, time_end=None)
|
||||
count = process.write(ns_entries)
|
||||
|
||||
self.assertEqual(count, 4)
|
||||
self.assertEqual(len(self.nightscout.uploaded_entries['entries']), 4)
|
||||
# g6
|
||||
self.assertEqual(self.nightscout.uploaded_entries['entries'][0]['sgv'], 75)
|
||||
self.assertEqual(self.nightscout.uploaded_entries['entries'][0]['dateString'], '2022-01-28T00:01:09-0500')
|
||||
self.assertEqual(self.nightscout.uploaded_entries['entries'][1]['sgv'], 77)
|
||||
self.assertEqual(self.nightscout.uploaded_entries['entries'][1]['dateString'], '2022-01-28T00:06:08-0500')
|
||||
# g7
|
||||
self.assertEqual(self.nightscout.uploaded_entries['entries'][2]['sgv'], 80)
|
||||
self.assertEqual(self.nightscout.uploaded_entries['entries'][2]['dateString'], '2025-12-13T18:16:44-0500')
|
||||
self.assertEqual(self.nightscout.uploaded_entries['entries'][3]['sgv'], 71)
|
||||
self.assertEqual(self.nightscout.uploaded_entries['entries'][3]['dateString'], '2025-12-13T18:21:44-0500')
|
||||
|
||||
|
||||
class TestProcessCGMReadingMultipleTimezones(unittest.TestCase):
|
||||
"""Test timezone processing across TIMEZONE_NAME values."""
|
||||
maxDiff = None
|
||||
|
||||
def setUp(self):
|
||||
self.tconnect = TConnectApi()
|
||||
self.nightscout = NightscoutApi()
|
||||
self.nightscout.last_uploaded_bg_entry = lambda *args, **kwargs: None
|
||||
self.tconnect_device_id = 'abcdef'
|
||||
|
||||
def test_America_New_York(self):
|
||||
timezone = 'America/New_York'
|
||||
|
||||
events = [Event(G7_EVENT_1)]
|
||||
process = ProcessCGMReading(self.tconnect, self.nightscout, self.tconnect_device_id, pretend=False, timezone=timezone)
|
||||
p = process.process(events, time_start=None, time_end=None)
|
||||
|
||||
self.assertEqual(len(p), 1)
|
||||
self.assertEqual(p[0]['dateString'], '2025-12-13T18:16:44-0500')
|
||||
|
||||
def test_America_Chicago(self):
|
||||
timezone = 'America/Chicago'
|
||||
|
||||
events = [Event(G7_EVENT_1)]
|
||||
process = ProcessCGMReading(self.tconnect, self.nightscout, self.tconnect_device_id, pretend=False, timezone=timezone)
|
||||
p = process.process(events, time_start=None, time_end=None)
|
||||
|
||||
self.assertEqual(len(p), 1)
|
||||
self.assertEqual(p[0]['dateString'], '2025-12-13T18:16:44-0600')
|
||||
|
||||
def test_GMT(self):
|
||||
timezone = 'Etc/GMT'
|
||||
|
||||
events = [Event(G7_EVENT_1)]
|
||||
process = ProcessCGMReading(self.tconnect, self.nightscout, self.tconnect_device_id, pretend=False, timezone=timezone)
|
||||
p = process.process(events, time_start=None, time_end=None)
|
||||
|
||||
self.assertEqual(len(p), 1)
|
||||
self.assertEqual(p[0]['dateString'], '2025-12-13T18:16:44+0000')
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user