Skip to content

Commit b989968

Browse files
authored
Merge pull request #169 from dknowles2/codes
Shore up test coverage
2 parents cfdb196 + 601e29c commit b989968

File tree

11 files changed

+179
-57
lines changed

11 files changed

+179
-57
lines changed

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ readme = { file = ["README.md"], content-type = "text/markdown" }
3535
[tool.setuptools_scm]
3636
write_to = "pyschlage/_version.py"
3737

38+
[tool.coverage.report]
39+
omit = ["pyschlage/_version.py"]
40+
3841
[tool.isort]
3942
profile = "black"
4043
combine_as_imports = true

pyschlage/auth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def wrapper(*args, **kwargs) -> requests.Response:
4343
raise NotAuthorizedError(
4444
resp_err.get("Message", "Not authorized")
4545
) from ex
46-
raise UnknownError(str(ex)) from ex
46+
raise UnknownError(str(ex)) from ex # pragma: no cover
4747

4848
return wrapper
4949

pyschlage/code.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ class RecurringSchedule:
105105
"""Minute at which the access code is disabled."""
106106

107107
@classmethod
108-
def from_json(cls, json) -> RecurringSchedule | None:
108+
def from_json(cls, json: dict[str, Any] | None) -> RecurringSchedule | None:
109109
"""Creates a RecurringSchedule from a JSON dict.
110110
111111
:meta private:
@@ -179,7 +179,7 @@ def request_path(device_id: str, access_code_id: str | None = None) -> str:
179179
"""
180180
path = f"devices/{device_id}/storage/accesscode"
181181
if access_code_id:
182-
return f"{path}/{access_code_id}"
182+
return f"{path}/{access_code_id}" # pragma: no cover
183183
return path
184184

185185
@classmethod
@@ -250,7 +250,6 @@ def save(self):
250250
if self._notification is None:
251251
self._notification = Notification(
252252
_auth=self._auth,
253-
_device=self._device,
254253
notification_id=f"{self._auth.user_id}_{self.access_code_id}",
255254
user_id=self._auth.user_id,
256255
device_id=self.device_id,
@@ -259,7 +258,7 @@ def save(self):
259258
)
260259
self._notification.filter_value = self.name
261260
self._notification.active = self.notify_on_use
262-
self._notification.save(self._device)
261+
self._notification.save()
263262

264263
def delete(self):
265264
"""Deletes the access code.

pyschlage/lock.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ def _get_access_codes(self) -> Iterable[AccessCode]:
338338
if notification.notification_type == ON_UNLOCK_ACTION:
339339
if not notification.notification_id.startswith(self._auth.user_id):
340340
# This shouldn't happen, but ignore it just in case.
341-
continue
341+
continue # pragma: no cover
342342
access_code_id = notification.notification_id[user_id_len + 1 :]
343343
notifications[access_code_id] = notification
344344
path = AccessCode.request_path(self.device_id)
@@ -352,12 +352,12 @@ def _get_access_codes(self) -> Iterable[AccessCode]:
352352

353353
def _get_notifications(self) -> Iterable[Notification]:
354354
if not self._auth:
355-
raise NotAuthenticatedError
355+
raise NotAuthenticatedError # pragma: no cover
356356
path = Notification.request_path()
357357
params = {"deviceId": self.device_id}
358358
resp = self._auth.request("get", path, params=params)
359359
for notification_json in resp.json():
360-
notification = Notification.from_json(self._auth, self, notification_json)
360+
notification = Notification.from_json(self._auth, notification_json)
361361
notification.device_type = self.device_type
362362
yield notification
363363

@@ -379,7 +379,7 @@ def set_beeper(self, enabled: bool):
379379

380380
def set_lock_and_leave(self, enabled: bool):
381381
"""Sets the lock_and_leave setting."""
382-
self._put_attributes({"lockAndLeave": 1 if enabled else 0})
382+
self._put_attributes({"lockAndLeaveEnabled": 1 if enabled else 0})
383383

384384
def set_auto_lock_time(self, auto_lock_time: int):
385385
"""Sets the auto_lock_time setting."""

pyschlage/notification.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
from .auth import Auth
88
from .common import Mutable, fromisoformat
9-
from .device import Device
109
from .exceptions import NotAuthenticatedError
1110

1211
ON_ALARM = "onalarmstate"
@@ -31,7 +30,6 @@ class Notification(Mutable):
3130
filter_value: str | None = None
3231
created_at: datetime | None = None
3332
updated_at: datetime | None = None
34-
_device: Device | None = field(default=None, repr=False)
3533
_json: dict[str, Any] = field(default_factory=dict, repr=False)
3634

3735
@staticmethod
@@ -46,13 +44,10 @@ def request_path(notification_id: str | None = None) -> str:
4644
return path
4745

4846
@classmethod
49-
def from_json(
50-
cls, auth: Auth, device: Device, json: dict[str, Any]
51-
) -> "Notification":
47+
def from_json(cls, auth: Auth, json: dict[str, Any]) -> "Notification":
5248
return Notification(
5349
_auth=auth,
5450
_json=json,
55-
_device=device,
5651
notification_id=json["notificationId"],
5752
user_id=json["userId"],
5853
device_id=json["deviceId"],
@@ -77,17 +72,14 @@ def to_json(self) -> dict[str, Any]:
7772
json["filterValue"] = self.filter_value
7873
return json
7974

80-
def save(self, device: Device | None = None):
75+
def save(self):
8176
"""Saves the Notification."""
8277
if not self._auth:
8378
raise NotAuthenticatedError
84-
if device:
85-
self._device = device
86-
assert self._device is not None
8779
method = "put" if self.created_at else "post"
8880
path = self.request_path(self.notification_id)
8981
resp = self._auth.request(method, path, self.to_json())
90-
self._update_with(self._device, resp.json())
82+
self._update_with(resp.json())
9183

9284
def delete(self):
9385
"""Deletes the notification."""
@@ -97,6 +89,5 @@ def delete(self):
9789
self._auth.request("delete", path)
9890
self._auth = None
9991
self._json = {}
100-
self._device = None
10192
self.notification_id = None
10293
self.active = False

pyschlage/user.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def request_path(user_id: str | None = None) -> str:
2626
"""
2727
path = "users"
2828
if user_id:
29-
return f"{path}/{user_id}"
29+
return f"{path}/{user_id}" # pragma: no cover
3030
return path
3131

3232
@classmethod

tests/conftest.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from pyschlage.code import AccessCode
1010
from pyschlage.device import Device
1111
from pyschlage.lock import Lock
12+
from pyschlage.log import LockLog
1213
from pyschlage.notification import ON_UNLOCK_ACTION, Notification
1314

1415

@@ -232,14 +233,12 @@ def notification_json() -> dict[str, Any]:
232233

233234

234235
@fixture
235-
def notification(
236-
mock_auth: Auth, wifi_device: Device, notification_json
237-
) -> Notification:
238-
return Notification.from_json(mock_auth, wifi_device, notification_json)
236+
def notification(mock_auth: Auth, notification_json) -> Notification:
237+
return Notification.from_json(mock_auth, notification_json)
239238

240239

241240
@fixture
242-
def log_json():
241+
def log_json() -> dict[str, Any]:
243242
return {
244243
"createdAt": "2023-03-01T17:26:47.366Z",
245244
"deviceId": "__device_uuid__",
@@ -257,3 +256,8 @@ def log_json():
257256
"type": "DEVICE_LOG",
258257
"updatedAt": "2023-03-01T17:26:47.366Z",
259258
}
259+
260+
261+
@fixture
262+
def lock_log(log_json: dict[str, Any]) -> LockLog:
263+
return LockLog.from_json(log_json)

tests/test_code.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
from typing import Any
44
from unittest.mock import Mock, create_autospec, patch
55

6+
import pytest
7+
68
from pyschlage.code import AccessCode, DaysOfWeek, RecurringSchedule, TemporarySchedule
79
from pyschlage.device import Device
10+
from pyschlage.exceptions import NotAuthenticatedError
811
from pyschlage.notification import Notification
912

1013

@@ -29,6 +32,8 @@ def test_to_from_json(
2932
def test_to_from_json_recurring_schedule(
3033
self, mock_auth: Mock, access_code_json: dict[str, Any], wifi_device: Device
3134
):
35+
assert RecurringSchedule.from_json({}) is None
36+
assert RecurringSchedule.from_json(None) is None
3237
access_code_id = "__access_code_uuid__"
3338
sched = RecurringSchedule(days_of_week=DaysOfWeek(mon=False))
3439
json = deepcopy(access_code_json)
@@ -75,6 +80,8 @@ def test_save(
7580
mock_auth: Mock,
7681
access_code_json: dict[str, Any],
7782
):
83+
with pytest.raises(NotAuthenticatedError):
84+
AccessCode().save()
7885
mock_device = create_autospec(Device, spec_set=True, device_id="__wifi_uuid__")
7986
code = AccessCode.from_json(mock_auth, mock_device, access_code_json)
8087
code.code = "1122"
@@ -94,7 +101,7 @@ def test_save(
94101
json=Mock(return_value=new_json)
95102
)
96103
code.save()
97-
mock_notification.save.assert_called_once_with(mock_device)
104+
mock_notification.save.assert_called_once_with()
98105
mock_device.send_command.assert_called_once_with(
99106
"updateaccesscode", old_json
100107
)
@@ -103,12 +110,17 @@ def test_save(
103110
assert code.name == "New name"
104111

105112
def test_delete(self, mock_auth: Mock, access_code_json: dict[str, Any]):
113+
with pytest.raises(NotAuthenticatedError):
114+
AccessCode().delete()
106115
mock_device = create_autospec(Device, spec_set=True, device_id="__wifi_uuid__")
107116
code = AccessCode.from_json(mock_auth, mock_device, access_code_json)
117+
mock_notification = create_autospec(Notification, spec_set=True)
118+
code._notification = mock_notification
108119
mock_auth.request.return_value = Mock()
109120
json = code.to_json()
110121
code.delete()
111122
mock_device.send_command.assert_called_once_with("deleteaccesscode", json)
123+
mock_notification.delete.assert_called_once_with()
112124
assert code._auth is None
113125
assert code._json == {}
114126
assert code.access_code_id is None

tests/test_common.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
from __future__ import annotations
22

3+
from pickle import dumps, loads
34
from typing import Any
45

56
import pytest
67

78
from pyschlage import common
89

910

11+
def test_pickle_unpickle() -> None:
12+
mut = common.Mutable()
13+
mut2 = loads(dumps(mut))
14+
assert mut2._mu is not None
15+
assert mut2._mu != mut._mu
16+
assert mut2._auth == mut._auth
17+
18+
1019
@pytest.fixture
1120
def json_dict() -> dict[Any, Any]:
1221
return {

0 commit comments

Comments
 (0)