mirror of
https://github.com/bckelley/tconnectsync.git
synced 2026-08-24 03:34:12 -05:00
arrow's isoformat() ends a timestamp with its UTC offset, e.g. '2026-07-16T00:00:00+02:00'. Placed raw into a query string, '+' is a reserved character that servers decode as a space, so Nightscout receives '2026-07-16T00:00:00 02:00' and answers 'could not parse as a valid ISO-8601 date'. Any user on a positive UTC offset hits this on every date-filtered query. The current workaround retries the request with 'T' replaced by a space (t_to_space) and treats a failure as 'no previous entry'. That gets a response, but it works around the symptom: the value is still mangled, every affected query costs two round-trips, and a genuinely empty result is indistinguishable from a rejected one. Percent-encoding the value fixes the cause: the offset survives, the first request succeeds, and the t_to_space fallback and its retry wrapper are no longer needed and are removed. Adds tests for time_range(): that a positive offset is encoded rather than emitted raw, that the encoded value round-trips back to the original instant, that negative offsets and 'Z' still parse, and the bounds/no-bounds cases. There was no coverage of time_range() before. The encoding test fails on master with '+' present in the query and passes with this change. Running with this in my EU deployment since May.
69 lines
2.5 KiB
Python
69 lines
2.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Tests for the Nightscout date-filter query building in time_range()."""
|
|
|
|
import unittest
|
|
import urllib.parse
|
|
|
|
import arrow
|
|
|
|
from tconnectsync.nightscout import time_range
|
|
|
|
|
|
class TestTimeRangeEncoding(unittest.TestCase):
|
|
"""A positive UTC offset ends an ISO-8601 timestamp with '+02:00'. Placed
|
|
raw into a query string, the '+' is a reserved character that servers decode
|
|
as a space, so Nightscout receives '2026-07-16T00:00:00 02:00' and rejects
|
|
it with "could not parse as a valid ISO-8601 date". Percent-encoding the
|
|
value keeps the offset intact."""
|
|
|
|
def test_positive_offset_is_percent_encoded(self):
|
|
start = arrow.get("2026-07-16T00:00:00+02:00")
|
|
|
|
arg = time_range('created_at', start, None)
|
|
|
|
self.assertNotIn(
|
|
'+', arg,
|
|
"A raw '+' in the query string is decoded to a space by the server, "
|
|
"mangling the timestamp. Got: %s" % arg,
|
|
)
|
|
self.assertIn('%2B', arg, "Expected the offset '+' to be encoded as %%2B. Got: %s" % arg)
|
|
|
|
def test_encoded_value_round_trips_to_the_original_timestamp(self):
|
|
"""Decoding the query the way a server would must yield the timestamp
|
|
we meant to send."""
|
|
start = arrow.get("2026-07-16T00:00:00+02:00")
|
|
|
|
arg = time_range('created_at', start, None)
|
|
|
|
value = arg.split('=', 1)[1]
|
|
self.assertEqual(
|
|
arrow.get(urllib.parse.unquote(value)), start,
|
|
"Round-tripping the encoded filter must reproduce the original instant",
|
|
)
|
|
|
|
def test_negative_offset_and_z_suffix_still_parse(self):
|
|
"""US pumps report a '-04:00' offset and UTC values end in 'Z'; neither
|
|
is ambiguous in a query string, but both must survive encoding."""
|
|
for iso in ("2026-07-16T00:00:00-04:00", "2026-07-16T00:00:00Z"):
|
|
with self.subTest(iso=iso):
|
|
expected = arrow.get(iso)
|
|
arg = time_range('created_at', expected, None)
|
|
value = arg.split('=', 1)[1]
|
|
self.assertEqual(arrow.get(urllib.parse.unquote(value)), expected)
|
|
|
|
def test_both_bounds_are_emitted(self):
|
|
start = arrow.get("2026-07-16T00:00:00+02:00")
|
|
end = arrow.get("2026-07-17T00:00:00+02:00")
|
|
|
|
arg = time_range('created_at', start, end)
|
|
|
|
self.assertIn('find[created_at][$gte]=', arg)
|
|
self.assertIn('find[created_at][$lte]=', arg)
|
|
|
|
def test_omitted_bounds_produce_no_filter(self):
|
|
self.assertEqual(time_range('created_at', None, None), '')
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|