pgw-api 1.2.1__tar.gz → 1.4.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.
- {pgw_api-1.2.1 → pgw_api-1.4.0}/PKG-INFO +3 -1
- {pgw_api-1.2.1 → pgw_api-1.4.0}/README.md +2 -0
- {pgw_api-1.2.1 → pgw_api-1.4.0}/pgw_api/client.py +40 -99
- {pgw_api-1.2.1 → pgw_api-1.4.0}/pgw_api/models.py +0 -1
- {pgw_api-1.2.1 → pgw_api-1.4.0}/pgw_api.egg-info/PKG-INFO +3 -1
- {pgw_api-1.2.1 → pgw_api-1.4.0}/pyproject.toml +12 -1
- {pgw_api-1.2.1 → pgw_api-1.4.0}/tests/test_billing.py +61 -32
- pgw_api-1.4.0/tests/test_client.py +459 -0
- pgw_api-1.2.1/tests/test_client.py +0 -388
- {pgw_api-1.2.1 → pgw_api-1.4.0}/LICENSE +0 -0
- {pgw_api-1.2.1 → pgw_api-1.4.0}/pgw_api/__init__.py +0 -0
- {pgw_api-1.2.1 → pgw_api-1.4.0}/pgw_api/exceptions.py +0 -0
- {pgw_api-1.2.1 → pgw_api-1.4.0}/pgw_api.egg-info/SOURCES.txt +0 -0
- {pgw_api-1.2.1 → pgw_api-1.4.0}/pgw_api.egg-info/dependency_links.txt +0 -0
- {pgw_api-1.2.1 → pgw_api-1.4.0}/pgw_api.egg-info/requires.txt +0 -0
- {pgw_api-1.2.1 → pgw_api-1.4.0}/pgw_api.egg-info/top_level.txt +0 -0
- {pgw_api-1.2.1 → pgw_api-1.4.0}/setup.cfg +0 -0
- {pgw_api-1.2.1 → pgw_api-1.4.0}/tests/test_models.py +0 -0
- {pgw_api-1.2.1 → pgw_api-1.4.0}/tests/test_parsing.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pgw-api
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Python API client for Philadelphia Gas Works (PGW)
|
|
5
5
|
Author: zackwag
|
|
6
6
|
License: MIT
|
|
@@ -29,6 +29,7 @@ import asyncio
|
|
|
29
29
|
import aiohttp
|
|
30
30
|
from pgw_api import PGWApiClient
|
|
31
31
|
|
|
32
|
+
|
|
32
33
|
async def main():
|
|
33
34
|
client = PGWApiClient("your-email@example.com", "your-password")
|
|
34
35
|
|
|
@@ -40,6 +41,7 @@ async def main():
|
|
|
40
41
|
if entry.period_start and entry.period_end:
|
|
41
42
|
print(f" Period: {entry.period_start} to {entry.period_end}")
|
|
42
43
|
|
|
44
|
+
|
|
43
45
|
asyncio.run(main())
|
|
44
46
|
```
|
|
45
47
|
|
|
@@ -15,6 +15,7 @@ import asyncio
|
|
|
15
15
|
import aiohttp
|
|
16
16
|
from pgw_api import PGWApiClient
|
|
17
17
|
|
|
18
|
+
|
|
18
19
|
async def main():
|
|
19
20
|
client = PGWApiClient("your-email@example.com", "your-password")
|
|
20
21
|
|
|
@@ -26,6 +27,7 @@ async def main():
|
|
|
26
27
|
if entry.period_start and entry.period_end:
|
|
27
28
|
print(f" Period: {entry.period_start} to {entry.period_end}")
|
|
28
29
|
|
|
30
|
+
|
|
29
31
|
asyncio.run(main())
|
|
30
32
|
```
|
|
31
33
|
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
import re
|
|
7
|
-
from datetime import date
|
|
7
|
+
from datetime import date, datetime
|
|
8
8
|
|
|
9
9
|
import aiohttp
|
|
10
10
|
|
|
@@ -33,18 +33,14 @@ class PGWApiClient:
|
|
|
33
33
|
self._username = username
|
|
34
34
|
self._password = password
|
|
35
35
|
|
|
36
|
-
async def async_get_usage(
|
|
37
|
-
self, session: aiohttp.ClientSession
|
|
38
|
-
) -> list[GasUsage]:
|
|
36
|
+
async def async_get_usage(self, session: aiohttp.ClientSession) -> list[GasUsage]:
|
|
39
37
|
"""Authenticate and fetch gas usage data from PGW."""
|
|
40
38
|
await self._establish_session(session)
|
|
41
39
|
await self._authenticate(session)
|
|
42
40
|
csrf_token = await self._get_csrf_token(session)
|
|
43
41
|
return await self._load_gas_usage(session, csrf_token)
|
|
44
42
|
|
|
45
|
-
async def async_get_billing(
|
|
46
|
-
self, session: aiohttp.ClientSession
|
|
47
|
-
) -> BillingSummary:
|
|
43
|
+
async def async_get_billing(self, session: aiohttp.ClientSession) -> BillingSummary:
|
|
48
44
|
"""Authenticate and fetch billing summary from PGW."""
|
|
49
45
|
await self._establish_session(session)
|
|
50
46
|
await self._authenticate(session)
|
|
@@ -84,9 +80,7 @@ class PGWApiClient:
|
|
|
84
80
|
usage = await self._load_gas_usage(session, csrf_token)
|
|
85
81
|
return usage, billing
|
|
86
82
|
|
|
87
|
-
async def async_validate_credentials(
|
|
88
|
-
self, session: aiohttp.ClientSession
|
|
89
|
-
) -> bool:
|
|
83
|
+
async def async_validate_credentials(self, session: aiohttp.ClientSession) -> bool:
|
|
90
84
|
"""Validate credentials without fetching usage data."""
|
|
91
85
|
await self._establish_session(session)
|
|
92
86
|
await self._authenticate(session)
|
|
@@ -97,9 +91,7 @@ class PGWApiClient:
|
|
|
97
91
|
try:
|
|
98
92
|
async with session.get(LOGIN_URL, allow_redirects=True) as resp:
|
|
99
93
|
if resp.status != 200:
|
|
100
|
-
raise PGWConnectionError(
|
|
101
|
-
f"Login page returned status {resp.status}"
|
|
102
|
-
)
|
|
94
|
+
raise PGWConnectionError(f"Login page returned status {resp.status}")
|
|
103
95
|
await resp.text()
|
|
104
96
|
except aiohttp.ClientError as err:
|
|
105
97
|
raise PGWConnectionError(f"Failed to connect to PGW: {err}") from err
|
|
@@ -114,26 +106,18 @@ class PGWApiClient:
|
|
|
114
106
|
headers = {**_HEADERS, "Referer": LOGIN_URL}
|
|
115
107
|
|
|
116
108
|
try:
|
|
117
|
-
async with session.post(
|
|
118
|
-
VALIDATE_LOGIN_URL, json=payload, headers=headers
|
|
119
|
-
) as resp:
|
|
109
|
+
async with session.post(VALIDATE_LOGIN_URL, json=payload, headers=headers) as resp:
|
|
120
110
|
if resp.status != 200:
|
|
121
|
-
raise PGWAuthError(
|
|
122
|
-
f"Login endpoint returned status {resp.status}"
|
|
123
|
-
)
|
|
111
|
+
raise PGWAuthError(f"Login endpoint returned status {resp.status}")
|
|
124
112
|
body = await resp.text()
|
|
125
113
|
except aiohttp.ClientError as err:
|
|
126
|
-
raise PGWConnectionError(
|
|
127
|
-
f"Failed during authentication: {err}"
|
|
128
|
-
) from err
|
|
114
|
+
raise PGWConnectionError(f"Failed during authentication: {err}") from err
|
|
129
115
|
|
|
130
116
|
try:
|
|
131
117
|
data = json.loads(body)
|
|
132
118
|
inner = json.loads(data["d"])
|
|
133
119
|
except (json.JSONDecodeError, KeyError) as err:
|
|
134
|
-
raise PGWConnectionError(
|
|
135
|
-
"Unexpected response from login endpoint"
|
|
136
|
-
) from err
|
|
120
|
+
raise PGWConnectionError("Unexpected response from login endpoint") from err
|
|
137
121
|
|
|
138
122
|
if isinstance(inner, dict) and "dtException" in inner:
|
|
139
123
|
msg = inner["dtException"][0].get("MessageInformation", "Unknown error")
|
|
@@ -144,9 +128,7 @@ class PGWApiClient:
|
|
|
144
128
|
|
|
145
129
|
first = inner[0]
|
|
146
130
|
if isinstance(first, dict) and first.get("STATUS") == 0:
|
|
147
|
-
raise PGWAuthError(
|
|
148
|
-
first.get("Message", "Invalid username or password")
|
|
149
|
-
)
|
|
131
|
+
raise PGWAuthError(first.get("Message", "Invalid username or password"))
|
|
150
132
|
|
|
151
133
|
async def _get_csrf_token(self, session: aiohttp.ClientSession) -> str:
|
|
152
134
|
"""Navigate to the usage page and extract the CSRF token."""
|
|
@@ -154,18 +136,12 @@ class PGWApiClient:
|
|
|
154
136
|
async with session.get(DASHBOARD_URL, allow_redirects=True) as resp:
|
|
155
137
|
await resp.text()
|
|
156
138
|
|
|
157
|
-
async with session.get(
|
|
158
|
-
USAGE_URL, params={"type": "GU"}, allow_redirects=True
|
|
159
|
-
) as resp:
|
|
139
|
+
async with session.get(USAGE_URL, params={"type": "GU"}, allow_redirects=True) as resp:
|
|
160
140
|
if resp.status != 200:
|
|
161
|
-
raise PGWConnectionError(
|
|
162
|
-
f"Usage page returned status {resp.status}"
|
|
163
|
-
)
|
|
141
|
+
raise PGWConnectionError(f"Usage page returned status {resp.status}")
|
|
164
142
|
html = await resp.text()
|
|
165
143
|
except aiohttp.ClientError as err:
|
|
166
|
-
raise PGWConnectionError(
|
|
167
|
-
f"Failed to fetch usage page: {err}"
|
|
168
|
-
) from err
|
|
144
|
+
raise PGWConnectionError(f"Failed to fetch usage page: {err}") from err
|
|
169
145
|
|
|
170
146
|
match = re.search(r'id="hdnCSRFToken"[^>]*value="([^"]+)"', html)
|
|
171
147
|
if not match:
|
|
@@ -173,31 +149,21 @@ class PGWApiClient:
|
|
|
173
149
|
|
|
174
150
|
return match.group(1)
|
|
175
151
|
|
|
176
|
-
async def _load_billing(
|
|
177
|
-
self, session: aiohttp.ClientSession
|
|
178
|
-
) -> BillingSummary:
|
|
152
|
+
async def _load_billing(self, session: aiohttp.ClientSession) -> BillingSummary:
|
|
179
153
|
"""Fetch billing summary from the BillDashboard page."""
|
|
180
154
|
try:
|
|
181
155
|
async with session.get(DASHBOARD_URL, allow_redirects=True) as resp:
|
|
182
156
|
await resp.text()
|
|
183
157
|
|
|
184
|
-
async with session.get(
|
|
185
|
-
BILL_DASHBOARD_URL, allow_redirects=True
|
|
186
|
-
) as resp:
|
|
158
|
+
async with session.get(BILL_DASHBOARD_URL, allow_redirects=True) as resp:
|
|
187
159
|
if resp.status != 200:
|
|
188
|
-
raise PGWConnectionError(
|
|
189
|
-
f"BillDashboard returned status {resp.status}"
|
|
190
|
-
)
|
|
160
|
+
raise PGWConnectionError(f"BillDashboard returned status {resp.status}")
|
|
191
161
|
html = await resp.text()
|
|
192
162
|
except aiohttp.ClientError as err:
|
|
193
|
-
raise PGWConnectionError(
|
|
194
|
-
f"Failed to fetch billing data: {err}"
|
|
195
|
-
) from err
|
|
163
|
+
raise PGWConnectionError(f"Failed to fetch billing data: {err}") from err
|
|
196
164
|
|
|
197
165
|
def field(name: str, default: str = "0") -> str:
|
|
198
|
-
match = re.search(
|
|
199
|
-
rf'id="{name}"[^>]*value="([^"]*)"', html
|
|
200
|
-
)
|
|
166
|
+
match = re.search(rf'id="{name}"[^>]*value="([^"]*)"', html)
|
|
201
167
|
return match.group(1) if match else default
|
|
202
168
|
|
|
203
169
|
current_bill = _parse_dollar(field("hdnTotalBillOFCurrentMonth"))
|
|
@@ -206,12 +172,8 @@ class PGWApiClient:
|
|
|
206
172
|
previous_bill = _parse_dollar(field("hdnTotalBillOFPreviousMonth"))
|
|
207
173
|
previous_usage = _parse_float(field("hdnGasUsageOFPreviousMonth"))
|
|
208
174
|
previous_days = _parse_int(field("hdnnumOfDaysPreviousMonth"))
|
|
209
|
-
prev_year_bill = _parse_dollar(
|
|
210
|
-
|
|
211
|
-
)
|
|
212
|
-
prev_year_usage = _parse_float(
|
|
213
|
-
field("hdnGasUsageOFPreviousYearPreviousMonth")
|
|
214
|
-
)
|
|
175
|
+
prev_year_bill = _parse_dollar(field("hdnTotalBillOFPreviousYearPreviousMonth"))
|
|
176
|
+
prev_year_usage = _parse_float(field("hdnGasUsageOFPreviousYearPreviousMonth"))
|
|
215
177
|
balance = _parse_dollar(field("hdnPrevAmount"))
|
|
216
178
|
|
|
217
179
|
# Parse period from billing comparison JSON
|
|
@@ -271,26 +233,18 @@ class PGWApiClient:
|
|
|
271
233
|
}
|
|
272
234
|
|
|
273
235
|
try:
|
|
274
|
-
async with session.post(
|
|
275
|
-
LOAD_GAS_URL, json=payload, headers=headers
|
|
276
|
-
) as resp:
|
|
236
|
+
async with session.post(LOAD_GAS_URL, json=payload, headers=headers) as resp:
|
|
277
237
|
if resp.status != 200:
|
|
278
|
-
raise PGWConnectionError(
|
|
279
|
-
f"LoadGasUsage returned status {resp.status}"
|
|
280
|
-
)
|
|
238
|
+
raise PGWConnectionError(f"LoadGasUsage returned status {resp.status}")
|
|
281
239
|
body = await resp.text()
|
|
282
240
|
except aiohttp.ClientError as err:
|
|
283
|
-
raise PGWConnectionError(
|
|
284
|
-
f"Failed to fetch gas usage: {err}"
|
|
285
|
-
) from err
|
|
241
|
+
raise PGWConnectionError(f"Failed to fetch gas usage: {err}") from err
|
|
286
242
|
|
|
287
243
|
try:
|
|
288
244
|
data = json.loads(body)
|
|
289
245
|
inner = json.loads(data["d"])
|
|
290
246
|
except (json.JSONDecodeError, KeyError) as err:
|
|
291
|
-
raise PGWConnectionError(
|
|
292
|
-
"Unexpected response from LoadGasUsage"
|
|
293
|
-
) from err
|
|
247
|
+
raise PGWConnectionError("Unexpected response from LoadGasUsage") from err
|
|
294
248
|
|
|
295
249
|
if isinstance(inner, dict) and "dtException" in inner:
|
|
296
250
|
msg = inner["dtException"][0].get("MessageInformation", "Unknown error")
|
|
@@ -356,26 +310,18 @@ class PGWApiClient:
|
|
|
356
310
|
}
|
|
357
311
|
|
|
358
312
|
try:
|
|
359
|
-
async with session.post(
|
|
360
|
-
LOAD_GAS_URL, json=payload, headers=headers
|
|
361
|
-
) as resp:
|
|
313
|
+
async with session.post(LOAD_GAS_URL, json=payload, headers=headers) as resp:
|
|
362
314
|
if resp.status != 200:
|
|
363
|
-
raise PGWConnectionError(
|
|
364
|
-
f"LoadGasUsage (daily) returned status {resp.status}"
|
|
365
|
-
)
|
|
315
|
+
raise PGWConnectionError(f"LoadGasUsage (daily) returned status {resp.status}")
|
|
366
316
|
body = await resp.text()
|
|
367
317
|
except aiohttp.ClientError as err:
|
|
368
|
-
raise PGWConnectionError(
|
|
369
|
-
f"Failed to fetch daily gas usage: {err}"
|
|
370
|
-
) from err
|
|
318
|
+
raise PGWConnectionError(f"Failed to fetch daily gas usage: {err}") from err
|
|
371
319
|
|
|
372
320
|
try:
|
|
373
321
|
data = json.loads(body)
|
|
374
322
|
inner = json.loads(data["d"])
|
|
375
323
|
except (json.JSONDecodeError, KeyError) as err:
|
|
376
|
-
raise PGWConnectionError(
|
|
377
|
-
"Unexpected response from LoadGasUsage (daily)"
|
|
378
|
-
) from err
|
|
324
|
+
raise PGWConnectionError("Unexpected response from LoadGasUsage (daily)") from err
|
|
379
325
|
|
|
380
326
|
if isinstance(inner, dict) and "dtException" in inner:
|
|
381
327
|
msg = inner["dtException"][0].get("MessageInformation", "Unknown error")
|
|
@@ -406,7 +352,6 @@ class PGWApiClient:
|
|
|
406
352
|
usage_date: date,
|
|
407
353
|
) -> list[HourlyGasUsage]:
|
|
408
354
|
"""Call LoadGasUsage in daily mode with hourlyType to get hourly data."""
|
|
409
|
-
from datetime import datetime
|
|
410
355
|
|
|
411
356
|
payload = {
|
|
412
357
|
"Type": "C",
|
|
@@ -429,26 +374,18 @@ class PGWApiClient:
|
|
|
429
374
|
}
|
|
430
375
|
|
|
431
376
|
try:
|
|
432
|
-
async with session.post(
|
|
433
|
-
LOAD_GAS_URL, json=payload, headers=headers
|
|
434
|
-
) as resp:
|
|
377
|
+
async with session.post(LOAD_GAS_URL, json=payload, headers=headers) as resp:
|
|
435
378
|
if resp.status != 200:
|
|
436
|
-
raise PGWConnectionError(
|
|
437
|
-
f"LoadGasUsage (hourly) returned status {resp.status}"
|
|
438
|
-
)
|
|
379
|
+
raise PGWConnectionError(f"LoadGasUsage (hourly) returned status {resp.status}")
|
|
439
380
|
body = await resp.text()
|
|
440
381
|
except aiohttp.ClientError as err:
|
|
441
|
-
raise PGWConnectionError(
|
|
442
|
-
f"Failed to fetch hourly gas usage: {err}"
|
|
443
|
-
) from err
|
|
382
|
+
raise PGWConnectionError(f"Failed to fetch hourly gas usage: {err}") from err
|
|
444
383
|
|
|
445
384
|
try:
|
|
446
385
|
data = json.loads(body)
|
|
447
386
|
inner = json.loads(data["d"])
|
|
448
387
|
except (json.JSONDecodeError, KeyError) as err:
|
|
449
|
-
raise PGWConnectionError(
|
|
450
|
-
"Unexpected response from LoadGasUsage (hourly)"
|
|
451
|
-
) from err
|
|
388
|
+
raise PGWConnectionError("Unexpected response from LoadGasUsage (hourly)") from err
|
|
452
389
|
|
|
453
390
|
if isinstance(inner, dict) and "dtException" in inner:
|
|
454
391
|
msg = inner["dtException"][0].get("MessageInformation", "Unknown error")
|
|
@@ -474,20 +411,24 @@ class PGWApiClient:
|
|
|
474
411
|
|
|
475
412
|
|
|
476
413
|
def _parse_datetime(dt_str: str | None) -> datetime | None:
|
|
477
|
-
"""Parse a datetime string from the portal (various formats).
|
|
414
|
+
"""Parse a datetime string from the portal (various formats).
|
|
415
|
+
|
|
416
|
+
Returns a naive datetime deliberately: the portal reports these in its
|
|
417
|
+
own local time with no timezone offset in the source string, so there is
|
|
418
|
+
no correct tzinfo to attach without guessing.
|
|
419
|
+
"""
|
|
478
420
|
if not dt_str:
|
|
479
421
|
return None
|
|
480
|
-
from datetime import datetime
|
|
481
422
|
|
|
482
423
|
for fmt in ("%m/%d/%Y %I:%M:%S %p", "%m/%d/%Y %H:%M:%S", "%m/%d/%y %I:%M:%S %p", "%m/%d/%y"):
|
|
483
424
|
try:
|
|
484
|
-
return datetime.strptime(dt_str, fmt)
|
|
425
|
+
return datetime.strptime(dt_str, fmt) # noqa: DTZ007 - naive by design, see docstring
|
|
485
426
|
except ValueError:
|
|
486
427
|
continue
|
|
487
428
|
|
|
488
429
|
parsed_date = _parse_date(dt_str)
|
|
489
430
|
if parsed_date:
|
|
490
|
-
return datetime(parsed_date.year, parsed_date.month, parsed_date.day)
|
|
431
|
+
return datetime(parsed_date.year, parsed_date.month, parsed_date.day) # noqa: DTZ001 - naive by design, see docstring
|
|
491
432
|
return None
|
|
492
433
|
|
|
493
434
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pgw-api
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Python API client for Philadelphia Gas Works (PGW)
|
|
5
5
|
Author: zackwag
|
|
6
6
|
License: MIT
|
|
@@ -29,6 +29,7 @@ import asyncio
|
|
|
29
29
|
import aiohttp
|
|
30
30
|
from pgw_api import PGWApiClient
|
|
31
31
|
|
|
32
|
+
|
|
32
33
|
async def main():
|
|
33
34
|
client = PGWApiClient("your-email@example.com", "your-password")
|
|
34
35
|
|
|
@@ -40,6 +41,7 @@ async def main():
|
|
|
40
41
|
if entry.period_start and entry.period_end:
|
|
41
42
|
print(f" Period: {entry.period_start} to {entry.period_end}")
|
|
42
43
|
|
|
44
|
+
|
|
43
45
|
asyncio.run(main())
|
|
44
46
|
```
|
|
45
47
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pgw-api"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.4.0"
|
|
8
8
|
description = "Python API client for Philadelphia Gas Works (PGW)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -25,3 +25,14 @@ asyncio_mode = "auto"
|
|
|
25
25
|
|
|
26
26
|
[tool.setuptools.packages.find]
|
|
27
27
|
include = ["pgw_api*"]
|
|
28
|
+
|
|
29
|
+
[tool.ruff]
|
|
30
|
+
line-length = 100
|
|
31
|
+
|
|
32
|
+
[tool.ruff.lint.per-file-ignores]
|
|
33
|
+
# These construct naive datetimes to match _parse_datetime's deliberately
|
|
34
|
+
# naive output (the portal's timestamps carry no timezone offset) -- see
|
|
35
|
+
# the docstring on _parse_datetime in pgw_api/client.py.
|
|
36
|
+
"tests/test_client.py" = ["DTZ001"]
|
|
37
|
+
"tests/test_models.py" = ["DTZ001"]
|
|
38
|
+
"tests/test_parsing.py" = ["DTZ001"]
|
|
@@ -7,8 +7,7 @@ import pytest
|
|
|
7
7
|
|
|
8
8
|
from pgw_api.client import PGWApiClient
|
|
9
9
|
from pgw_api.models import BillingSummary
|
|
10
|
-
from tests.conftest import
|
|
11
|
-
|
|
10
|
+
from tests.conftest import make_login_success, make_response
|
|
12
11
|
|
|
13
12
|
BILLING_HTML = """
|
|
14
13
|
<input type="hidden" id="hdnTotalBillOFCurrentMonth" value="$120.50" />
|
|
@@ -47,12 +46,14 @@ def _setup_session(responses):
|
|
|
47
46
|
class TestBillingParsing:
|
|
48
47
|
@pytest.mark.asyncio
|
|
49
48
|
async def test_parses_all_fields(self):
|
|
50
|
-
session = _setup_session(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
session = _setup_session(
|
|
50
|
+
[
|
|
51
|
+
make_response(), # GET login page
|
|
52
|
+
make_response(text=make_login_success()), # POST validateLogin
|
|
53
|
+
make_response(), # GET Dashboard
|
|
54
|
+
make_response(text=BILLING_HTML), # GET BillDashboard
|
|
55
|
+
]
|
|
56
|
+
)
|
|
56
57
|
|
|
57
58
|
client = PGWApiClient("user", "pass")
|
|
58
59
|
billing = await client.async_get_billing(session)
|
|
@@ -72,12 +73,14 @@ class TestBillingParsing:
|
|
|
72
73
|
@pytest.mark.asyncio
|
|
73
74
|
async def test_missing_fields_default_to_zero(self):
|
|
74
75
|
html = "<html><body>no hidden fields here</body></html>"
|
|
75
|
-
session = _setup_session(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
session = _setup_session(
|
|
77
|
+
[
|
|
78
|
+
make_response(),
|
|
79
|
+
make_response(text=make_login_success()),
|
|
80
|
+
make_response(),
|
|
81
|
+
make_response(text=html),
|
|
82
|
+
]
|
|
83
|
+
)
|
|
81
84
|
|
|
82
85
|
client = PGWApiClient("user", "pass")
|
|
83
86
|
billing = await client.async_get_billing(session)
|
|
@@ -91,12 +94,14 @@ class TestBillingParsing:
|
|
|
91
94
|
@pytest.mark.asyncio
|
|
92
95
|
async def test_malformed_period_json(self):
|
|
93
96
|
html = '<input type="hidden" id="hdnbillComparisionOFCurrentMonth" value="not json" />'
|
|
94
|
-
session = _setup_session(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
session = _setup_session(
|
|
98
|
+
[
|
|
99
|
+
make_response(),
|
|
100
|
+
make_response(text=make_login_success()),
|
|
101
|
+
make_response(),
|
|
102
|
+
make_response(text=html),
|
|
103
|
+
]
|
|
104
|
+
)
|
|
100
105
|
|
|
101
106
|
client = PGWApiClient("user", "pass")
|
|
102
107
|
billing = await client.async_get_billing(session)
|
|
@@ -107,32 +112,56 @@ class TestBillingParsing:
|
|
|
107
112
|
class TestBillingSummaryModel:
|
|
108
113
|
def test_current_usage_cf(self):
|
|
109
114
|
b = BillingSummary(
|
|
110
|
-
current_bill=100,
|
|
111
|
-
|
|
112
|
-
|
|
115
|
+
current_bill=100,
|
|
116
|
+
current_usage_ccf=50,
|
|
117
|
+
current_period_days=30,
|
|
118
|
+
previous_bill=80,
|
|
119
|
+
previous_usage_ccf=40,
|
|
120
|
+
previous_period_days=30,
|
|
121
|
+
previous_year_bill=90,
|
|
122
|
+
previous_year_usage_ccf=45,
|
|
123
|
+
balance_due=100,
|
|
113
124
|
)
|
|
114
125
|
assert b.current_usage_cf == 5000.0
|
|
115
126
|
|
|
116
127
|
def test_previous_usage_cf(self):
|
|
117
128
|
b = BillingSummary(
|
|
118
|
-
current_bill=100,
|
|
119
|
-
|
|
120
|
-
|
|
129
|
+
current_bill=100,
|
|
130
|
+
current_usage_ccf=50,
|
|
131
|
+
current_period_days=30,
|
|
132
|
+
previous_bill=80,
|
|
133
|
+
previous_usage_ccf=40,
|
|
134
|
+
previous_period_days=30,
|
|
135
|
+
previous_year_bill=90,
|
|
136
|
+
previous_year_usage_ccf=45,
|
|
137
|
+
balance_due=100,
|
|
121
138
|
)
|
|
122
139
|
assert b.previous_usage_cf == 4000.0
|
|
123
140
|
|
|
124
141
|
def test_current_rate(self):
|
|
125
142
|
b = BillingSummary(
|
|
126
|
-
current_bill=200,
|
|
127
|
-
|
|
128
|
-
|
|
143
|
+
current_bill=200,
|
|
144
|
+
current_usage_ccf=100,
|
|
145
|
+
current_period_days=30,
|
|
146
|
+
previous_bill=80,
|
|
147
|
+
previous_usage_ccf=40,
|
|
148
|
+
previous_period_days=30,
|
|
149
|
+
previous_year_bill=90,
|
|
150
|
+
previous_year_usage_ccf=45,
|
|
151
|
+
balance_due=200,
|
|
129
152
|
)
|
|
130
153
|
assert b.current_rate == 2.0
|
|
131
154
|
|
|
132
155
|
def test_current_rate_zero_usage(self):
|
|
133
156
|
b = BillingSummary(
|
|
134
|
-
current_bill=0,
|
|
135
|
-
|
|
136
|
-
|
|
157
|
+
current_bill=0,
|
|
158
|
+
current_usage_ccf=0,
|
|
159
|
+
current_period_days=0,
|
|
160
|
+
previous_bill=0,
|
|
161
|
+
previous_usage_ccf=0,
|
|
162
|
+
previous_period_days=0,
|
|
163
|
+
previous_year_bill=0,
|
|
164
|
+
previous_year_usage_ccf=0,
|
|
165
|
+
balance_due=0,
|
|
137
166
|
)
|
|
138
167
|
assert b.current_rate is None
|