solarlog-cli 0.2.2__tar.gz → 0.3.1__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 (41) hide show
  1. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/.coverage +0 -0
  2. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/PKG-INFO +1 -1
  3. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/pyproject.toml +1 -1
  4. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/src/solarlog_cli/solarlog_client.py +104 -27
  5. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/src/solarlog_cli/solarlog_connector.py +45 -5
  6. solarlog_cli-0.3.1/src/solarlog_cli/solarlog_exceptions.py +14 -0
  7. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/src/solarlog_cli/solarlog_models.py +0 -1
  8. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/tests/__snapshots__/test_solarlog_cli.ambr +3 -3
  9. solarlog_cli-0.3.1/tests/fixtures/device_list_access_denied.json +9 -0
  10. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/tests/test_solarlog_cli.py +96 -0
  11. solarlog_cli-0.2.2/src/solarlog_cli/solarlog_exceptions.py +0 -11
  12. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/.devcontainer/devcontainer.json +0 -0
  13. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/.github/workflows/pylint.yml +0 -0
  14. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/.github/workflows/python-publish.yml +0 -0
  15. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/.github/workflows/ruff.yml +0 -0
  16. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/.github/workflows/tests.yml +0 -0
  17. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/.github/workflows/typing.yml +0 -0
  18. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/.gitignore +0 -0
  19. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/.vscode/settings.json +0 -0
  20. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/.vscode/tasks.json +0 -0
  21. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/LICENSE +0 -0
  22. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/README.md +0 -0
  23. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/pytest.ini +0 -0
  24. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/requirements.txt +0 -0
  25. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/src/solarlog_cli/__init__.py +0 -0
  26. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/src/solarlog_cli/py.typed +0 -0
  27. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/tests/__init__.py +0 -0
  28. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/tests/conftest.py +0 -0
  29. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/tests/fixtures/basic_data.json +0 -0
  30. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/tests/fixtures/basic_data_during_update.json +0 -0
  31. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/tests/fixtures/basic_data_no_power.json +0 -0
  32. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/tests/fixtures/device_data_1.json +0 -0
  33. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/tests/fixtures/device_data_2.json +0 -0
  34. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/tests/fixtures/device_data_3.json +0 -0
  35. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/tests/fixtures/device_data_4.json +0 -0
  36. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/tests/fixtures/device_list.json +0 -0
  37. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/tests/fixtures/energy_per_inverter.json +0 -0
  38. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/tests/fixtures/extended_data.json +0 -0
  39. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/tests/fixtures/power_per_inverter.json +0 -0
  40. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/tests/ruff.toml +0 -0
  41. {solarlog_cli-0.2.2 → solarlog_cli-0.3.1}/tests/syrupy.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: solarlog_cli
3
- Version: 0.2.2
3
+ Version: 0.3.1
4
4
  Summary: Python library to access the Solar-Log
5
5
  Project-URL: Repository, https://github.com/dontinelli/solarlog_cli.git
6
6
  Project-URL: Issues, https://github.com/dontinelli/solarlog_cli/issues
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "solarlog_cli"
3
- version = "0.2.2"
3
+ version = "0.3.1"
4
4
  authors = [
5
5
  { name="dontinelli", email="73341522+dontinelli@users.noreply.github.com" },
6
6
  ]
@@ -5,29 +5,47 @@ from __future__ import annotations
5
5
  import asyncio
6
6
  from datetime import datetime
7
7
  import json
8
+ import logging
8
9
  from typing import Any
9
10
 
10
- from aiohttp import ClientSession, ClientTimeout
11
+ from aiohttp import ClientResponse, ClientSession, ClientTimeout
11
12
 
12
- from .solarlog_exceptions import SolarLogConnectionError, SolarLogUpdateError
13
- from .solarlog_models import SolarlogData
13
+ from .solarlog_exceptions import (
14
+ SolarLogAuthenticationError,
15
+ SolarLogConnectionError,
16
+ SolarLogUpdateError,
17
+ )
14
18
 
15
- SOLARLOG_REQUEST_PAYLOAD = {801: {170: None}}
19
+ from .solarlog_models import SolarlogData
16
20
 
21
+ SOLARLOG_REQUEST_PAYLOAD = '{ "801": { "170": null } }'
22
+ _LOGGER = logging.getLogger(__name__)
17
23
 
18
24
  class Client:
19
25
  """Client class to access Solar-Log."""
20
26
 
21
- def __init__(self, host: str) -> None:
27
+ # pylint: disable=too-many-positional-arguments
28
+
29
+ def __init__(self, host: str, password: str = "") -> None:
22
30
  self.host: str = host
31
+ self.password: str = password
32
+ self.token: str = ""
33
+
34
+ self.request_timeout = 10
35
+ self.session = ClientSession(
36
+ timeout=ClientTimeout(total=self.request_timeout)
37
+ )
23
38
 
24
- self.session: ClientSession = ClientSession()
25
39
  self._close_session: bool = True
26
40
 
27
- self.request_timeout = 10
28
41
 
29
42
  async def test_connection(self) -> bool:
30
- """Test the connection to Solar-Log"""
43
+ """Test the connection to Solar-Log."""
44
+ if self.session is None:
45
+ self.session = ClientSession(
46
+ timeout=ClientTimeout(total=self.request_timeout)
47
+ )
48
+ self._close_session = True
31
49
 
32
50
  url = f"{self.host}/getjp"
33
51
 
@@ -38,24 +56,55 @@ class Client:
38
56
 
39
57
  return False
40
58
 
41
- async def execute_http_request(self, json_payload) -> dict[str, Any]:
42
- """Hepler function to process the HTTP Get call."""
59
+ async def login(self) -> bool:
60
+ """Test the connection to Solar-Log."""
61
+
62
+ if self.password == "":
63
+ return False
64
+
65
+ payload: str = f"u=user&p={self.password}"
66
+
67
+ response = await self.execute_http_request(payload,"login")
68
+
69
+ text = await response.text()
70
+ if text.count("FAILED - Password was wrong"):
71
+ raise SolarLogAuthenticationError
72
+ if text.count("FAILED - User was wrong"):
73
+ #Response means, that no password is required
74
+ self.password = ""
75
+ return False
76
+
77
+ self.token = response.cookies["SolarLog"].value
78
+
79
+ _LOGGER.debug("response: %s",text)
80
+ _LOGGER.debug("cookies: %s",response.cookies)
81
+ _LOGGER.debug("Login successful, token: %s",self.token)
82
+
83
+ return True
84
+
85
+ async def execute_http_request(self, body: str, path: str = "getjp") -> ClientResponse:
86
+ """Helper function to process the HTTP Get call."""
43
87
  if self.session is None:
44
- self.session = ClientSession()
88
+ self.session = ClientSession(
89
+ timeout=ClientTimeout(total=self.request_timeout)
90
+ )
45
91
  self._close_session = True
46
92
 
47
- url = f"{self.host}/getjp"
93
+ url = f"{self.host}/{path}"
94
+
95
+ header = {"Content-Type": "text/html"}
96
+ if self.token != "":
97
+ header |= {"Cookie": f"SolarLog={self.token}"}
98
+ body = f"token={self.token}; " + body
48
99
 
49
- header = {
50
- "Content-Type": "application/json",
51
- }
100
+ _LOGGER.debug("HTTP-request header: %s",header)
101
+ _LOGGER.debug("HTTP-request body: %s", body)
52
102
 
53
103
  try:
54
104
  response = await self.session.post(
55
105
  url=url,
56
106
  headers=header,
57
- json=json_payload,
58
- timeout=ClientTimeout(total=self.request_timeout)
107
+ data=body,
59
108
  )
60
109
  except asyncio.TimeoutError as exception:
61
110
  msg = f"Timeout occurred while connecting to Solar-Log at {self.host}"
@@ -65,14 +114,27 @@ class Client:
65
114
 
66
115
  if response.status != 200:
67
116
  # pylint: disable-next=line-too-long
68
- msg = f"The server responded with error code {response.status} while fetching data from Solar-Log at {self.host}.\n{url}\n{header}\n{json_payload}"
117
+ msg = f"The server responded with error code {response.status} while fetching data from Solar-Log at {self.host}.\n{url}\n{header}\n{body}"
69
118
  text = await response.text()
70
119
  raise SolarLogUpdateError(
71
120
  msg,
72
121
  {"Content-Type": content_type, "response": text},
73
122
  )
74
123
 
124
+ _LOGGER.debug("HTTP-request successful: %s",response)
125
+ return response
126
+
127
+ async def parse_http_response(self, response: ClientResponse) -> dict[str, Any]:
128
+ """Helper function to parse the HTTP response."""
129
+
75
130
  text = await response.text()
131
+ _LOGGER.debug("Parsing http response: %s",text)
132
+
133
+ if text.count('{"QUERY IMPOSSIBLE 000"}'):
134
+ raise SolarLogUpdateError(f"Server response: {text}")
135
+
136
+ if text.count("ACCESS DENIED"):
137
+ raise SolarLogAuthenticationError(f"Server response: {text}")
76
138
 
77
139
  try:
78
140
  json_response = json.loads(text)
@@ -80,7 +142,7 @@ class Client:
80
142
  msg = f"Value error while decoding response: {err}."
81
143
  raise SolarLogUpdateError(
82
144
  msg,
83
- {"Content-Type": content_type, "Server response": text},
145
+ {"Server response": text},
84
146
  ) from err
85
147
 
86
148
  return json_response
@@ -88,7 +150,9 @@ class Client:
88
150
  async def get_basic_data(self) -> SolarlogData:
89
151
  """Get basic data from Solar-Log."""
90
152
 
91
- raw_data: dict = await self.execute_http_request({801: {170: None}})
153
+ raw_data: dict[str, Any] = await self.parse_http_response(
154
+ await self.execute_http_request(SOLARLOG_REQUEST_PAYLOAD)
155
+ )
92
156
  raw_data = raw_data["801"]["170"]
93
157
 
94
158
  data = SolarlogData(
@@ -116,7 +180,9 @@ class Client:
116
180
  async def get_power_per_inverter(self) -> dict[int, float]:
117
181
  """Get power data from Solar-Log"""
118
182
 
119
- raw_data: dict = await self.execute_http_request({782: None})
183
+ raw_data: dict = await self.parse_http_response(
184
+ await self.execute_http_request('{ "782": null }')
185
+ )
120
186
 
121
187
  data = {int(key): val for key, val in raw_data["782"].items() if val != "0"}
122
188
 
@@ -125,7 +191,9 @@ class Client:
125
191
  async def get_energy_per_inverter(self) -> dict[int, float]:
126
192
  """Get power data from Solar-Log"""
127
193
 
128
- raw_data: dict = await self.execute_http_request({854: None})
194
+ raw_data: dict = await self.parse_http_response(
195
+ await self.execute_http_request('{ "854": null }')
196
+ )
129
197
  data_list = raw_data["854"][-1][-1]
130
198
 
131
199
  data: dict[int, float] = {}
@@ -139,10 +207,13 @@ class Client:
139
207
  async def get_energy(self, data: SolarlogData) -> SolarlogData:
140
208
  """Get energy data from Solar-Log"""
141
209
 
142
- raw_data: dict = await self.execute_http_request({878: None})
210
+ raw_data: dict = await self.parse_http_response(
211
+ await self.execute_http_request('{ "878": null }')
212
+ )
143
213
 
144
- data.production_year = raw_data["878"][-1][1]
145
- data.self_consumption_year = raw_data["878"][-1][3]
214
+ if raw_data["878"] != "QUERY IMPOSSIBLE 000":
215
+ data.production_year = raw_data["878"][-1][1]
216
+ data.self_consumption_year = raw_data["878"][-1][3]
146
217
 
147
218
  return data
148
219
 
@@ -150,7 +221,9 @@ class Client:
150
221
  """Get list of all connected devices."""
151
222
 
152
223
  # get list of all inverters connected to Solar-Log
153
- raw_data: dict = await self.execute_http_request({740: None})
224
+ raw_data: dict = await self.parse_http_response(
225
+ await self.execute_http_request('{ "740": null }')
226
+ )
154
227
  raw_data = raw_data["740"]
155
228
 
156
229
  device_list: dict[int, str] = {}
@@ -158,7 +231,11 @@ class Client:
158
231
  for key, value in raw_data.items():
159
232
  if value != "Err":
160
233
  # get name of the inverter
161
- raw_data = await self.execute_http_request({141: {key: {119: None}}})
234
+ raw_data = await self.parse_http_response(
235
+ await self.execute_http_request(
236
+ f"""{{ "141": {{ "{key}": {{ "119": null }} }} }}"""
237
+ )
238
+ )
162
239
  device_list |= {int(key): raw_data["141"][key]["119"]}
163
240
 
164
241
  return device_list
@@ -5,7 +5,11 @@ import logging
5
5
  from zoneinfo import ZoneInfo
6
6
 
7
7
  from .solarlog_client import Client
8
- from .solarlog_exceptions import SolarLogUpdateError
8
+ from .solarlog_exceptions import(
9
+ SolarLogAuthenticationError,
10
+ SolarLogConnectionError,
11
+ SolarLogUpdateError,
12
+ )
9
13
  from .solarlog_models import SolarlogData, InverterData
10
14
 
11
15
  _LOGGER = logging.getLogger(__name__)
@@ -14,14 +18,18 @@ _LOGGER = logging.getLogger(__name__)
14
18
  class SolarLogConnector:
15
19
  """Connector class to access Solar-Log."""
16
20
 
21
+ # pylint: disable=too-many-arguments
22
+ # pylint: disable=too-many-positional-arguments
23
+
17
24
  def __init__(
18
25
  self,
19
26
  host: str,
20
27
  extended_data: bool = False,
21
28
  tz: str = "",
22
29
  device_enabled: dict[int, bool] | None = None,
30
+ password: str = "",
23
31
  ):
24
- self.client = Client(host)
32
+ self.client = Client(host, password)
25
33
  self.extended_data: bool = extended_data
26
34
 
27
35
  self._device_list: dict[int, InverterData] = {}
@@ -38,6 +46,33 @@ class SolarLogConnector:
38
46
 
39
47
  return await self.client.test_connection()
40
48
 
49
+ async def test_extended_data_available(self) -> bool:
50
+ """Test if extended data is reachable."""
51
+
52
+ _LOGGER.debug("Start testing extended data available")
53
+
54
+ try:
55
+ await self.client.parse_http_response(
56
+ await self.client.execute_http_request('{"740": null}')
57
+ )
58
+ except (SolarLogConnectionError, SolarLogUpdateError) as err:
59
+ _LOGGER.debug("Error: %s", err)
60
+ return False
61
+ except SolarLogAuthenticationError as err:
62
+ #User has no unprotected access to extended API, try to log in
63
+ _LOGGER.debug("Authentication error: %s", err)
64
+ self.extended_data = await self.login()
65
+ _LOGGER.debug("Login successful?: %s", self.extended_data)
66
+ else:
67
+ self.extended_data = True
68
+
69
+ return self.extended_data
70
+
71
+ async def login(self) -> bool:
72
+ """Login to Solar-Log."""
73
+
74
+ return await self.client.login()
75
+
41
76
  async def update_data(self) -> SolarlogData:
42
77
  """Get data from Solar-Log."""
43
78
 
@@ -62,15 +97,15 @@ class SolarLogConnector:
62
97
  #calculated values (for downward compatibility)
63
98
  data.alternator_loss = data.power_dc - data.power_ac
64
99
  if data.power_dc != 0:
65
- data.efficiency = round(data.power_ac / data.power_dc *100, 1)
100
+ data.efficiency = data.power_ac / data.power_dc *100
66
101
  if data.power_ac != 0:
67
- data.usage = round(data.consumption_ac / data.power_ac * 100, 1)
102
+ data.usage = data.consumption_ac / data.power_ac * 100
68
103
  data.power_available = data.power_ac - data.consumption_ac
69
104
  else:
70
105
  data.usage = 0.0
71
106
  data.power_available = 0.0
72
107
  if data.total_power != 0:
73
- data.capacity = round(data.power_dc / data.total_power * 100, 1)
108
+ data.capacity = data.power_dc / data.total_power * 100
74
109
 
75
110
  return data
76
111
 
@@ -117,6 +152,11 @@ class SolarLogConnector:
117
152
  """List of all devices of Solar-Log."""
118
153
  return self._device_list
119
154
 
155
+ @property
156
+ def password(self) -> str:
157
+ """Password for Solar-Log."""
158
+ return self.client.password
159
+
120
160
  def device(self, device_id: int) -> InverterData:
121
161
  """Get device data."""
122
162
  return self._device_list.get(device_id, InverterData())
@@ -0,0 +1,14 @@
1
+ """Exceptions for Python client for Solar-Log."""
2
+
3
+
4
+ class SolarLogError(Exception):
5
+ """Generic SolarLog exception."""
6
+
7
+ class SolarLogConnectionError(SolarLogError):
8
+ """SolarLog connection exception."""
9
+
10
+ class SolarLogAuthenticationError(SolarLogError):
11
+ """Exception in login data."""
12
+
13
+ class SolarLogUpdateError(SolarLogError):
14
+ """Exception in updating data."""
@@ -4,7 +4,6 @@ from datetime import datetime
4
4
 
5
5
  from mashumaro import DataClassDictMixin
6
6
 
7
-
8
7
  @dataclass
9
8
  class InverterData():
10
9
  """Inverter Data model."""
@@ -2,14 +2,14 @@
2
2
  # name: test_update_data[asyncio]
3
3
  dict({
4
4
  'alternator_loss': 100,
5
- 'capacity': 27.9,
5
+ 'capacity': 27.901119402985074,
6
6
  'consumption_ac': 3110,
7
7
  'consumption_day': 47819,
8
8
  'consumption_month': 697471,
9
9
  'consumption_total': 28385247,
10
10
  'consumption_year': 4218251,
11
11
  'consumption_yesterday': 94350,
12
- 'efficiency': 96.7,
12
+ 'efficiency': 96.65663657639585,
13
13
  'inverter_data': dict({
14
14
  0: dict({
15
15
  'consumption_year': 4227027.0,
@@ -43,7 +43,7 @@
43
43
  'production_year': 8852462,
44
44
  'self_consumption_year': 2071,
45
45
  'total_power': 10720,
46
- 'usage': 107.6,
46
+ 'usage': 107.5752334832238,
47
47
  'voltage_ac': 0,
48
48
  'voltage_dc': 492,
49
49
  'yield_day': 17490,
@@ -0,0 +1,9 @@
1
+ {
2
+ "740": {
3
+ "0":"ACCESS DENIED",
4
+ "1":"ACCESS DENIED",
5
+ "2":"ACCESS DENIED",
6
+ "3":"ACCESS DENIED",
7
+ "4":"ACCESS DENIED"
8
+ }
9
+ }
@@ -7,6 +7,7 @@ from syrupy.assertion import SnapshotAssertion
7
7
 
8
8
  from solarlog_cli.solarlog_connector import SolarLogConnector
9
9
  from solarlog_cli.solarlog_exceptions import (
10
+ SolarLogAuthenticationError,
10
11
  SolarLogConnectionError,
11
12
  SolarLogError,
12
13
  SolarLogUpdateError,
@@ -35,6 +36,8 @@ async def test_connection(
35
36
 
36
37
  solarlog_connector = SolarLogConnector("localhost")
37
38
 
39
+ await solarlog_connector.client.close()
40
+ solarlog_connector.client.session = None # type: ignore [assignment]
38
41
  assert await solarlog_connector.test_connection() == return_value
39
42
 
40
43
  assert solarlog_connector.host == "localhost"
@@ -45,6 +48,92 @@ async def test_connection(
45
48
  assert solarlog_connector.client.session.closed
46
49
 
47
50
 
51
+ async def test_extended_data_available(
52
+ responses: aioresponses,
53
+ ) -> None:
54
+ """Test extended data available."""
55
+
56
+ solarlog_connector = SolarLogConnector("http://solarlog.com", password="pwd")
57
+
58
+ responses.post(
59
+ "http://solarlog.com/getjp",
60
+ timeout=True,
61
+ )
62
+ assert not await solarlog_connector.test_extended_data_available()
63
+
64
+ responses.post(
65
+ "http://solarlog.com/getjp",
66
+ status=400,
67
+ )
68
+ assert not await solarlog_connector.test_extended_data_available()
69
+
70
+ responses.post(
71
+ "http://solarlog.com/getjp",
72
+ body=load_fixture("device_list_access_denied.json"),
73
+ )
74
+ responses.post(
75
+ "http://solarlog.com/login",
76
+ body="FAILED - User was wrong",
77
+ )
78
+ assert not await solarlog_connector.test_extended_data_available()
79
+ assert solarlog_connector.password == ""
80
+
81
+ responses.post(
82
+ "http://solarlog.com/getjp",
83
+ body=load_fixture("device_list.json"),
84
+ )
85
+ assert await solarlog_connector.test_extended_data_available()
86
+
87
+ await solarlog_connector.client.close()
88
+ assert solarlog_connector.client.session.closed
89
+
90
+ async def test_login_and_data_retreival(responses: aioresponses) -> None:
91
+ """Test login into Solar-Log."""
92
+ responses.post(
93
+ "http://solarlog.com/login",
94
+ headers={"Set-Cookie": "SolarLog=token"},
95
+ body="SUCCESS - Password was correct, you are now logged in",
96
+ )
97
+ solarlog_connector = SolarLogConnector("http://solarlog.com", password="pwd")
98
+
99
+ assert await solarlog_connector.login()
100
+ assert solarlog_connector.client.token == "token"
101
+
102
+ responses.post(
103
+ "http://solarlog.com/getjp",
104
+ body=load_fixture("basic_data.json"),
105
+ )
106
+ await solarlog_connector.update_data()
107
+
108
+ await solarlog_connector.client.close()
109
+ assert solarlog_connector.client.session.closed
110
+
111
+ async def test_login_exceptions(responses: aioresponses) -> None:
112
+ """Test exceptions at login into Solar-Log."""
113
+ solarlog_connector = SolarLogConnector("http://solarlog.com", password="pwd")
114
+
115
+ responses.post(
116
+ "http://solarlog.com/login",
117
+ body="FAILED - Password was wrong",
118
+ )
119
+ with pytest.raises(SolarLogAuthenticationError): # type: ignore [call-overload]
120
+ await solarlog_connector.client.login()
121
+
122
+ responses.post(
123
+ "http://solarlog.com/login",
124
+ body="FAILED - User was wrong",
125
+ )
126
+ assert not await solarlog_connector.client.login()
127
+
128
+ responses.post(
129
+ "http://solarlog.com/getjp",
130
+ body=load_fixture("device_list.json"),
131
+ )
132
+ assert not await solarlog_connector.client.login()
133
+
134
+ await solarlog_connector.client.close()
135
+ assert solarlog_connector.client.session.closed
136
+
48
137
  async def test_update_data(
49
138
  responses: aioresponses,
50
139
  snapshot: SnapshotAssertion
@@ -126,6 +215,10 @@ async def test_update_data_with_data_exceptions(
126
215
  "http://solarlog.com/getjp",
127
216
  body="",
128
217
  )
218
+ responses.post(
219
+ "http://solarlog.com/getjp",
220
+ body='{"QUERY IMPOSSIBLE 000"}',
221
+ )
129
222
  responses.post(
130
223
  "http://solarlog.com/getjp",
131
224
  body=load_fixture("basic_data_during_update.json"),
@@ -143,6 +236,9 @@ async def test_update_data_with_data_exceptions(
143
236
  with pytest.raises(SolarLogUpdateError):
144
237
  await solarlog_connector.update_data()
145
238
 
239
+ with pytest.raises(SolarLogUpdateError):
240
+ await solarlog_connector.update_data()
241
+
146
242
  data = await solarlog_connector.update_data()
147
243
 
148
244
  assert data.usage == 0
@@ -1,11 +0,0 @@
1
- """Exceptions for Python client for Solar-Log."""
2
-
3
-
4
- class SolarLogError(Exception):
5
- """Generic exception."""
6
-
7
- class SolarLogConnectionError(SolarLogError):
8
- """Analytics connection exception."""
9
-
10
- class SolarLogUpdateError(SolarLogError):
11
- """Analytics connection exception."""
File without changes
File without changes
File without changes
File without changes