pyechorobotics 1.2.0__tar.gz → 2.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (19) hide show
  1. {pyechorobotics-1.2.0/src/pyechorobotics.egg-info → pyechorobotics-2.0.0}/PKG-INFO +7 -7
  2. {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/README.md +6 -6
  3. {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/pyproject.toml +1 -1
  4. {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/src/echoroboticsapi/__init__.py +0 -1
  5. {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/src/echoroboticsapi/api.py +105 -5
  6. {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/src/echoroboticsapi/models.py +6 -2
  7. {pyechorobotics-1.2.0 → pyechorobotics-2.0.0/src/pyechorobotics.egg-info}/PKG-INFO +7 -7
  8. {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/test/test_api.py +94 -17
  9. {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/test/test_smart_fetch.py +4 -2
  10. {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/LICENSE.md +0 -0
  11. {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/setup.cfg +0 -0
  12. {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/setup.py +0 -0
  13. {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/src/echoroboticsapi/smart_fetch.py +0 -0
  14. {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/src/echoroboticsapi/smart_mode.py +0 -0
  15. {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/src/pyechorobotics.egg-info/SOURCES.txt +0 -0
  16. {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/src/pyechorobotics.egg-info/dependency_links.txt +0 -0
  17. {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/src/pyechorobotics.egg-info/requires.txt +0 -0
  18. {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/src/pyechorobotics.egg-info/top_level.txt +0 -0
  19. {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/test/test_smart_mode.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyechorobotics
3
- Version: 1.2.0
3
+ Version: 2.0.0
4
4
  Summary: Access API for echorobotics robot lawn mowers
5
5
  Author-email: functionpointer <suspendfunction@gmail.com>
6
6
  License: GNU General Public License v3 or later (GPLv3+)
@@ -39,8 +39,8 @@ import logging
39
39
 
40
40
 
41
41
  async def main():
42
- async with aiohttp.ClientSession(cookies=echoroboticsapi.create_cookies(user_id="your-user-id", user_token="user-user-token")) as session:
43
- api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"])
42
+ async with aiohttp.ClientSession() as session:
43
+ api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"], email="your-email", password="your-password")
44
44
  print(await api.last_statuses())
45
45
  print(await api.set_mode("chargeAndWork"))
46
46
 
@@ -66,8 +66,8 @@ import logging
66
66
 
67
67
 
68
68
  async def main():
69
- async with aiohttp.ClientSession(cookies=echoroboticsapi.create_cookies(user_id="your-user-id", user_token="user-user-token")) as session:
70
- api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"])
69
+ async with aiohttp.ClientSession() as session:
70
+ api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"], email="your-email", password="your-password")
71
71
  smartmode = echoroboticsapi.SmartMode("your-robot-id")
72
72
  api.register_smart_mode(smartmode)
73
73
 
@@ -102,8 +102,8 @@ import logging
102
102
 
103
103
 
104
104
  async def main():
105
- async with aiohttp.ClientSession(cookies=echoroboticsapi.create_cookies(user_id="your-user-id", user_token="user-user-token")) as session:
106
- api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"])
105
+ async with aiohttp.ClientSession() as session:
106
+ api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"], email="your-email", password="your-password")
107
107
  smartmode = echoroboticsapi.SmartMode("your-robot-id")
108
108
  api.register_smart_mode(smartmode)
109
109
  smartfetch = echoroboticsapi.SmartFetch(api)
@@ -21,8 +21,8 @@ import logging
21
21
 
22
22
 
23
23
  async def main():
24
- async with aiohttp.ClientSession(cookies=echoroboticsapi.create_cookies(user_id="your-user-id", user_token="user-user-token")) as session:
25
- api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"])
24
+ async with aiohttp.ClientSession() as session:
25
+ api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"], email="your-email", password="your-password")
26
26
  print(await api.last_statuses())
27
27
  print(await api.set_mode("chargeAndWork"))
28
28
 
@@ -48,8 +48,8 @@ import logging
48
48
 
49
49
 
50
50
  async def main():
51
- async with aiohttp.ClientSession(cookies=echoroboticsapi.create_cookies(user_id="your-user-id", user_token="user-user-token")) as session:
52
- api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"])
51
+ async with aiohttp.ClientSession() as session:
52
+ api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"], email="your-email", password="your-password")
53
53
  smartmode = echoroboticsapi.SmartMode("your-robot-id")
54
54
  api.register_smart_mode(smartmode)
55
55
 
@@ -84,8 +84,8 @@ import logging
84
84
 
85
85
 
86
86
  async def main():
87
- async with aiohttp.ClientSession(cookies=echoroboticsapi.create_cookies(user_id="your-user-id", user_token="user-user-token")) as session:
88
- api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"])
87
+ async with aiohttp.ClientSession() as session:
88
+ api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"], email="your-email", password="your-password")
89
89
  smartmode = echoroboticsapi.SmartMode("your-robot-id")
90
90
  api.register_smart_mode(smartmode)
91
91
  smartfetch = echoroboticsapi.SmartFetch(api)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyechorobotics"
3
- version = "1.2.0"
3
+ version = "2.0.0"
4
4
  dependencies = [
5
5
  "aiohttp",
6
6
  "yarl",
@@ -1,5 +1,4 @@
1
1
  from .api import Api
2
2
  from .models import *
3
- from .api import create_cookies
4
3
  from .smart_mode import SmartMode
5
4
  from .smart_fetch import SmartFetch
@@ -1,16 +1,20 @@
1
1
  import datetime
2
2
 
3
3
  import asyncio
4
+ from typing import Any, TYPE_CHECKING, TypedDict
5
+ from warnings import deprecated
6
+
4
7
  import pydantic
5
8
  from aiohttp import ClientSession, ClientResponse
6
9
  from yarl import URL
10
+
7
11
  from .models import *
8
12
  import logging
9
13
  import time
14
+ from abc import ABC, abstractmethod
10
15
 
11
-
12
- def create_cookies(user_id: str, user_token: str) -> dict[str, str]:
13
- return {"UserId": user_id, "UserToken": user_token}
16
+ if TYPE_CHECKING:
17
+ from . import SmartMode
14
18
 
15
19
 
16
20
  class LastKnownMode:
@@ -22,12 +26,27 @@ class LastKnownMode:
22
26
  self.pending_since = pending_since or time.time()
23
27
 
24
28
 
29
+ class AuthInfo(TypedDict):
30
+ access_token: str
31
+ refresh_token: str
32
+ obtained_timestamp: float
33
+
34
+
25
35
  class Api:
26
36
  """Class to make authenticated requests."""
27
37
 
28
- def __init__(self, websession: ClientSession, robot_ids: RobotId | list[RobotId]):
38
+ def __init__(
39
+ self,
40
+ websession: ClientSession,
41
+ robot_ids: RobotId | list[RobotId],
42
+ email: str,
43
+ password: str,
44
+ ):
29
45
  """Initialize the auth."""
30
46
  self.websession = websession
47
+ self.email = email
48
+ self.password = password
49
+ self.auth_info: AuthInfo | None = None
31
50
  if not isinstance(robot_ids, list):
32
51
  robot_ids = [robot_ids]
33
52
  self.robot_ids = robot_ids
@@ -36,6 +55,80 @@ class Api:
36
55
  self.logger = logging.getLogger("echoroboticsapi")
37
56
  self.smart_modes: dict[RobotId, "SmartMode"] = {}
38
57
 
58
+ async def loginv2(self) -> AuthInfo:
59
+ data = {"Email": self.email, "Password": self.password}
60
+ url = URL(f"https://myrobot.echorobotics.com/api/authentication/loginv2")
61
+ result = await self.request(
62
+ method="POST",
63
+ add_auth=False,
64
+ url=url,
65
+ json=data,
66
+ )
67
+ result.raise_for_status()
68
+ json_result = await result.json()
69
+ ret: AuthInfo = {
70
+ "access_token": json_result["Token"],
71
+ "refresh_token": json_result["RefreshToken"],
72
+ "obtained_timestamp": time.time(),
73
+ }
74
+ self.logger.info(
75
+ "loginv2 %s. Token len=%s; RefreshToken len=%s",
76
+ result.status,
77
+ len(ret["access_token"]),
78
+ len(ret["refresh_token"]),
79
+ )
80
+ return ret
81
+
82
+ @property
83
+ def token_refresh_duration(self):
84
+ return 5 * 60 # 5 minutes
85
+
86
+ async def get_access_token(self) -> str:
87
+ """Returns a valid access token
88
+ Performs API call to the refresh endpoint if needed.
89
+ """
90
+ if self.auth_info is None:
91
+ self.auth_info = await self.loginv2()
92
+ if self.auth_info is None:
93
+ return ""
94
+ if (
95
+ remaining := (
96
+ self.auth_info["obtained_timestamp"] + self.token_refresh_duration
97
+ )
98
+ - time.time()
99
+ ) <= 0:
100
+ # have to refresh
101
+ url = URL(f"https://myrobot.echorobotics.com/api/authentication/refresh")
102
+ # url = URL(f"http://httpbin.org/cookies")
103
+ result = await self.request(
104
+ method="POST",
105
+ add_auth=False,
106
+ url=url,
107
+ headers={"Authorization": f"Bearer {self.auth_info['access_token']}"},
108
+ cookies={
109
+ "br_rt": self.auth_info["refresh_token"],
110
+ # "br_at": self.auth_info["access_token"],
111
+ },
112
+ json={},
113
+ )
114
+ result.raise_for_status()
115
+ json = await result.json()
116
+ new_auth_info: AuthInfo = {
117
+ "access_token": json["Token"],
118
+ "refresh_token": json["RefreshToken"],
119
+ "obtained_timestamp": time.time(),
120
+ }
121
+ self.auth_info = new_auth_info
122
+ self.logger.info(
123
+ "auth refresh %s. access_token len=%s, refresh_token len=%s",
124
+ result.status,
125
+ len(new_auth_info["access_token"]),
126
+ len(new_auth_info["refresh_token"]),
127
+ )
128
+ else:
129
+ self.logger.debug("auth refresh not needed, %.2f s remaining", remaining)
130
+ return self.auth_info["access_token"]
131
+
39
132
  def _set_mode_use_current_sleep_times(self):
40
133
  yield from [3, 2, 2, 2]
41
134
  while True:
@@ -278,10 +371,17 @@ class Api:
278
371
  await self.smart_modes[robot_id].notify_history_list_received(resp)
279
372
  return resp
280
373
 
281
- async def request(self, method: str, url: URL, **kwargs) -> ClientResponse:
374
+ async def request(
375
+ self, method: str, url: URL, add_auth: bool = True, **kwargs
376
+ ) -> ClientResponse:
282
377
  """Make a request."""
378
+ if headers := kwargs.pop("headers", {}):
379
+ headers = dict(headers)
380
+ if add_auth:
381
+ headers["authorization"] = f"Bearer {await self.get_access_token()}"
283
382
  return await self.websession.request(
284
383
  method,
285
384
  url,
385
+ headers=headers,
286
386
  **kwargs,
287
387
  )
@@ -11,12 +11,16 @@ from pydantic import (
11
11
  validator,
12
12
  RootModel,
13
13
  ConfigDict,
14
+ StringConstraints,
14
15
  )
15
- from typing import Literal, Self
16
+ from typing import Literal, Self, Annotated
16
17
  from dateutil.parser import isoparse as dateutil_isoparse
17
18
  from enum import Enum
18
19
 
19
- RobotId = constr()
20
+ RobotId = Annotated[
21
+ str,
22
+ StringConstraints(strip_whitespace=True, to_upper=True),
23
+ ]
20
24
  Mode = Literal["chargeAndWork", "chargeAndStay", "work"]
21
25
  Status = Literal[
22
26
  "Offline",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyechorobotics
3
- Version: 1.2.0
3
+ Version: 2.0.0
4
4
  Summary: Access API for echorobotics robot lawn mowers
5
5
  Author-email: functionpointer <suspendfunction@gmail.com>
6
6
  License: GNU General Public License v3 or later (GPLv3+)
@@ -39,8 +39,8 @@ import logging
39
39
 
40
40
 
41
41
  async def main():
42
- async with aiohttp.ClientSession(cookies=echoroboticsapi.create_cookies(user_id="your-user-id", user_token="user-user-token")) as session:
43
- api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"])
42
+ async with aiohttp.ClientSession() as session:
43
+ api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"], email="your-email", password="your-password")
44
44
  print(await api.last_statuses())
45
45
  print(await api.set_mode("chargeAndWork"))
46
46
 
@@ -66,8 +66,8 @@ import logging
66
66
 
67
67
 
68
68
  async def main():
69
- async with aiohttp.ClientSession(cookies=echoroboticsapi.create_cookies(user_id="your-user-id", user_token="user-user-token")) as session:
70
- api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"])
69
+ async with aiohttp.ClientSession() as session:
70
+ api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"], email="your-email", password="your-password")
71
71
  smartmode = echoroboticsapi.SmartMode("your-robot-id")
72
72
  api.register_smart_mode(smartmode)
73
73
 
@@ -102,8 +102,8 @@ import logging
102
102
 
103
103
 
104
104
  async def main():
105
- async with aiohttp.ClientSession(cookies=echoroboticsapi.create_cookies(user_id="your-user-id", user_token="user-user-token")) as session:
106
- api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"])
105
+ async with aiohttp.ClientSession() as session:
106
+ api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"], email="your-email", password="your-password")
107
107
  smartmode = echoroboticsapi.SmartMode("your-robot-id")
108
108
  api.register_smart_mode(smartmode)
109
109
  smartfetch = echoroboticsapi.SmartFetch(api)
@@ -1,3 +1,5 @@
1
+ import time
2
+
1
3
  import datetime
2
4
  import json
3
5
  import urllib.parse
@@ -26,20 +28,64 @@ async def client_session():
26
28
 
27
29
 
28
30
  @pytest.fixture
29
- def api(client_session: aiohttp.ClientSession, robot_id: echoroboticsapi.RobotId):
30
- ret = echoroboticsapi.Api(client_session, robot_id)
31
+ def api_not_logged_in(
32
+ client_session: aiohttp.ClientSession,
33
+ robot_id: echoroboticsapi.RobotId,
34
+ user_email: str,
35
+ user_password: str,
36
+ ):
37
+ ret = echoroboticsapi.Api(
38
+ client_session, robot_id, email=user_email, password=user_password
39
+ )
31
40
 
32
41
  def fast():
33
42
  while True:
34
43
  yield 0.01
35
44
 
36
45
  ret._set_mode_use_current_sleep_times = fast
37
- yield ret
46
+ return ret
47
+
48
+
49
+ @pytest.fixture
50
+ def api(api_not_logged_in: echoroboticsapi.Api):
51
+ api_not_logged_in.auth_info = {
52
+ "access_token": "abc",
53
+ "refresh_token": "abc",
54
+ "obtained_timestamp": time.time(),
55
+ }
56
+ yield api_not_logged_in
57
+
58
+
59
+ @pytest.fixture
60
+ def expected_headers():
61
+ return {"authorization": "Bearer abc"}
62
+
63
+
64
+ @pytest.mark.asyncio
65
+ async def test_loginv2(
66
+ robot_id: echoroboticsapi.RobotId,
67
+ api_not_logged_in: echoroboticsapi.Api,
68
+ mock_aioresponse,
69
+ user_email: str,
70
+ user_password: str,
71
+ ):
72
+ mock_payload = {"Token": "abc", "RefreshToken": "abc"}
73
+ expected_url = f"https://myrobot.echorobotics.com/api/authentication/loginv2"
74
+ mock_aioresponse.post(expected_url, payload=mock_payload)
75
+
76
+ api_not_logged_in.auth_info = await api_not_logged_in.loginv2()
77
+ assert api_not_logged_in.auth_info["access_token"] == "abc"
78
+ mock_aioresponse.assert_called_once_with(
79
+ expected_url,
80
+ method="POST",
81
+ headers={},
82
+ json={"Email": user_email, "Password": user_password},
83
+ )
38
84
 
39
85
 
40
86
  @pytest.mark.asyncio
41
87
  async def test_get_config(
42
- robot_id: RobotId, api: echoroboticsapi.Api, mock_aioresponse
88
+ robot_id: RobotId, api: echoroboticsapi.Api, mock_aioresponse, expected_headers
43
89
  ):
44
90
  mock_json = '{"IsError": true, "IsInProgress": false, "Message": "configurator.messages.robotOffline", "Data": null, "Descriptors": null, "ConfigId": 0, "ConfigVersionId": 0, "ConfigDateTime": "0001-01-01T00:00:00", "ConfigValidated": false}'
45
91
  expected_url = f"https://myrobot.echorobotics.com/api/RobotConfig/GetConfig/{robot_id}?reload=False"
@@ -48,12 +94,14 @@ async def test_get_config(
48
94
  resp = await api.get_config(reload=False, robot_id=robot_id)
49
95
 
50
96
  assert resp
51
- mock_aioresponse.assert_called_once_with(expected_url, method="GET")
97
+ mock_aioresponse.assert_called_once_with(
98
+ expected_url, method="GET", headers=expected_headers
99
+ )
52
100
 
53
101
 
54
102
  @pytest.mark.asyncio
55
103
  async def test_get_config_in_progress_without_config_version_id(
56
- robot_id: RobotId, api: echoroboticsapi.Api, mock_aioresponse
104
+ robot_id: RobotId, api: echoroboticsapi.Api, mock_aioresponse, expected_headers
57
105
  ):
58
106
  mock_json = '{"IsError": false, "IsInProgress": true, "Message": "configurator.apiresponse.get.willNowContactRobot", "ConfigId": 0, "ConfigDateTime": "0001-01-01T00:00:00", "ConfigValidated": false}'
59
107
  expected_url = f"https://myrobot.echorobotics.com/api/RobotConfig/GetConfig/{robot_id}?reload=True"
@@ -63,12 +111,17 @@ async def test_get_config_in_progress_without_config_version_id(
63
111
 
64
112
  assert resp.is_in_progress
65
113
  assert resp.config_version_id is None
66
- mock_aioresponse.assert_called_once_with(expected_url, method="GET")
114
+ mock_aioresponse.assert_called_once_with(
115
+ expected_url, method="GET", headers=expected_headers
116
+ )
67
117
 
68
118
 
69
119
  @pytest.mark.asyncio
70
120
  async def test_laststatuses(
71
- api: echoroboticsapi.Api, mock_aioresponse, robot_id: echoroboticsapi.RobotId
121
+ api: echoroboticsapi.Api,
122
+ mock_aioresponse,
123
+ robot_id: echoroboticsapi.RobotId,
124
+ expected_headers,
72
125
  ):
73
126
  mock_json = r'{"QueryDate":"2025-01-24T18:50:19.3556944Z","Robots":["robot_id"],"StatusesInfo":[{"Robot":"robot_id","Status":"Off","MacAddress":"ac1f0fb92010","Date":"2024-12-20T15:17:14.861Z","Delta":"5 Wochen","EstimatedBatteryLevel":99.99,"Position":{"Longitude":6.1,"Latitude":5.5,"DateTime":"2024-11-04T13:17:48.461Z"},"QueryTime":"2025-01-24T18:50:19.3556944Z","HasValues":true,"IsOnline":false}],"RobotOfflineDelayInSeconds":1500}'
74
127
  mock_json = mock_json.replace("robot_id", robot_id)
@@ -76,10 +129,11 @@ async def test_laststatuses(
76
129
  mock_aioresponse.post(expected_url, body=mock_json)
77
130
  last_statuses = await api.last_statuses()
78
131
 
79
- mock_aioresponse.assert_called_once_with(
132
+ mock_aioresponse.assert_called_with(
80
133
  expected_url,
81
134
  method="POST",
82
135
  json=[robot_id],
136
+ headers=expected_headers,
83
137
  )
84
138
  assert last_statuses
85
139
  assert last_statuses.statuses_info[0].robot == robot_id
@@ -89,7 +143,10 @@ async def test_laststatuses(
89
143
 
90
144
  @pytest.mark.asyncio
91
145
  async def test_history_list(
92
- api: echoroboticsapi.Api, mock_aioresponse, robot_id: echoroboticsapi.RobotId
146
+ api: echoroboticsapi.Api,
147
+ mock_aioresponse,
148
+ robot_id: echoroboticsapi.RobotId,
149
+ expected_headers,
93
150
  ):
94
151
  mock_json = [
95
152
  {
@@ -403,13 +460,17 @@ async def test_history_list(
403
460
  mock_aioresponse.assert_called_once_with(
404
461
  expected_url,
405
462
  method="GET",
463
+ headers=expected_headers,
406
464
  )
407
465
  assert len(history_list) > 3
408
466
 
409
467
 
410
468
  @pytest.mark.asyncio
411
469
  async def test_current(
412
- api: echoroboticsapi.Api, mock_aioresponse, robot_id: echoroboticsapi.RobotId
470
+ api: echoroboticsapi.Api,
471
+ mock_aioresponse,
472
+ robot_id: echoroboticsapi.RobotId,
473
+ expected_headers,
413
474
  ):
414
475
  mock_json = r'{"SerialNumber":"robot_id","ActionId":null,"Status":0,"Message":null}'
415
476
  mock_json = mock_json.replace("robot_id", robot_id)
@@ -424,12 +485,16 @@ async def test_current(
424
485
  mock_aioresponse.assert_called_once_with(
425
486
  expected_url,
426
487
  method="GET",
488
+ headers=expected_headers,
427
489
  )
428
490
 
429
491
 
430
492
  @pytest.mark.asyncio
431
493
  async def test_set_mode_bad_contact(
432
- api: echoroboticsapi.Api, mock_aioresponse, robot_id: echoroboticsapi.RobotId
494
+ api: echoroboticsapi.Api,
495
+ mock_aioresponse,
496
+ robot_id: echoroboticsapi.RobotId,
497
+ expected_headers,
433
498
  ):
434
499
  mock_json_old = (
435
500
  rf'{{"SerialNumber":"{robot_id}","ActionId":null,"Status":0,"Message":null}}'
@@ -448,12 +513,17 @@ async def test_set_mode_bad_contact(
448
513
  status_code = await api.set_mode("work", robot_id, use_current=True)
449
514
 
450
515
  assert status_code == -1
451
- mock_aioresponse.assert_called_with(expected_url, method="GET")
516
+ mock_aioresponse.assert_called_with(
517
+ expected_url, method="GET", headers=expected_headers
518
+ )
452
519
 
453
520
 
454
521
  @pytest.mark.asyncio
455
522
  async def test_set_mode_bad_contact_timeout(
456
- api: echoroboticsapi.Api, mock_aioresponse, robot_id: echoroboticsapi.RobotId
523
+ api: echoroboticsapi.Api,
524
+ mock_aioresponse,
525
+ robot_id: echoroboticsapi.RobotId,
526
+ expected_headers,
457
527
  ):
458
528
  mock_json_old = (
459
529
  rf'{{"SerialNumber":"{robot_id}","ActionId":null,"Status":0,"Message":null}}'
@@ -472,12 +542,17 @@ async def test_set_mode_bad_contact_timeout(
472
542
  )
473
543
 
474
544
  assert status_code == -1
475
- mock_aioresponse.assert_called_with(expected_url, method="GET")
545
+ mock_aioresponse.assert_called_with(
546
+ expected_url, method="GET", headers=expected_headers
547
+ )
476
548
 
477
549
 
478
550
  @pytest.mark.asyncio
479
551
  async def test_set_mode_working(
480
- api: echoroboticsapi.Api, mock_aioresponse, robot_id: echoroboticsapi.RobotId
552
+ api: echoroboticsapi.Api,
553
+ mock_aioresponse,
554
+ robot_id: echoroboticsapi.RobotId,
555
+ expected_headers,
481
556
  ):
482
557
  mock_json_old = (
483
558
  rf'{{"SerialNumber":"{robot_id}","ActionId":null,"Status":0,"Message":null}}'
@@ -500,4 +575,6 @@ async def test_set_mode_working(
500
575
  status_code = await api.set_mode("work", robot_id, use_current=True)
501
576
 
502
577
  assert status_code == 200
503
- mock_aioresponse.assert_called_with(expected_url, method="GET")
578
+ mock_aioresponse.assert_called_with(
579
+ expected_url, method="GET", headers=expected_headers
580
+ )
@@ -13,8 +13,10 @@ ALL_STATUSES = list(typing.get_args(echoroboticsapi.Status))
13
13
 
14
14
 
15
15
  @pytest.fixture
16
- def api(robot_id: RobotId):
17
- api = echoroboticsapi.Api(websession=None, robot_ids=[robot_id])
16
+ def api(robot_id: RobotId, user_email: str, user_password: str):
17
+ api = echoroboticsapi.Api(
18
+ websession=None, robot_ids=[robot_id], email=user_email, password=user_password
19
+ )
18
20
  yield api
19
21
 
20
22
 
File without changes
File without changes