diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 5b036714..b1887704 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -14,10 +14,10 @@ jobs: matrix: platform: - ubuntu-latest - python-version: ['3.9'] + python-version: ['3.11'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: @@ -33,7 +33,7 @@ jobs: run: | tox -r -e cov - name: Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} flags: unittests diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 58510477..da2d3818 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,10 +14,10 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9] + python-version: [3.11] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 517d64fa..3c35998c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,7 +17,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.11' - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4b8812e3..d424c132 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: matrix: platform: - ubuntu-latest - python-version: ['3.8', '3.9', '3.10', '3.11'] + python-version: ['3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v3 diff --git a/CHANGES.rst b/CHANGES.rst index 403d98bf..cf43887d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,13 +4,42 @@ Changelog A list of changes between each release +0.22.5 (2024-01-07) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Warning: This release removes support for Python 3.8 and adds Python 3.12 support. + +**Bugfixes** + +- Add new keys for wifi, lfr, and battery (`mkmer #835 `__) +- Battery level (`mkmer #837 `__) +- Address not awaited warning (`mkmer #838 `__) +- Catch ContentTypeError in 2FA (`mkmer #843 `__) +- Handle empty put response in wait_command (`mkmer #847 `__) +- Change default user agent to fix API calls (`gingerm0nkey #848 `__) +- Android for new user agent (`mkmer #850 `__) + +**Other changes** + +- Remove Py3.8, add 3.12 (`fronzbot #839 `__) +- Deprecate py38 (`fronzbot #840 `__) +- Add/extract firmware version (`mkmer #842 `__) +- Additional logging, fix blinksync json (`mkmer #844 `__) +- Log text response (`mkmer #845 `__) +- Add tests for logging (`mkmer #846 `__) +- Bump ruff to 0.1.11 +- Bump black to 23.12.1 +- Bump coverage to 7.4.0 +- Bump pytest to 7.4.4 + + 0.22.4 (2023-12-18) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ **Bugfixes** -- Allow kwargs to throttled functions, await sleep in throttle (`mkmer #823 `__) -- add missing entry in type_key_map (`@Rosi2143 `__) +- Allow kwargs to throttled functions, await sleep in throttle (`mkmer #823 `__) +- add missing entry in type_key_map (`@Rosi2143 #813 `__) ** Other Changes ** diff --git a/README.rst b/README.rst index a2767e39..269ee368 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,6 @@ blinkpy |Build Status| |Coverage Status| |PyPi Version| |Codestyle| ============================================================================================= -A Python library for the Blink Camera system (Python 3.8+) +A Python library for the Blink Camera system (Python 3.9+) Like the library? Consider buying me a cup of coffee! @@ -32,9 +32,7 @@ To install the current development version, perform the following steps. Note t $ cd ~ $ git clone https://github.com/fronzbot/blinkpy.git $ cd blinkpy - $ rm -rf build dist - $ python3 setup.py bdist_wheel - $ pip3 install --upgrade dist/*.whl + $ pip install . If you'd like to contribute to this library, please read the `contributing instructions `__. diff --git a/blinkpy/api.py b/blinkpy/api.py index a10c4497..82485dad 100644 --- a/blinkpy/api.py +++ b/blinkpy/api.py @@ -503,8 +503,11 @@ async def http_post(blink, url, is_retry=False, data=None, json=True, timeout=TI async def wait_for_command(blink, json_data: dict) -> bool: """Wait for command to complete.""" _LOGGER.debug("Command Wait %s", json_data) - network_id = json_data.get("network_id") - command_id = json_data.get("id") + try: + network_id = json_data.get("network_id") + command_id = json_data.get("id") + except AttributeError: + return False if command_id and network_id: for _ in range(0, MAX_RETRY): _LOGGER.debug("Making GET request waiting for command") diff --git a/blinkpy/auth.py b/blinkpy/auth.py index 09eab0a0..bb559907 100644 --- a/blinkpy/auth.py +++ b/blinkpy/auth.py @@ -1,6 +1,11 @@ """Login handler for blink.""" import logging -from aiohttp import ClientSession, ClientConnectionError +from aiohttp import ( + ClientSession, + ClientConnectionError, + ContentTypeError, + ClientResponse, +) from blinkpy import api from blinkpy.helpers import util from blinkpy.helpers.constants import ( @@ -123,7 +128,7 @@ async def startup(self): if None in self.login_attributes.values(): await self.refresh_token() - async def validate_response(self, response, json_resp): + async def validate_response(self, response: ClientResponse, json_resp): """Check for valid response.""" if not json_resp: self.is_errored = False @@ -137,6 +142,9 @@ async def validate_response(self, response, json_resp): json_data = await response.json() except (AttributeError, ValueError) as error: raise BlinkBadResponse from error + except ContentTypeError as error: + _LOGGER.warning("Got text for JSON response: %s", await response.text()) + raise BlinkBadResponse from error self.is_errored = False return json_data @@ -172,10 +180,11 @@ async def query( url=url, data=data, headers=headers, timeout=timeout ) return await self.validate_response(response, json_resp) - except (ClientConnectionError, TimeoutError): + except (ClientConnectionError, TimeoutError) as er: _LOGGER.error( - "Connection error. Endpoint %s possibly down or throttled.", + "Connection error. Endpoint %s possibly down or throttled. Error: %s", url, + er, ) except BlinkBadResponse: code = None @@ -220,8 +229,11 @@ async def send_auth_key(self, blink, key): if not blink.available: _LOGGER.error("%s", json_resp["message"]) return False - except (KeyError, TypeError): - _LOGGER.error("Did not receive valid response from server.") + except (KeyError, TypeError, ContentTypeError) as er: + _LOGGER.error( + "Did not receive valid response from server. Error: %s", + er, + ) return False return True diff --git a/blinkpy/camera.py b/blinkpy/camera.py index bc5c6d80..ba0a37b1 100644 --- a/blinkpy/camera.py +++ b/blinkpy/camera.py @@ -27,8 +27,9 @@ def __init__(self, sync): self.network_id = None self.thumbnail = None self.serial = None + self.version = None self.motion_enabled = None - self.battery_voltage = None + self.battery_level = None self.clip = None # A clip remains in the recent clips list until is has # been downloaded or has been expired. @@ -43,6 +44,7 @@ def __init__(self, sync): self._cached_video = None self.camera_type = "" self.product_type = None + self.sync_signal_strength = None @property def attributes(self): @@ -51,11 +53,12 @@ def attributes(self): "name": self.name, "camera_id": self.camera_id, "serial": self.serial, + "version": self.version, "temperature": self.temperature, "temperature_c": self.temperature_c, "temperature_calibrated": self.temperature_calibrated, "battery": self.battery, - "battery_voltage": self.battery_voltage, + "battery_level": self.battery_level, "thumbnail": self.thumbnail, "video": self.clip, "recent_clips": self.recent_clips, @@ -64,6 +67,7 @@ def attributes(self): "wifi_strength": self.wifi_strength, "network_id": self.sync.network_id, "sync_module": self.sync.name, + "sync_signal_strength": self.sync_signal_strength, "last_record": self.last_record, "type": self.product_type, } @@ -229,15 +233,18 @@ def extract_config_info(self, config): self.name = config.get("name", "unknown") self.camera_id = str(config.get("id", "unknown")) self.network_id = str(config.get("network_id", "unknown")) - self.serial = config.get("serial", None) + self.serial = config.get("serial") + self.version = config.get("fw_version") self.motion_enabled = config.get("enabled", "unknown") - self.battery_voltage = config.get("battery_voltage", None) - self.battery_state = config.get("battery_state", None) or config.get( - "battery", None - ) - self.temperature = config.get("temperature", None) - self.wifi_strength = config.get("wifi_strength", None) - self.product_type = config.get("type", None) + self.battery_state = config.get("battery_state") or config.get("battery") + self.temperature = config.get("temperature") + if signals := config.get("signals"): + self.wifi_strength = signals.get("wifi") + self.battery_level = signals.get("battery") + self.sync_signal_strength = signals.get("lfr") + else: + self.wifi_strength = config.get("wifi_strength") + self.product_type = config.get("type") async def get_sensor_info(self): """Retrieve calibrated temperature from special endpoint.""" diff --git a/blinkpy/helpers/constants.py b/blinkpy/helpers/constants.py index b208f56c..6af0671a 100644 --- a/blinkpy/helpers/constants.py +++ b/blinkpy/helpers/constants.py @@ -20,11 +20,8 @@ """ OTHER """ -DEFAULT_USER_AGENT = ( - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) " - "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/71.0.3578.98 Safari/537.36" -) + +DEFAULT_USER_AGENT = "27.0ANDROID_28373244" DEVICE_ID = "Blinkpy" TIMESTAMP_FORMAT = "%Y-%m-%dT%H:%M:%S%z" DEFAULT_MOTION_INTERVAL = 1 diff --git a/blinkpy/sync_module.py b/blinkpy/sync_module.py index 01af15c8..44f96a60 100644 --- a/blinkpy/sync_module.py +++ b/blinkpy/sync_module.py @@ -34,6 +34,7 @@ def __init__(self, blink, network_name, network_id, camera_list): self.region_id = blink.auth.region_id self.name = network_name self.serial = None + self.version = None self.status = "offline" self.sync_id = None self.host = None @@ -72,6 +73,7 @@ def attributes(self): "id": self.sync_id, "network_id": self.network_id, "serial": self.serial, + "version": self.version, "status": self.status, "region_id": self.region_id, "local_storage": self.local_storage, @@ -153,6 +155,7 @@ async def sync_initialize(self): "Could not retrieve sync module information with response: %s", response ) return False + self.version = self.summary.get("fw_version") return response async def _init_local_storage(self, sync_id): diff --git a/blinksync/blinksync.py b/blinksync/blinksync.py index 346c649f..8f21e236 100644 --- a/blinksync/blinksync.py +++ b/blinksync/blinksync.py @@ -100,6 +100,7 @@ async def main(): working = None frame = None await session.close() + await blink.save(f"{path}/blink.json") # Run the program diff --git a/pyproject.toml b/pyproject.toml index c88b1abf..0c703e76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "blinkpy" -version = "0.22.4" +version = "0.22.5" license = {text = "MIT"} description = "A Blink camera Python Library." readme = "README.rst" @@ -15,13 +15,13 @@ classifiers = [ "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Home Automation", ] -requires-python = ">=3.8.0" +requires-python = ">=3.9.0" dynamic = ["dependencies"] [tool.setuptools.dynamic] @@ -79,13 +79,14 @@ ignore = [ "UP006", # keep type annotation style as is "UP007", # keep type annotation style as is "UP015", # Unnecessary open mode parameters + "UP017", # UTC stuff # Ignored due to performance: https://github.com/charliermarsh/ruff/issues/2923 "UP038", # Use `X | Y` in `isinstance` call instead of `(X, Y)` ] line-length = 88 -target-version = "py39" +target-version = "py311" [tool.ruff.per-file-ignores] diff --git a/requirements_test.txt b/requirements_test.txt index 2eb03f58..3ec2cc31 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,8 +1,8 @@ -ruff==0.1.8 -black==23.12.0 +ruff==0.1.11 +black==23.12.1 build==1.0.3 -coverage==7.3.2 -pytest==7.4.3 +coverage==7.4.0 +pytest==7.4.4 pytest-cov==4.1.0 pytest-sugar==0.9.7 pytest-timeout==2.2.0 diff --git a/tests/mock_responses.py b/tests/mock_responses.py index 2e249d21..43a6d608 100644 --- a/tests/mock_responses.py +++ b/tests/mock_responses.py @@ -5,7 +5,14 @@ class MockResponse: """Class for mock request response.""" - def __init__(self, json_data, status_code, headers={}, raw_data=None): + def __init__( + self, + json_data, + status_code, + headers={}, + raw_data=None, + raise_error=None, + ): """Initialize mock get response.""" self.json_data = json_data self.status = status_code @@ -13,9 +20,13 @@ def __init__(self, json_data, status_code, headers={}, raw_data=None): self.reason = "foobar" self.headers = headers self.read = mock.AsyncMock(return_value=self.raw_data) + self.raise_error = raise_error + self.text = mock.AsyncMock(return_vlaue="some text") async def json(self): """Return json data from get_request.""" + if self.raise_error: + raise self.raise_error("I'm broken", "") return self.json_data def get(self, name): diff --git a/tests/test_api.py b/tests/test_api.py index 1c6e02d7..86edb1fd 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -183,3 +183,6 @@ async def test_wait_for_command(self, mock_resp): mock_resp.side_effect = (COMMAND_COMPLETE_BAD, {}) response = await api.wait_for_command(self.blink, COMMAND_RESPONSE) self.assertFalse(response) + + response = await api.wait_for_command(self.blink, None) + self.assertFalse(response) diff --git a/tests/test_auth.py b/tests/test_auth.py index c881fa8e..31a9bda5 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -2,7 +2,7 @@ from unittest import mock from unittest import IsolatedAsyncioTestCase -from aiohttp import ClientConnectionError +from aiohttp import ClientConnectionError, ContentTypeError from blinkpy.auth import ( Auth, TokenRefreshFailed, @@ -100,6 +100,10 @@ async def test_bad_response_code(self): with self.assertRaises(UnauthorizedError): await self.auth.validate_response(fake_resp, True) self.assertTrue(self.auth.is_errored) + fake_resp = mresp.MockResponse({"code": 101}, 406, raise_error=ContentTypeError) + with self.assertRaises(BlinkBadResponse): + await self.auth.validate_response(fake_resp, True) + self.assertTrue(self.auth.is_errored) async def test_good_response_code(self): """Check good response code from server.""" diff --git a/tests/test_blink_functions.py b/tests/test_blink_functions.py index bb3326ab..afd62a2f 100644 --- a/tests/test_blink_functions.py +++ b/tests/test_blink_functions.py @@ -220,7 +220,9 @@ async def test_download_videos_file_exists(self, mock_isfile, mock_req): ] with self.assertLogs(level="DEBUG") as dl_log: await self.blink.download_videos("/tmp", camera="foo", stop=2, delay=0) - self.assertListEqual(dl_log.output, expected_log) + assert expected_log[0] in dl_log.output + assert expected_log[1] in dl_log.output + assert expected_log[2] in dl_log.output @mock.patch("blinkpy.blinkpy.api.request_videos") async def test_parse_camera_not_in_list(self, mock_req): diff --git a/tests/test_camera_functions.py b/tests/test_camera_functions.py index c1b7df49..e01aafca 100644 --- a/tests/test_camera_functions.py +++ b/tests/test_camera_functions.py @@ -15,19 +15,20 @@ from blinkpy.camera import BlinkCamera, BlinkCameraMini, BlinkDoorbell import tests.mock_responses as mresp -CAMERA_CFG = { - "camera": [ - { - "battery_voltage": 90, - "motion_alert": True, - "wifi_strength": -30, - "temperature": 68, - } - ] +CONFIG = { + "name": "new", + "id": 1234, + "network_id": 5678, + "serial": "12345678", + "enabled": False, + "battery_state": "ok", + "temperature": 68, + "signals": {"lfr": 5, "wifi": 4, "battery": 3}, + "thumbnail": "/thumb", } -@mock.patch("blinkpy.auth.Auth.query") +@mock.patch("blinkpy.auth.Auth.query", return_value={}) class TestBlinkCameraSetup(IsolatedAsyncioTestCase): """Test the Blink class in blinkpy.""" @@ -47,18 +48,6 @@ def tearDown(self): async def test_camera_update(self, mock_resp): """Test that we can properly update camera properties.""" - config = { - "name": "new", - "id": 1234, - "network_id": 5678, - "serial": "12345678", - "enabled": False, - "battery_voltage": 90, - "battery_state": "ok", - "temperature": 68, - "wifi_strength": 4, - "thumbnail": "/thumb", - } self.camera.last_record = ["1"] self.camera.sync.last_records = { "new": [{"clip": "/test.mp4", "time": "1970-01-01T00:00:00"}] @@ -70,7 +59,7 @@ async def test_camera_update(self, mock_resp): ] self.assertIsNone(self.camera.image_from_cache) - await self.camera.update(config, expire_clips=False) + await self.camera.update(CONFIG, expire_clips=False) self.assertEqual(self.camera.name, "new") self.assertEqual(self.camera.camera_id, "1234") self.assertEqual(self.camera.network_id, "5678") @@ -107,17 +96,12 @@ async def test_no_thumbnails(self, mock_resp): mock_resp.return_value = "foobar" self.camera.last_record = ["1"] config = { - "name": "new", - "id": 1234, - "network_id": 5678, - "serial": "12345678", - "enabled": False, - "battery_voltage": 90, - "battery_state": "ok", - "temperature": 68, - "wifi_strength": 4, - "thumbnail": "", + **CONFIG, + **{ + "thumbnail": "", + }, } + self.camera.sync.homescreen = {"devices": []} self.assertEqual(self.camera.temperature_calibrated, None) with self.assertLogs() as logrecord: @@ -144,16 +128,10 @@ async def test_no_video_clips(self, mock_resp): """Tests that we still proceed with camera setup with no videos.""" mock_resp.return_value = "foobar" config = { - "name": "new", - "id": 1234, - "network_id": 5678, - "serial": "12345678", - "enabled": False, - "battery_voltage": 90, - "battery_state": "ok", - "temperature": 68, - "wifi_strength": 4, - "thumbnail": "/foobar", + **CONFIG, + **{ + "thumbnail": "/foobar", + }, } mock_resp.return_value = mresp.MockResponse({"test": 200}, 200, raw_data="") self.camera.sync.homescreen = {"devices": []} @@ -167,25 +145,13 @@ async def test_recent_video_clips(self, mock_resp): Tests that the last records in the sync module are added to the camera recent clips list. """ - config = { - "name": "new", - "id": 1234, - "network_id": 5678, - "serial": "12345678", - "enabled": False, - "battery_voltage": 90, - "battery_state": "ok", - "temperature": 68, - "wifi_strength": 4, - "thumbnail": "/thumb", - } self.camera.sync.last_records["foobar"] = [] record2 = {"clip": "/clip2", "time": "2022-12-01 00:00:10+00:00"} self.camera.sync.last_records["foobar"].append(record2) record1 = {"clip": "/clip1", "time": "2022-12-01 00:00:00+00:00"} self.camera.sync.last_records["foobar"].append(record1) self.camera.sync.motion["foobar"] = True - await self.camera.update_images(config, expire_clips=False) + await self.camera.update_images(CONFIG, expire_clips=False) record1["clip"] = self.blink.urls.base_url + "/clip1" record2["clip"] = self.blink.urls.base_url + "/clip2" self.assertEqual(self.camera.recent_clips[0], record1) @@ -193,25 +159,13 @@ async def test_recent_video_clips(self, mock_resp): async def test_recent_video_clips_missing_key(self, mock_resp): """Tests that the missing key failst.""" - config = { - "name": "new", - "id": 1234, - "network_id": 5678, - "serial": "12345678", - "enabled": False, - "battery_voltage": 90, - "battery_state": "ok", - "temperature": 68, - "wifi_strength": 4, - "thumbnail": "/thumb", - } self.camera.sync.last_records["foobar"] = [] record2 = {"clip": "/clip2"} self.camera.sync.last_records["foobar"].append(record2) self.camera.sync.motion["foobar"] = True with self.assertLogs(level="ERROR") as dl_log: - await self.camera.update_images(config, expire_clips=False) + await self.camera.update_images(CONFIG, expire_clips=False) self.assertIsNotNone(dl_log.output) @@ -408,3 +362,22 @@ async def test_save_recent_clips_exception(self, mock_clip, mock_open, mock_resp in "\t".join(dl_log.output) ) assert mock_open.call_count == 1 + + async def test_missing_keys(self, mock_resp): + """Tests missing signal keys.""" + config = { + **CONFIG, + **{ + "signals": {"junk": 1}, + "thumbnail": "", + }, + } + self.camera.sync.homescreen = {"devices": []} + mock_resp.side_effect = [ + {"temp": 71}, + mresp.MockResponse({"test": 200}, 200, raw_data="test"), + mresp.MockResponse({"foobar": 200}, 200, raw_data="foobar"), + ] + await self.camera.update(config, expire_clips=False, force=True) + self.assertEqual(self.camera.wifi_strength, None) + self.assertEqual(self.camera.battery_level, None) diff --git a/tests/test_cameras.py b/tests/test_cameras.py index 8689237f..25f68489 100644 --- a/tests/test_cameras.py +++ b/tests/test_cameras.py @@ -14,19 +14,21 @@ from blinkpy.camera import BlinkCamera, BlinkCameraMini, BlinkDoorbell import tests.mock_responses as mresp -CAMERA_CFG = { - "camera": [ - { - "battery_voltage": 90, - "motion_alert": True, - "wifi_strength": -30, - "temperature": 68, - } - ] +CONFIG = { + "name": "new", + "id": 1234, + "network_id": 5678, + "serial": "12345678", + "enabled": False, + "battery_state": "ok", + "temperature": 68, + "thumbnail": 1357924680, + "signals": {"lfr": 5, "wifi": 4, "battery": 3}, + "type": "test", } -@mock.patch("blinkpy.auth.Auth.query") +@mock.patch("blinkpy.auth.Auth.query", return_value={}) class TestBlinkCameraSetup(IsolatedAsyncioTestCase): """Test the Blink class in blinkpy.""" @@ -128,41 +130,21 @@ async def test_camera_stream(self, mock_resp): async def test_different_thumb_api(self, mock_resp): """Test that the correct url is created with new api.""" thumb_endpoint = "https://rest-test.immedia-semi.com/api/v3/media/accounts/9999/networks/5678/test/1234/thumbnail/thumbnail.jpg?ts=1357924680&ext=" - config = { - "name": "new", - "id": 1234, - "network_id": 5678, - "serial": "12345678", - "enabled": False, - "battery_voltage": 90, - "battery_state": "ok", - "temperature": 68, - "wifi_strength": 4, - "thumbnail": 1357924680, - "type": "test", - } mock_resp.side_effect = [ {"temp": 71}, mresp.MockResponse({"test": 200}, 200, raw_data="test"), ] self.camera.sync.blink.account_id = 9999 - await self.camera.update(config, expire_clips=False) + await self.camera.update(CONFIG, expire_clips=False) self.assertEqual(self.camera.thumbnail, thumb_endpoint) async def test_thumb_return_none(self, mock_resp): """Test that a 'None" thumbnail is doesn't break system.""" config = { - "name": "new", - "id": 1234, - "network_id": 5678, - "serial": "12345678", - "enabled": False, - "battery_voltage": 90, - "battery_state": "ok", - "temperature": 68, - "wifi_strength": 4, - "thumbnail": None, - "type": "test", + **CONFIG, + **{ + "thumbnail": None, + }, } mock_resp.side_effect = [ {"temp": 71}, @@ -178,17 +160,10 @@ async def test_new_thumb_url_returned(self, mock_resp): "test/1234/thumbnail/thumbnail.jpg?ts=1357924680&ext=" ) config = { - "name": "new", - "id": 1234, - "network_id": 5678, - "serial": "12345678", - "enabled": False, - "battery_voltage": 90, - "battery_state": "ok", - "temperature": 68, - "wifi_strength": 4, - "thumbnail": thumb_return, - "type": "test", + **CONFIG, + **{ + "thumbnail": thumb_return, + }, } mock_resp.side_effect = [ {"temp": 71}, diff --git a/tests/test_sync_module.py b/tests/test_sync_module.py index 9b4c5a5d..b0fd81cb 100644 --- a/tests/test_sync_module.py +++ b/tests/test_sync_module.py @@ -22,7 +22,7 @@ _LOGGER.setLevel(logging.DEBUG) -@mock.patch("blinkpy.auth.Auth.query") +@mock.patch("blinkpy.auth.Auth.query", return_value={"status": 200}) class TestBlinkSyncModule(IsolatedAsyncioTestCase): """Test BlinkSyncModule functions in blinkpy.""" diff --git a/tox.ini b/tox.ini index 0adeb49b..6dcfbb95 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = build, py38, py39, py310, py311, lint +envlist = build, py39, py310, py311, py312, lint skip_missing_interpreters = True skipsdist = True