python-tado 0.18.1__tar.gz → 0.18.3__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.
- {python_tado-0.18.1/python_tado.egg-info → python_tado-0.18.3}/PKG-INFO +3 -2
- {python_tado-0.18.1 → python_tado-0.18.3}/PyTado/const.py +6 -0
- {python_tado-0.18.1 → python_tado-0.18.3}/PyTado/exceptions.py +0 -4
- {python_tado-0.18.1 → python_tado-0.18.3}/PyTado/http.py +20 -11
- {python_tado-0.18.1 → python_tado-0.18.3}/PyTado/interface/api/hops_tado.py +12 -12
- {python_tado-0.18.1 → python_tado-0.18.3}/PyTado/interface/api/my_tado.py +1 -1
- {python_tado-0.18.1 → python_tado-0.18.3}/pyproject.toml +3 -2
- {python_tado-0.18.1 → python_tado-0.18.3/python_tado.egg-info}/PKG-INFO +3 -2
- {python_tado-0.18.1 → python_tado-0.18.3}/python_tado.egg-info/SOURCES.txt +2 -1
- {python_tado-0.18.1 → python_tado-0.18.3}/python_tado.egg-info/requires.txt +1 -0
- python_tado-0.18.3/tests/test_http.py +180 -0
- {python_tado-0.18.1 → python_tado-0.18.3}/tests/test_my_tado.py +1 -1
- {python_tado-0.18.1 → python_tado-0.18.3}/tests/test_my_zone.py +1 -1
- {python_tado-0.18.1 → python_tado-0.18.3}/tests/test_tado_interface.py +19 -7
- {python_tado-0.18.1 → python_tado-0.18.3}/AUTHORS +0 -0
- {python_tado-0.18.1 → python_tado-0.18.3}/LICENSE +0 -0
- {python_tado-0.18.1 → python_tado-0.18.3}/PyTado/__init__.py +0 -0
- {python_tado-0.18.1 → python_tado-0.18.3}/PyTado/__main__.py +0 -0
- {python_tado-0.18.1 → python_tado-0.18.3}/PyTado/interface/__init__.py +0 -0
- {python_tado-0.18.1 → python_tado-0.18.3}/PyTado/interface/api/__init__.py +0 -0
- {python_tado-0.18.1 → python_tado-0.18.3}/PyTado/interface/interface.py +0 -0
- /python_tado-0.18.1/PyTado/logging.py → /python_tado-0.18.3/PyTado/logger.py +0 -0
- {python_tado-0.18.1 → python_tado-0.18.3}/PyTado/zone/__init__.py +0 -0
- {python_tado-0.18.1 → python_tado-0.18.3}/PyTado/zone/hops_zone.py +0 -0
- {python_tado-0.18.1 → python_tado-0.18.3}/PyTado/zone/my_zone.py +0 -0
- {python_tado-0.18.1 → python_tado-0.18.3}/README.md +0 -0
- {python_tado-0.18.1 → python_tado-0.18.3}/python_tado.egg-info/dependency_links.txt +0 -0
- {python_tado-0.18.1 → python_tado-0.18.3}/python_tado.egg-info/entry_points.txt +0 -0
- {python_tado-0.18.1 → python_tado-0.18.3}/python_tado.egg-info/not-zip-safe +0 -0
- {python_tado-0.18.1 → python_tado-0.18.3}/python_tado.egg-info/top_level.txt +0 -0
- {python_tado-0.18.1 → python_tado-0.18.3}/setup.cfg +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-tado
|
|
3
|
-
Version: 0.18.
|
|
3
|
+
Version: 0.18.3
|
|
4
4
|
Summary: PyTado from chrism0dwk, modfied by w.malgadey, diplix, michaelarnauts, LenhartStephan, splifter, syssi, andersonshatch, Yippy, p0thi, Coffee2CodeNL, chiefdragon, FilBr, nikilase, albertomontesg, Moritz-Schmidt, palazzem
|
|
5
|
-
Author-email: Chris Jewell <chrism0dwk@gmail.com>, "w.malgadey" <w.malgadey@gmail.com>
|
|
5
|
+
Author-email: Chris Jewell <chrism0dwk@gmail.com>, "w.malgadey" <w.malgadey@gmail.com>, FilBr <filippo.barba@protonmail.com>
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
7
7
|
Version 3, 29 June 2007
|
|
8
8
|
|
|
@@ -699,6 +699,7 @@ Requires-Dist: black>=24.3; extra == "dev"
|
|
|
699
699
|
Requires-Dist: pytype; extra == "dev"
|
|
700
700
|
Requires-Dist: pylint; extra == "dev"
|
|
701
701
|
Requires-Dist: types-requests; extra == "dev"
|
|
702
|
+
Requires-Dist: responses; extra == "dev"
|
|
702
703
|
Requires-Dist: coverage; extra == "dev"
|
|
703
704
|
Requires-Dist: pytest; extra == "dev"
|
|
704
705
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
"""Constant values for the Tado component."""
|
|
2
2
|
|
|
3
|
+
# Api credentials
|
|
4
|
+
CLIENT_ID = "tado-web-app"
|
|
5
|
+
CLIENT_SECRET = (
|
|
6
|
+
"wZaRN7rpjn3FoNyF5IFuxg9uMzYJcvOoQ8QWiIqS3hfk6gLhVlG57j5YNoZL2Rtc"
|
|
7
|
+
)
|
|
8
|
+
|
|
3
9
|
# Types
|
|
4
10
|
TYPE_AIR_CONDITIONING = "AIR_CONDITIONING"
|
|
5
11
|
TYPE_HEATING = "HEATING"
|
|
@@ -9,10 +9,6 @@ class TadoNotSupportedException(TadoException):
|
|
|
9
9
|
"""Exception to indicate a requested action is not supported by Tado."""
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
class TadoXNotSupportedException(TadoException):
|
|
13
|
-
"""Exception to indicate a requested action is not supported by the Tado X API."""
|
|
14
|
-
|
|
15
|
-
|
|
16
12
|
class TadoCredentialsException(TadoException):
|
|
17
13
|
"""Exception to indicate something with credentials"""
|
|
18
14
|
|
|
@@ -11,8 +11,9 @@ from typing import Any
|
|
|
11
11
|
|
|
12
12
|
import requests
|
|
13
13
|
|
|
14
|
-
from PyTado.
|
|
15
|
-
from PyTado.
|
|
14
|
+
from PyTado.const import CLIENT_ID, CLIENT_SECRET
|
|
15
|
+
from PyTado.exceptions import TadoException, TadoWrongCredentialsException
|
|
16
|
+
from PyTado.logger import Logger
|
|
16
17
|
|
|
17
18
|
_LOGGER = Logger(__name__)
|
|
18
19
|
|
|
@@ -141,7 +142,8 @@ class Http:
|
|
|
141
142
|
self._headers = {"Referer": "https://app.tado.com/"}
|
|
142
143
|
self._username = username
|
|
143
144
|
self._password = password
|
|
144
|
-
self._id, self.
|
|
145
|
+
self._id, self._token_refresh = self._login()
|
|
146
|
+
self._x_api = self._check_x_line_generation()
|
|
145
147
|
|
|
146
148
|
def _log_response(self, response: requests.Response, *args, **kwargs):
|
|
147
149
|
og_request_method = response.request.method
|
|
@@ -250,10 +252,8 @@ class Http:
|
|
|
250
252
|
|
|
251
253
|
url = "https://auth.tado.com/oauth/token"
|
|
252
254
|
data = {
|
|
253
|
-
"client_id":
|
|
254
|
-
"client_secret":
|
|
255
|
-
"wZaRN7rpjn3FoNyF5IFuxg9uMzYJcvOoQ8QWiIqS3hfk6gLhVlG57j5YNoZL2Rtc"
|
|
256
|
-
),
|
|
255
|
+
"client_id": CLIENT_ID,
|
|
256
|
+
"client_secret": CLIENT_SECRET,
|
|
257
257
|
"grant_type": "refresh_token",
|
|
258
258
|
"scope": "home.user",
|
|
259
259
|
"refresh_token": self._token_refresh,
|
|
@@ -274,9 +274,15 @@ class Http:
|
|
|
274
274
|
},
|
|
275
275
|
)
|
|
276
276
|
|
|
277
|
-
|
|
277
|
+
if response.status_code == 200:
|
|
278
|
+
self._set_oauth_header(response.json())
|
|
279
|
+
return
|
|
278
280
|
|
|
279
|
-
|
|
281
|
+
raise TadoException(
|
|
282
|
+
f"Unknown error while refreshing token with status code {response.status_code}"
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
def _login(self) -> tuple[int, str] | None:
|
|
280
286
|
|
|
281
287
|
headers = self._headers
|
|
282
288
|
headers["Content-Type"] = "application/json"
|
|
@@ -311,9 +317,12 @@ class Http:
|
|
|
311
317
|
if response.status_code == 200:
|
|
312
318
|
refresh_token = self._set_oauth_header(response.json())
|
|
313
319
|
id_ = self._get_id()
|
|
314
|
-
x_api_ = self._check_x_line_generation()
|
|
315
320
|
|
|
316
|
-
return id_,
|
|
321
|
+
return id_, refresh_token
|
|
322
|
+
|
|
323
|
+
raise TadoException(
|
|
324
|
+
f"Login failed for unknown reason with status code {response.status_code}"
|
|
325
|
+
)
|
|
317
326
|
|
|
318
327
|
def _get_id(self) -> int:
|
|
319
328
|
request = TadoRequest()
|
|
@@ -8,7 +8,7 @@ from typing import Any
|
|
|
8
8
|
|
|
9
9
|
from .my_tado import Tado, Timetable
|
|
10
10
|
|
|
11
|
-
from ...
|
|
11
|
+
from ...logger import Logger
|
|
12
12
|
from ...exceptions import TadoNotSupportedException
|
|
13
13
|
from ...http import (
|
|
14
14
|
Action,
|
|
@@ -56,7 +56,7 @@ class TadoX(Tado):
|
|
|
56
56
|
# set to None until explicitly set
|
|
57
57
|
self._auto_geofencing_supported = None
|
|
58
58
|
|
|
59
|
-
def
|
|
59
|
+
def _create_x_request(self) -> TadoRequest:
|
|
60
60
|
return TadoXRequest()
|
|
61
61
|
|
|
62
62
|
def get_devices(self):
|
|
@@ -64,7 +64,7 @@ class TadoX(Tado):
|
|
|
64
64
|
Gets device information.
|
|
65
65
|
"""
|
|
66
66
|
|
|
67
|
-
request = self.
|
|
67
|
+
request = self._create_x_request()
|
|
68
68
|
request.command = "roomsAndDevices"
|
|
69
69
|
|
|
70
70
|
rooms: list[dict[str, Any]] = self._http.request(request)["rooms"]
|
|
@@ -77,7 +77,7 @@ class TadoX(Tado):
|
|
|
77
77
|
Gets zones information.
|
|
78
78
|
"""
|
|
79
79
|
|
|
80
|
-
request = self.
|
|
80
|
+
request = self._create_x_request()
|
|
81
81
|
request.command = "roomsAndDevices"
|
|
82
82
|
|
|
83
83
|
return self._http.request(request)["rooms"]
|
|
@@ -94,7 +94,7 @@ class TadoX(Tado):
|
|
|
94
94
|
Gets current states of all zones.
|
|
95
95
|
"""
|
|
96
96
|
|
|
97
|
-
request = self.
|
|
97
|
+
request = self._create_x_request()
|
|
98
98
|
request.command = "rooms"
|
|
99
99
|
|
|
100
100
|
return self._http.request(request)
|
|
@@ -104,7 +104,7 @@ class TadoX(Tado):
|
|
|
104
104
|
Gets current state of Zone.
|
|
105
105
|
"""
|
|
106
106
|
|
|
107
|
-
request = self.
|
|
107
|
+
request = self._create_x_request()
|
|
108
108
|
request.command = f"rooms/{zone:d}"
|
|
109
109
|
data = self._http.request(request)
|
|
110
110
|
|
|
@@ -150,7 +150,7 @@ class TadoX(Tado):
|
|
|
150
150
|
Zone has 3 different schedules, one for each timetable (see setTimetable)
|
|
151
151
|
"""
|
|
152
152
|
|
|
153
|
-
request = self.
|
|
153
|
+
request = self._create_x_request()
|
|
154
154
|
request.command = f"rooms/{zone:d}/schedule"
|
|
155
155
|
|
|
156
156
|
return self._http.request(request)
|
|
@@ -160,7 +160,7 @@ class TadoX(Tado):
|
|
|
160
160
|
Set the schedule for a zone, day is required
|
|
161
161
|
"""
|
|
162
162
|
|
|
163
|
-
request = self.
|
|
163
|
+
request = self._create_x_request()
|
|
164
164
|
request.command = f"rooms/{zone:d}/schedule"
|
|
165
165
|
request.action = Action.SET
|
|
166
166
|
request.payload = data
|
|
@@ -173,7 +173,7 @@ class TadoX(Tado):
|
|
|
173
173
|
Delete current overlay
|
|
174
174
|
"""
|
|
175
175
|
|
|
176
|
-
request = self.
|
|
176
|
+
request = self._create_x_request()
|
|
177
177
|
request.command = f"rooms/{zone:d}/resumeSchedule"
|
|
178
178
|
request.action = Action.SET
|
|
179
179
|
|
|
@@ -213,7 +213,7 @@ class TadoX(Tado):
|
|
|
213
213
|
if duration is not None:
|
|
214
214
|
post_data["termination"]["durationInSeconds"] = duration
|
|
215
215
|
|
|
216
|
-
request = self.
|
|
216
|
+
request = self._create_x_request()
|
|
217
217
|
request.command = f"rooms/{zone:d}/manualControl"
|
|
218
218
|
request.action = Action.SET
|
|
219
219
|
request.payload = post_data
|
|
@@ -236,7 +236,7 @@ class TadoX(Tado):
|
|
|
236
236
|
|
|
237
237
|
data = self.get_state(zone)
|
|
238
238
|
|
|
239
|
-
if "activated" in data["openWindow"]:
|
|
239
|
+
if data["openWindow"] and "activated" in data["openWindow"]:
|
|
240
240
|
return {"openWindowDetected": True}
|
|
241
241
|
else:
|
|
242
242
|
return {"openWindowDetected": False}
|
|
@@ -275,7 +275,7 @@ class TadoX(Tado):
|
|
|
275
275
|
Set the Temperature offset on the device.
|
|
276
276
|
"""
|
|
277
277
|
|
|
278
|
-
request = self.
|
|
278
|
+
request = self._create_x_request()
|
|
279
279
|
request.command = f"roomsAndDevices/devices/{device_id}"
|
|
280
280
|
request.action = Action.CHANGE
|
|
281
281
|
request.payload = {"temperatureOffset": offset}
|
|
@@ -4,11 +4,12 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-tado"
|
|
7
|
-
version = "0.18.
|
|
7
|
+
version = "0.18.3"
|
|
8
8
|
description = "PyTado from chrism0dwk, modfied by w.malgadey, diplix, michaelarnauts, LenhartStephan, splifter, syssi, andersonshatch, Yippy, p0thi, Coffee2CodeNL, chiefdragon, FilBr, nikilase, albertomontesg, Moritz-Schmidt, palazzem"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "Chris Jewell", email = "chrism0dwk@gmail.com" },
|
|
11
11
|
{ name = "w.malgadey", email = "w.malgadey@gmail.com" },
|
|
12
|
+
{ name = "FilBr", email = "filippo.barba@protonmail.com" },
|
|
12
13
|
]
|
|
13
14
|
dependencies = ["requests"]
|
|
14
15
|
requires-python = ">=3.11"
|
|
@@ -30,7 +31,7 @@ classifiers = [
|
|
|
30
31
|
GitHub = "https://github.com/wmalgadey/PyTado"
|
|
31
32
|
|
|
32
33
|
[project.optional-dependencies]
|
|
33
|
-
dev = ["black>=24.3", "pytype", "pylint", "types-requests", "coverage", "pytest", "pytest-cov"]
|
|
34
|
+
dev = ["black>=24.3", "pytype", "pylint", "types-requests", "responses", "coverage", "pytest", "pytest-cov"]
|
|
34
35
|
|
|
35
36
|
[project.scripts]
|
|
36
37
|
pytado = "pytado.__main__:main"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-tado
|
|
3
|
-
Version: 0.18.
|
|
3
|
+
Version: 0.18.3
|
|
4
4
|
Summary: PyTado from chrism0dwk, modfied by w.malgadey, diplix, michaelarnauts, LenhartStephan, splifter, syssi, andersonshatch, Yippy, p0thi, Coffee2CodeNL, chiefdragon, FilBr, nikilase, albertomontesg, Moritz-Schmidt, palazzem
|
|
5
|
-
Author-email: Chris Jewell <chrism0dwk@gmail.com>, "w.malgadey" <w.malgadey@gmail.com>
|
|
5
|
+
Author-email: Chris Jewell <chrism0dwk@gmail.com>, "w.malgadey" <w.malgadey@gmail.com>, FilBr <filippo.barba@protonmail.com>
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
7
7
|
Version 3, 29 June 2007
|
|
8
8
|
|
|
@@ -699,6 +699,7 @@ Requires-Dist: black>=24.3; extra == "dev"
|
|
|
699
699
|
Requires-Dist: pytype; extra == "dev"
|
|
700
700
|
Requires-Dist: pylint; extra == "dev"
|
|
701
701
|
Requires-Dist: types-requests; extra == "dev"
|
|
702
|
+
Requires-Dist: responses; extra == "dev"
|
|
702
703
|
Requires-Dist: coverage; extra == "dev"
|
|
703
704
|
Requires-Dist: pytest; extra == "dev"
|
|
704
705
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
@@ -7,7 +7,7 @@ PyTado/__main__.py
|
|
|
7
7
|
PyTado/const.py
|
|
8
8
|
PyTado/exceptions.py
|
|
9
9
|
PyTado/http.py
|
|
10
|
-
PyTado/
|
|
10
|
+
PyTado/logger.py
|
|
11
11
|
PyTado/interface/__init__.py
|
|
12
12
|
PyTado/interface/interface.py
|
|
13
13
|
PyTado/interface/api/__init__.py
|
|
@@ -23,6 +23,7 @@ python_tado.egg-info/entry_points.txt
|
|
|
23
23
|
python_tado.egg-info/not-zip-safe
|
|
24
24
|
python_tado.egg-info/requires.txt
|
|
25
25
|
python_tado.egg-info/top_level.txt
|
|
26
|
+
tests/test_http.py
|
|
26
27
|
tests/test_my_tado.py
|
|
27
28
|
tests/test_my_zone.py
|
|
28
29
|
tests/test_tado_interface.py
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""Test the Http class."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timedelta
|
|
4
|
+
import json
|
|
5
|
+
import responses
|
|
6
|
+
import unittest
|
|
7
|
+
|
|
8
|
+
from PyTado.const import CLIENT_ID, CLIENT_SECRET
|
|
9
|
+
from PyTado.exceptions import TadoException, TadoWrongCredentialsException
|
|
10
|
+
|
|
11
|
+
from . import common
|
|
12
|
+
|
|
13
|
+
from PyTado.http import Http
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestHttp(unittest.TestCase):
|
|
17
|
+
"""Testcases for Http class."""
|
|
18
|
+
|
|
19
|
+
def setUp(self):
|
|
20
|
+
super().setUp()
|
|
21
|
+
|
|
22
|
+
# Mock the login response
|
|
23
|
+
responses.add(
|
|
24
|
+
responses.POST,
|
|
25
|
+
"https://auth.tado.com/oauth/token",
|
|
26
|
+
json={
|
|
27
|
+
"access_token": "value",
|
|
28
|
+
"expires_in": 1234,
|
|
29
|
+
"refresh_token": "another_value",
|
|
30
|
+
},
|
|
31
|
+
status=200,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
responses.add(
|
|
35
|
+
responses.GET,
|
|
36
|
+
"https://my.tado.com/api/v2/homes/1234/",
|
|
37
|
+
json={"homes": [{"id": 1234}]},
|
|
38
|
+
status=200,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Mock the get me response
|
|
42
|
+
responses.add(
|
|
43
|
+
responses.GET,
|
|
44
|
+
"https://my.tado.com/api/v2/me",
|
|
45
|
+
json={"homes": [{"id": 1234}]},
|
|
46
|
+
status=200,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
@responses.activate
|
|
50
|
+
def test_login_successful(self):
|
|
51
|
+
|
|
52
|
+
instance = Http(
|
|
53
|
+
username="test_user",
|
|
54
|
+
password="test_pass",
|
|
55
|
+
debug=True,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Verify that the login was successful
|
|
59
|
+
self.assertEqual(instance._id, 1234)
|
|
60
|
+
self.assertEqual(instance.is_x_line, False)
|
|
61
|
+
|
|
62
|
+
@responses.activate
|
|
63
|
+
def test_login_failed(self):
|
|
64
|
+
|
|
65
|
+
responses.replace(
|
|
66
|
+
responses.POST,
|
|
67
|
+
"https://auth.tado.com/oauth/token",
|
|
68
|
+
json={"error": "invalid_grant"},
|
|
69
|
+
status=400,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
with self.assertRaises(
|
|
73
|
+
expected_exception=TadoWrongCredentialsException,
|
|
74
|
+
msg="Your username or password is invalid",
|
|
75
|
+
):
|
|
76
|
+
Http(
|
|
77
|
+
username="test_user",
|
|
78
|
+
password="test_pass",
|
|
79
|
+
debug=True,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
responses.replace(
|
|
83
|
+
responses.POST,
|
|
84
|
+
"https://auth.tado.com/oauth/token",
|
|
85
|
+
json={"error": "server failed"},
|
|
86
|
+
status=503,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
with self.assertRaises(
|
|
90
|
+
expected_exception=TadoException,
|
|
91
|
+
msg="Login failed for unknown reason with status code 503",
|
|
92
|
+
):
|
|
93
|
+
Http(
|
|
94
|
+
username="test_user",
|
|
95
|
+
password="test_pass",
|
|
96
|
+
debug=True,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
@responses.activate
|
|
100
|
+
def test_line_x(self):
|
|
101
|
+
|
|
102
|
+
responses.replace(
|
|
103
|
+
responses.GET,
|
|
104
|
+
"https://my.tado.com/api/v2/homes/1234/",
|
|
105
|
+
json=json.loads(common.load_fixture("tadox/homes_response.json")),
|
|
106
|
+
status=200,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
instance = Http(
|
|
110
|
+
username="test_user",
|
|
111
|
+
password="test_pass",
|
|
112
|
+
debug=True,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Verify that the login was successful
|
|
116
|
+
self.assertEqual(instance._id, 1234)
|
|
117
|
+
self.assertEqual(instance.is_x_line, True)
|
|
118
|
+
|
|
119
|
+
@responses.activate
|
|
120
|
+
def test_refresh_token_success(self):
|
|
121
|
+
instance = Http(
|
|
122
|
+
username="test_user",
|
|
123
|
+
password="test_pass",
|
|
124
|
+
debug=True,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
expected_params = {
|
|
128
|
+
"client_id": CLIENT_ID,
|
|
129
|
+
"client_secret": CLIENT_SECRET,
|
|
130
|
+
"grant_type": "refresh_token",
|
|
131
|
+
"scope": "home.user",
|
|
132
|
+
"refresh_token": "another_value",
|
|
133
|
+
}
|
|
134
|
+
# Mock the refresh token response
|
|
135
|
+
refresh_token = responses.replace(
|
|
136
|
+
responses.POST,
|
|
137
|
+
"https://auth.tado.com/oauth/token",
|
|
138
|
+
match=[
|
|
139
|
+
responses.matchers.query_param_matcher(expected_params),
|
|
140
|
+
],
|
|
141
|
+
json={
|
|
142
|
+
"access_token": "new_value",
|
|
143
|
+
"expires_in": 1234,
|
|
144
|
+
"refresh_token": "new_refresh_value",
|
|
145
|
+
},
|
|
146
|
+
status=200,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Force token refresh
|
|
150
|
+
instance._refresh_at = datetime.now() - timedelta(seconds=1)
|
|
151
|
+
instance._refresh_token()
|
|
152
|
+
|
|
153
|
+
assert refresh_token.call_count == 1
|
|
154
|
+
|
|
155
|
+
# Verify that the token was refreshed
|
|
156
|
+
self.assertEqual(instance._headers["Authorization"], "Bearer new_value")
|
|
157
|
+
|
|
158
|
+
@responses.activate
|
|
159
|
+
def test_refresh_token_failure(self):
|
|
160
|
+
instance = Http(
|
|
161
|
+
username="test_user",
|
|
162
|
+
password="test_pass",
|
|
163
|
+
debug=True,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Mock the refresh token response with failure
|
|
167
|
+
refresh_token = responses.replace(
|
|
168
|
+
responses.POST,
|
|
169
|
+
"https://auth.tado.com/oauth/token",
|
|
170
|
+
json={"error": "invalid_grant"},
|
|
171
|
+
status=400,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Force token refresh
|
|
175
|
+
instance._refresh_at = datetime.now() - timedelta(seconds=1)
|
|
176
|
+
|
|
177
|
+
with self.assertRaises(TadoException):
|
|
178
|
+
instance._refresh_token()
|
|
179
|
+
|
|
180
|
+
assert refresh_token.call_count == 1
|
|
@@ -16,7 +16,7 @@ class TadoTestCase(unittest.TestCase):
|
|
|
16
16
|
def setUp(self) -> None:
|
|
17
17
|
super().setUp()
|
|
18
18
|
login_patch = mock.patch(
|
|
19
|
-
"PyTado.http.Http._login", return_value=(1,
|
|
19
|
+
"PyTado.http.Http._login", return_value=(1, "foo")
|
|
20
20
|
)
|
|
21
21
|
get_me_patch = mock.patch("PyTado.interface.api.Tado.get_me")
|
|
22
22
|
login_patch.start()
|
|
@@ -16,7 +16,7 @@ class TadoZoneTestCase(unittest.TestCase):
|
|
|
16
16
|
def setUp(self) -> None:
|
|
17
17
|
super().setUp()
|
|
18
18
|
login_patch = mock.patch(
|
|
19
|
-
"PyTado.http.Http._login", return_value=(1,
|
|
19
|
+
"PyTado.http.Http._login", return_value=(1, "foo")
|
|
20
20
|
)
|
|
21
21
|
get_me_patch = mock.patch("PyTado.interface.api.Tado.get_me")
|
|
22
22
|
login_patch.start()
|
|
@@ -14,10 +14,14 @@ class TestTadoInterface(unittest.TestCase):
|
|
|
14
14
|
|
|
15
15
|
def test_interface_with_tado_api(self):
|
|
16
16
|
login_patch = mock.patch(
|
|
17
|
-
"PyTado.http.Http._login", return_value=(1,
|
|
17
|
+
"PyTado.http.Http._login", return_value=(1, "foo")
|
|
18
18
|
)
|
|
19
19
|
login_mock = login_patch.start()
|
|
20
|
-
|
|
20
|
+
check_x_patch = mock.patch(
|
|
21
|
+
"PyTado.http.Http._check_x_line_generation", return_value=False
|
|
22
|
+
)
|
|
23
|
+
check_x_patch.start()
|
|
24
|
+
self.addCleanup(check_x_patch.stop)
|
|
21
25
|
|
|
22
26
|
with mock.patch("PyTado.interface.api.my_tado.Tado.get_me") as mock_it:
|
|
23
27
|
tado_interface = Tado("my@username.com", "mypassword")
|
|
@@ -38,10 +42,14 @@ class TestTadoInterface(unittest.TestCase):
|
|
|
38
42
|
|
|
39
43
|
def test_interface_with_tadox_api(self):
|
|
40
44
|
login_patch = mock.patch(
|
|
41
|
-
"PyTado.http.Http._login", return_value=(1,
|
|
45
|
+
"PyTado.http.Http._login", return_value=(1, "foo")
|
|
42
46
|
)
|
|
43
47
|
login_mock = login_patch.start()
|
|
44
|
-
|
|
48
|
+
check_x_patch = mock.patch(
|
|
49
|
+
"PyTado.http.Http._check_x_line_generation", return_value=True
|
|
50
|
+
)
|
|
51
|
+
check_x_patch.start()
|
|
52
|
+
self.addCleanup(check_x_patch.stop)
|
|
45
53
|
|
|
46
54
|
with mock.patch(
|
|
47
55
|
"PyTado.interface.api.hops_tado.TadoX.get_me"
|
|
@@ -56,10 +64,14 @@ class TestTadoInterface(unittest.TestCase):
|
|
|
56
64
|
|
|
57
65
|
def test_error_handling_on_api_calls(self):
|
|
58
66
|
login_patch = mock.patch(
|
|
59
|
-
"PyTado.http.Http._login", return_value=(1,
|
|
67
|
+
"PyTado.http.Http._login", return_value=(1, "foo")
|
|
68
|
+
)
|
|
69
|
+
login_mock = login_patch.start()
|
|
70
|
+
check_x_patch = mock.patch(
|
|
71
|
+
"PyTado.http.Http._check_x_line_generation", return_value=False
|
|
60
72
|
)
|
|
61
|
-
|
|
62
|
-
self.addCleanup(
|
|
73
|
+
check_x_patch.start()
|
|
74
|
+
self.addCleanup(check_x_patch.stop)
|
|
63
75
|
|
|
64
76
|
with mock.patch("PyTado.interface.api.my_tado.Tado.get_me") as mock_it:
|
|
65
77
|
mock_it.side_effect = Exception("API Error")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|