Skip to content

Commit 4cd53de

Browse files
committed
added tests for http class
1 parent 82a954b commit 4cd53de

File tree

5 files changed

+243
-6
lines changed

5 files changed

+243
-6
lines changed

PyTado/const.py

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
"""Constant values for the Tado component."""
22

3+
# Api credentials
4+
CLIENT_ID = "tado-web-app"
5+
CLIENT_SECRET = (
6+
"wZaRN7rpjn3FoNyF5IFuxg9uMzYJcvOoQ8QWiIqS3hfk6gLhVlG57j5YNoZL2Rtc"
7+
)
8+
39
# Types
410
TYPE_AIR_CONDITIONING = "AIR_CONDITIONING"
511
TYPE_HEATING = "HEATING"

PyTado/http.py

+10-5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import requests
1313

14+
from PyTado.const import CLIENT_ID, CLIENT_SECRET
1415
from PyTado.exceptions import TadoException, TadoWrongCredentialsException
1516
from PyTado.logger import Logger
1617

@@ -251,10 +252,8 @@ def _refresh_token(self) -> None:
251252

252253
url = "https://auth.tado.com/oauth/token"
253254
data = {
254-
"client_id": "tado-web-app",
255-
"client_secret": (
256-
"wZaRN7rpjn3FoNyF5IFuxg9uMzYJcvOoQ8QWiIqS3hfk6gLhVlG57j5YNoZL2Rtc"
257-
),
255+
"client_id": CLIENT_ID,
256+
"client_secret": CLIENT_SECRET,
258257
"grant_type": "refresh_token",
259258
"scope": "home.user",
260259
"refresh_token": self._token_refresh,
@@ -275,7 +274,13 @@ def _refresh_token(self) -> None:
275274
},
276275
)
277276

278-
self._set_oauth_header(response.json())
277+
if response.status_code == 200:
278+
self._set_oauth_header(response.json())
279+
return
280+
281+
raise TadoException(
282+
f"Unknown error while refreshing token with status code {response.status_code}"
283+
)
279284

280285
def _login(self) -> tuple[int, bool, str] | None:
281286

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ classifiers = [
3030
GitHub = "https://github.com/wmalgadey/PyTado"
3131

3232
[project.optional-dependencies]
33-
dev = ["black>=24.3", "pytype", "pylint", "types-requests", "coverage", "pytest", "pytest-cov"]
33+
dev = ["black>=24.3", "pytype", "pylint", "types-requests", "responses", "coverage", "pytest", "pytest-cov"]
3434

3535
[project.scripts]
3636
pytado = "pytado.__main__:main"
+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"id": 1234,
3+
"name": "My Home",
4+
"dateTimeZone": "Europe/Berlin",
5+
"dateCreated": "2024-12-04T21:53:35.862Z",
6+
"temperatureUnit": "CELSIUS",
7+
"partner": null,
8+
"simpleSmartScheduleEnabled": true,
9+
"awayRadiusInMeters": 400.00,
10+
"installationCompleted": true,
11+
"incidentDetection": {"supported": false, "enabled": true},
12+
"generation": "LINE_X",
13+
"zonesCount": 0,
14+
"language": "de-DE",
15+
"preventFromSubscribing": true,
16+
"skills": [],
17+
"christmasModeEnabled": true,
18+
"showAutoAssistReminders": true,
19+
"contactDetails": {
20+
"name": "Alice Wonderland",
21+
"email": "[email protected]",
22+
"phone": "+00000000"
23+
},
24+
"address": {
25+
"addressLine1": "Wonderland 1",
26+
"addressLine2": null,
27+
"zipCode": "112",
28+
"city": "Wonderland",
29+
"state": null,
30+
"country": "DEU"
31+
},
32+
"geolocation": {"latitude": 25.1532934, "longitude": 2.3324432},
33+
"consentGrantSkippable": true,
34+
"enabledFeatures": [
35+
"AA_REVERSE_TRIAL_7D",
36+
"EIQ_SETTINGS_AS_WEBVIEW",
37+
"HIDE_BOILER_REPAIR_SERVICE",
38+
"OWD_SETTINGS_AS_WEBVIEW",
39+
"SETTINGS_OVERVIEW_AS_WEBVIEW"
40+
],
41+
"isAirComfortEligible": false,
42+
"isBalanceAcEligible": false,
43+
"isEnergyIqEligible": true,
44+
"isHeatSourceInstalled": false,
45+
"isHeatPumpInstalled": false
46+
}

tests/test_http.py

+180
Original file line numberDiff line numberDiff line change
@@ -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

0 commit comments

Comments
 (0)