diff --git a/pyschlage/auth.py b/pyschlage/auth.py index 1cb980d..e2bedba 100644 --- a/pyschlage/auth.py +++ b/pyschlage/auth.py @@ -76,6 +76,17 @@ def authenticate(self): """ self.auth(requests.Request()) + @property + def user_id(self) -> str: + """Returns the unique user id for the authenticated user.""" + if self._user_id is None: + self._user_id = self._get_user_id() + return self._user_id + + def _get_user_id(self) -> str: + resp = self.request("get", "users/@me") + return resp.json()["identityId"] + @translate_errors def request( self, method: str, path: str, base_url: str = BASE_URL, **kwargs diff --git a/tests/test_auth.py b/tests/test_auth.py index 90addec..432563c 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -34,3 +34,60 @@ def test_request(mock_cognito, mock_srp_auth, mock_request): headers={"X-Api-Key": _auth.API_KEY}, baz="bam", ) + + +@mock.patch("requests.request") +@mock.patch("pycognito.utils.RequestsSrpAuth") +@mock.patch("pycognito.Cognito") +def test_user_id(mock_cognito, mock_srp_auth, mock_request): + auth = _auth.Auth("__username__", "__password__") + mock_request.return_value = mock.Mock( + json=mock.Mock( + return_value={ + "consentRecords": [], + "created": "2022-12-24T20:00:00.000Z", + "email": "asdf@asdf.com", + "friendlyName": "username", + "identityId": "", + "lastUpdated": "2022-12-24T20:00:00.000Z", + } + ) + ) + assert auth.user_id == "" + mock_request.assert_called_once_with( + "get", + "https://api.allegion.yonomi.cloud/v1/users/@me", + timeout=60, + auth=mock_srp_auth.return_value, + headers={"X-Api-Key": _auth.API_KEY}, + ) + + +@mock.patch("requests.request") +@mock.patch("pycognito.utils.RequestsSrpAuth") +@mock.patch("pycognito.Cognito") +def test_user_id_is_cached(mock_cognito, mock_srp_auth, mock_request): + auth = _auth.Auth("__username__", "__password__") + mock_request.return_value = mock.Mock( + json=mock.Mock( + return_value={ + "consentRecords": [], + "created": "2022-12-24T20:00:00.000Z", + "email": "asdf@asdf.com", + "friendlyName": "username", + "identityId": "", + "lastUpdated": "2022-12-24T20:00:00.000Z", + } + ) + ) + assert auth.user_id == "" + mock_request.assert_called_once_with( + "get", + "https://api.allegion.yonomi.cloud/v1/users/@me", + timeout=60, + auth=mock_srp_auth.return_value, + headers={"X-Api-Key": _auth.API_KEY}, + ) + mock_request.reset_mock() + assert auth.user_id == "" + mock_request.assert_not_called() diff --git a/tests/test_code.py b/tests/test_code.py index 34ab276..f744d95 100644 --- a/tests/test_code.py +++ b/tests/test_code.py @@ -3,12 +3,13 @@ from unittest import mock import pyschlage +from pyschlage.auth import Auth from pyschlage.code import AccessCode, DaysOfWeek, RecurringSchedule, TemporarySchedule class TestAccessCode: def test_to_from_json(self, access_code_json): - auth = mock.Mock() + auth = mock.create_autospec(Auth, spec_set=True) device_id = "__device_uuid__" access_code_id = "__access_code_uuid__" code = AccessCode( @@ -27,7 +28,7 @@ def test_to_from_json(self, access_code_json): assert code.to_json() == want_json def test_to_from_json_recurring_schedule(self, access_code_json): - auth = mock.Mock() + auth = mock.create_autospec(Auth, spec_set=True) device_id = "__device_uuid__" access_code_id = "__access_code_uuid__" sched = RecurringSchedule(days_of_week=DaysOfWeek(mon=False)) @@ -48,7 +49,7 @@ def test_to_from_json_recurring_schedule(self, access_code_json): assert code.to_json() == json def test_to_from_json_temporary_schedule(self, access_code_json): - auth = mock.Mock() + auth = mock.create_autospec(Auth, spec_set=True) device_id = "__device_uuid__" access_code_id = "__access_code_uuid__" sched = TemporarySchedule( @@ -73,7 +74,7 @@ def test_to_from_json_temporary_schedule(self, access_code_json): assert code.to_json() == json def test_refresh(self, access_code_json): - auth = mock.Mock() + auth = mock.create_autospec(Auth, spec_set=True) code = AccessCode.from_json(auth, access_code_json, "__device_uuid__") new_json = deepcopy(access_code_json) new_json["accessCode"] = 1122 @@ -87,7 +88,7 @@ def test_refresh(self, access_code_json): assert code.code == "1122" def test_save(self, access_code_json): - auth = mock.Mock() + auth = mock.create_autospec(Auth, spec_set=True) code = AccessCode.from_json(auth, access_code_json, "__device_uuid__") code.code = 1122 old_json = code.to_json() @@ -110,7 +111,7 @@ def test_save(self, access_code_json): assert code.name == "New name" def test_delete(self, access_code_json): - auth = mock.Mock() + auth = mock.create_autospec(Auth, spec_set=True) code = AccessCode.from_json(auth, access_code_json, "__device_uuid__") auth.request.return_value = mock.Mock() code.delete() diff --git a/tests/test_lock.py b/tests/test_lock.py index c8e6ac5..b79cd20 100644 --- a/tests/test_lock.py +++ b/tests/test_lock.py @@ -3,13 +3,14 @@ from unittest import mock import pyschlage +from pyschlage.auth import Auth from pyschlage.code import AccessCode from pyschlage.lock import Lock class TestLock: def test_from_json(self, lock_json): - auth = mock.Mock() + auth = mock.create_autospec(Auth, spec_set=True) lock = Lock.from_json(auth, lock_json) assert lock._auth == auth assert lock.device_id == "__wifi_uuid__" @@ -23,14 +24,14 @@ def test_from_json(self, lock_json): assert lock.firmware_version == "10.00.00264232" def test_from_json_is_jammed(self, lock_json): - auth = mock.Mock() + auth = mock.create_autospec(Auth, spec_set=True) lock_json["attributes"]["lockState"] = 2 lock = Lock.from_json(auth, lock_json) assert not lock.is_locked assert lock.is_jammed def test_refresh(self, lock_json): - auth = mock.Mock() + auth = mock.create_autospec(Auth, spec_set=True) lock = Lock.from_json(auth, lock_json) lock_json["name"] = "" @@ -41,7 +42,7 @@ def test_refresh(self, lock_json): assert lock.name == "" def test_lock_wifi(self, wifi_lock_json): - auth = mock.Mock() + auth = mock.create_autospec(Auth, spec_set=True) initial_json = deepcopy(wifi_lock_json) initial_json["attributes"]["lockState"] = 0 lock = Lock.from_json(auth, initial_json) @@ -58,7 +59,7 @@ def test_lock_wifi(self, wifi_lock_json): assert lock.is_locked def test_unlock_wifi(self, wifi_lock_json): - auth = mock.Mock() + auth = mock.create_autospec(Auth, spec_set=True) initial_json = deepcopy(wifi_lock_json) initial_json["attributes"]["lockState"] = 1 lock = Lock.from_json(auth, initial_json) @@ -75,7 +76,7 @@ def test_unlock_wifi(self, wifi_lock_json): assert not lock.is_locked def test_lock_ble(self, ble_lock_json): - auth = mock.Mock(user_id="") + auth = mock.create_autospec(Auth, spec_set=True, user_id="") lock = Lock.from_json(auth, ble_lock_json) lock.lock() @@ -94,7 +95,7 @@ def test_lock_ble(self, ble_lock_json): assert lock.is_locked def test_unlock_ble(self, ble_lock_json): - auth = mock.Mock(user_id="") + auth = mock.create_autospec(Auth, spec_set=True, user_id="") lock = Lock.from_json(auth, ble_lock_json) lock.unlock() @@ -113,7 +114,7 @@ def test_unlock_ble(self, ble_lock_json): assert not lock.is_locked def test_access_codes(self, lock_json, access_code_json): - auth = mock.Mock() + auth = mock.create_autospec(Auth, spec_set=True) lock = Lock.from_json(auth, lock_json) auth.request.return_value = mock.Mock( @@ -127,7 +128,7 @@ def test_access_codes(self, lock_json, access_code_json): assert codes == [AccessCode.from_json(auth, access_code_json, lock.device_id)] def test_add_access_code(self, lock_json, access_code_json): - auth = mock.Mock() + auth = mock.create_autospec(Auth, spec_set=True) lock = Lock.from_json(auth, lock_json) code = AccessCode.from_json(auth, access_code_json, lock.device_id) # Users should not set these.