e-data 1.3.1__py3-none-any.whl

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.
@@ -0,0 +1,242 @@
1
+ """Tests for DatadisConnector (offline)."""
2
+
3
+ import datetime
4
+ import datetime
5
+ from unittest.mock import patch, AsyncMock, MagicMock
6
+ from ..connectors.datadis import DatadisConnector
7
+
8
+ from ..connectors.datadis import DatadisConnector
9
+
10
+ MOCK_USERNAME = "USERNAME"
11
+ MOCK_PASSWORD = "PASSWORD"
12
+
13
+ SUPPLIES_RESPONSE = [
14
+ {
15
+ "cups": "ESXXXXXXXXXXXXXXXXTEST",
16
+ "validDateFrom": "2022/03/09",
17
+ "validDateTo": "2022/10/28",
18
+ "address": "-",
19
+ "postalCode": "-",
20
+ "province": "-",
21
+ "municipality": "-",
22
+ "distributor": "-",
23
+ "pointType": 5,
24
+ "distributorCode": "2",
25
+ }
26
+ ]
27
+
28
+ CONTRACTS_RESPONSE = [
29
+ {
30
+ "startDate": "2022/03/09",
31
+ "endDate": "2022/10/28",
32
+ "marketer": "MARKETER",
33
+ "distributorCode": "2",
34
+ "contractedPowerkW": [4.4, 4.4],
35
+ }
36
+ ]
37
+
38
+ CONSUMPTIONS_RESPONSE = [
39
+ {
40
+ "date": "2022/10/22",
41
+ "time": "01:00",
42
+ "consumptionKWh": 0.203,
43
+ "surplusEnergyKWh": 0,
44
+ "obtainMethod": "Real",
45
+ },
46
+ {
47
+ "date": "2022/10/22",
48
+ "time": "02:00",
49
+ "consumptionKWh": 0.163,
50
+ "surplusEnergyKWh": 0,
51
+ "obtainMethod": "Real",
52
+ },
53
+ ]
54
+
55
+ MAXIMETER_RESPONSE = [
56
+ {
57
+ "date": "2022/03/10",
58
+ "time": "14:15",
59
+ "maxPower": 2.436,
60
+ },
61
+ {
62
+ "date": "2022/03/14",
63
+ "time": "13:15",
64
+ "maxPower": 3.008,
65
+ },
66
+ {
67
+ "date": "2022/03/27",
68
+ "time": "10:30",
69
+ "maxPower": 3.288,
70
+ },
71
+ ]
72
+
73
+ @patch("aiohttp.ClientSession.get")
74
+ @patch.object(DatadisConnector, "_async_get_token", new_callable=AsyncMock, return_value=True)
75
+ def test_get_supplies(mock_token, mock_get, snapshot):
76
+ """Test a successful 'get_supplies' query (syrupy snapshot)."""
77
+ mock_response = MagicMock()
78
+ mock_response.status = 200
79
+ mock_response.text = AsyncMock(return_value="text")
80
+ mock_response.json = AsyncMock(return_value=SUPPLIES_RESPONSE)
81
+ mock_get.return_value.__aenter__.return_value = mock_response
82
+ connector = DatadisConnector(MOCK_USERNAME, MOCK_PASSWORD)
83
+ assert connector.get_supplies() == snapshot
84
+
85
+
86
+
87
+ @patch("aiohttp.ClientSession.get")
88
+ @patch.object(DatadisConnector, "_async_get_token", new_callable=AsyncMock, return_value=True)
89
+ def test_get_contract_detail(mock_token, mock_get, snapshot):
90
+ """Test a successful 'get_contract_detail' query (syrupy snapshot)."""
91
+ mock_response = MagicMock()
92
+ mock_response.status = 200
93
+ mock_response.text = AsyncMock(return_value="text")
94
+ mock_response.json = AsyncMock(return_value=CONTRACTS_RESPONSE)
95
+ mock_get.return_value.__aenter__.return_value = mock_response
96
+ connector = DatadisConnector(MOCK_USERNAME, MOCK_PASSWORD)
97
+ assert connector.get_contract_detail("ESXXXXXXXXXXXXXXXXTEST", "2") == snapshot
98
+
99
+
100
+
101
+ @patch("aiohttp.ClientSession.get")
102
+ @patch.object(DatadisConnector, "_async_get_token", new_callable=AsyncMock, return_value=True)
103
+ def test_get_consumption_data(mock_token, mock_get, snapshot):
104
+ """Test a successful 'get_consumption_data' query (syrupy snapshot)."""
105
+ mock_response = MagicMock()
106
+ mock_response.status = 200
107
+ mock_response.text = AsyncMock(return_value="text")
108
+ mock_response.json = AsyncMock(return_value=CONSUMPTIONS_RESPONSE)
109
+ mock_get.return_value.__aenter__.return_value = mock_response
110
+ connector = DatadisConnector(MOCK_USERNAME, MOCK_PASSWORD)
111
+ assert connector.get_consumption_data(
112
+ "ESXXXXXXXXXXXXXXXXTEST",
113
+ "2",
114
+ datetime.datetime(2022, 10, 22, 0, 0, 0),
115
+ datetime.datetime(2022, 10, 22, 2, 0, 0),
116
+ "0",
117
+ 5,
118
+ ) == snapshot
119
+
120
+
121
+
122
+ @patch("aiohttp.ClientSession.get")
123
+ @patch.object(DatadisConnector, "_async_get_token", new_callable=AsyncMock, return_value=True)
124
+ def test_get_max_power(mock_token, mock_get, snapshot):
125
+ """Test a successful 'get_max_power' query (syrupy snapshot)."""
126
+ mock_response = MagicMock()
127
+ mock_response.status = 200
128
+ mock_response.text = AsyncMock(return_value="text")
129
+ mock_response.json = AsyncMock(return_value=MAXIMETER_RESPONSE)
130
+ mock_get.return_value.__aenter__.return_value = mock_response
131
+ connector = DatadisConnector(MOCK_USERNAME, MOCK_PASSWORD)
132
+ assert connector.get_max_power(
133
+ "ESXXXXXXXXXXXXXXXXTEST",
134
+ "2",
135
+ datetime.datetime(2022, 3, 1, 0, 0, 0),
136
+ datetime.datetime(2022, 4, 1, 0, 0, 0),
137
+ None,
138
+ ) == snapshot
139
+
140
+
141
+ @patch("aiohttp.ClientSession.get")
142
+ @patch.object(DatadisConnector, "_async_get_token", new_callable=AsyncMock, return_value=True)
143
+ def test_get_supplies_empty_response(mock_token, mock_get, snapshot):
144
+ """Test get_supplies with empty response (syrupy snapshot)."""
145
+ mock_response = MagicMock()
146
+ mock_response.status = 200
147
+ mock_response.text = AsyncMock(return_value="text")
148
+ mock_response.json = AsyncMock(return_value=[])
149
+ mock_get.return_value.__aenter__.return_value = mock_response
150
+ connector = DatadisConnector(MOCK_USERNAME, MOCK_PASSWORD)
151
+ assert connector.get_supplies() == snapshot
152
+
153
+
154
+ @patch("aiohttp.ClientSession.get")
155
+ @patch.object(DatadisConnector, "_async_get_token", new_callable=AsyncMock, return_value=True)
156
+ def test_get_supplies_malformed_response(mock_token, mock_get, snapshot):
157
+ """Test get_supplies with malformed response (missing required fields, syrupy snapshot)."""
158
+ malformed = [{"validDateFrom": "2022/03/09"}] # missing 'cups', etc.
159
+ mock_response = MagicMock()
160
+ mock_response.status = 200
161
+ mock_response.text = AsyncMock(return_value="text")
162
+ mock_response.json = AsyncMock(return_value=malformed)
163
+ mock_get.return_value.__aenter__.return_value = mock_response
164
+ connector = DatadisConnector(MOCK_USERNAME, MOCK_PASSWORD)
165
+ assert connector.get_supplies() == snapshot
166
+
167
+
168
+ @patch("aiohttp.ClientSession.get")
169
+ @patch.object(DatadisConnector, "_async_get_token", new_callable=AsyncMock, return_value=True)
170
+ def test_get_supplies_partial_response(mock_token, mock_get, snapshot):
171
+ """Test get_supplies with partial valid/invalid response (syrupy snapshot)."""
172
+ partial = [
173
+ SUPPLIES_RESPONSE[0],
174
+ {"validDateFrom": "2022/03/09"}, # invalid
175
+ ]
176
+ mock_response = MagicMock()
177
+ mock_response.status = 200
178
+ mock_response.text = AsyncMock(return_value="text")
179
+ mock_response.json = AsyncMock(return_value=partial)
180
+ mock_get.return_value.__aenter__.return_value = mock_response
181
+ connector = DatadisConnector(MOCK_USERNAME, MOCK_PASSWORD)
182
+ assert connector.get_supplies() == snapshot
183
+
184
+
185
+
186
+ @patch("aiohttp.ClientSession.get")
187
+ @patch.object(DatadisConnector, "_async_get_token", new_callable=AsyncMock, return_value=True)
188
+ def test_get_consumption_data_cache(mock_token, mock_get, snapshot):
189
+ """Test get_consumption_data uses cache on second call (should not call HTTP again, syrupy snapshot)."""
190
+ mock_response = MagicMock()
191
+ mock_response.status = 200
192
+ mock_response.text = AsyncMock(return_value="text")
193
+ mock_response.json = AsyncMock(return_value=CONSUMPTIONS_RESPONSE)
194
+ mock_get.return_value.__aenter__.return_value = mock_response
195
+ connector = DatadisConnector(MOCK_USERNAME, MOCK_PASSWORD)
196
+ # First call populates cache
197
+ assert connector.get_consumption_data(
198
+ "ESXXXXXXXXXXXXXXXXTEST",
199
+ "2",
200
+ datetime.datetime(2022, 10, 22, 0, 0, 0),
201
+ datetime.datetime(2022, 10, 22, 2, 0, 0),
202
+ "0",
203
+ 5,
204
+ ) == snapshot
205
+ # Second call should use cache, not call HTTP again
206
+ mock_get.reset_mock()
207
+ assert connector.get_consumption_data(
208
+ "ESXXXXXXXXXXXXXXXXTEST",
209
+ "2",
210
+ datetime.datetime(2022, 10, 22, 0, 0, 0),
211
+ datetime.datetime(2022, 10, 22, 2, 0, 0),
212
+ "0",
213
+ 5,
214
+ ) == snapshot
215
+ mock_get.assert_not_called()
216
+
217
+
218
+ @patch("aiohttp.ClientSession.get")
219
+ @patch.object(DatadisConnector, "_async_get_token", new_callable=AsyncMock, return_value=True)
220
+ def test_get_supplies_optional_fields_none(mock_token, mock_get, snapshot):
221
+ """Test get_supplies with optional fields as None (syrupy snapshot)."""
222
+ response = [
223
+ {
224
+ "cups": "ESXXXXXXXXXXXXXXXXTEST",
225
+ "validDateFrom": "2022/03/09",
226
+ "validDateTo": "2022/10/28",
227
+ "address": None,
228
+ "postalCode": None,
229
+ "province": None,
230
+ "municipality": None,
231
+ "distributor": None,
232
+ "pointType": 5,
233
+ "distributorCode": "2",
234
+ }
235
+ ]
236
+ mock_response = MagicMock()
237
+ mock_response.status = 200
238
+ mock_response.text = AsyncMock(return_value="text")
239
+ mock_response.json = AsyncMock(return_value=response)
240
+ mock_get.return_value.__aenter__.return_value = mock_response
241
+ connector = DatadisConnector(MOCK_USERNAME, MOCK_PASSWORD)
242
+ assert connector.get_supplies() == snapshot
tests/test_helpers.py ADDED
@@ -0,0 +1,52 @@
1
+ """A collection of tests for e-data processors"""
2
+
3
+ import json
4
+ import pathlib
5
+
6
+ from freezegun import freeze_time
7
+
8
+ from ..definitions import PricingRules
9
+ from ..helpers import EdataHelper
10
+ from ..processors import utils
11
+
12
+ AT_TIME = "2022-10-22"
13
+ TESTS_DIR = str(pathlib.Path(__file__).parent.resolve())
14
+ TEST_GOOD_INPUT = TESTS_DIR + "/assets/helpers/edata.storage_TEST"
15
+ TEST_EXPECTATIONS_DATA = TESTS_DIR + "/assets/helpers/data.out"
16
+ TEST_EXPECTATIONS_ATTRIBUTES = (
17
+ TESTS_DIR + f"/assets/helpers/attributes_at_{AT_TIME}.out"
18
+ )
19
+
20
+ PRICING_RULES_PVPC = PricingRules(
21
+ p1_kw_year_eur=30.67266,
22
+ p2_kw_year_eur=1.4243591,
23
+ meter_month_eur=0.81,
24
+ market_kw_year_eur=3.113,
25
+ electricity_tax=1.0511300560,
26
+ iva_tax=1.05,
27
+ p1_kwh_eur=None,
28
+ p2_kwh_eur=None,
29
+ p3_kwh_eur=None,
30
+ )
31
+
32
+ @freeze_time(AT_TIME)
33
+ def test_helper_offline(snapshot) -> None:
34
+ """Tests EdataHelper (syrupy snapshot)"""
35
+ with open(TEST_GOOD_INPUT, "r", encoding="utf-8") as original_file:
36
+ data = utils.deserialize_dict(json.load(original_file))
37
+
38
+ helper = EdataHelper(
39
+ "USER",
40
+ "PASS",
41
+ "CUPS",
42
+ datadis_authorized_nif=None,
43
+ pricing_rules=PRICING_RULES_PVPC,
44
+ data=data,
45
+ )
46
+ helper.process_data()
47
+
48
+ # Compara ambos outputs con snapshot
49
+ assert {
50
+ "data": utils.serialize_dict(helper.data),
51
+ "attributes": utils.serialize_dict(helper.attributes),
52
+ } == snapshot
@@ -0,0 +1,106 @@
1
+ """A collection of tests for e-data processors"""
2
+
3
+ import datetime as dt
4
+ import json
5
+ import pathlib
6
+ import typing
7
+ from collections.abc import Iterable
8
+
9
+ import pytest
10
+
11
+ from ..definitions import PricingData, PricingRules
12
+ from ..processors import utils
13
+ from ..processors.base import Processor
14
+ from ..processors.billing import BillingProcessor
15
+ from ..processors.consumption import ConsumptionProcessor
16
+ from ..processors.maximeter import MaximeterProcessor
17
+
18
+ TESTS_DIR = str(pathlib.Path(__file__).parent.resolve())
19
+ TEST_GOOD_INPUT = TESTS_DIR + "/assets/processors/edata.storage_TEST"
20
+
21
+ def _compare_processor_output(
22
+ source_filepath: str,
23
+ processor_class: Processor,
24
+ key: str,
25
+ snapshot,
26
+ ):
27
+ with open(source_filepath, encoding="utf-8") as original_file:
28
+ data = utils.deserialize_dict(json.load(original_file))
29
+ if key == "consumptions":
30
+ processor = processor_class({"consumptions": data[key]})
31
+ else:
32
+ processor = processor_class(data[key])
33
+ assert utils.serialize_dict(processor.output) == snapshot
34
+
35
+
36
+ @pytest.mark.parametrize(
37
+ "processor, key",
38
+ [(ConsumptionProcessor, "consumptions"), (MaximeterProcessor, "maximeter")],
39
+ )
40
+ def test_processor(processor: Processor, key: str, snapshot) -> None:
41
+ """Tests all processors but billing (syrupy snapshot)"""
42
+ _compare_processor_output(
43
+ TEST_GOOD_INPUT,
44
+ processor,
45
+ key,
46
+ snapshot,
47
+ )
48
+
49
+
50
+ @pytest.mark.parametrize(
51
+ "_id, rules, prices",
52
+ [
53
+ (
54
+ "custom_prices",
55
+ PricingRules(
56
+ p1_kw_year_eur=30.67266,
57
+ p2_kw_year_eur=1.4243591,
58
+ meter_month_eur=0.81,
59
+ market_kw_year_eur=3.113,
60
+ electricity_tax=1.0511300560,
61
+ iva_tax=1.1,
62
+ p1_kwh_eur=None,
63
+ p2_kwh_eur=None,
64
+ p3_kwh_eur=None,
65
+ ),
66
+ [
67
+ PricingData(
68
+ datetime=dt.datetime(2022, 10, 22, x, 0, 0),
69
+ value_eur_kWh=1,
70
+ delta_h=1,
71
+ )
72
+ for x in range(0, 24)
73
+ ],
74
+ ),
75
+ (
76
+ "constant_prices",
77
+ PricingRules(
78
+ p1_kw_year_eur=30.67266,
79
+ p2_kw_year_eur=1.4243591,
80
+ meter_month_eur=0.81,
81
+ market_kw_year_eur=3.113,
82
+ electricity_tax=1.0511300560,
83
+ iva_tax=1.1,
84
+ p1_kwh_eur=1,
85
+ p2_kwh_eur=1,
86
+ p3_kwh_eur=1,
87
+ ),
88
+ None,
89
+ ),
90
+ ],
91
+ )
92
+ def test_processor_billing(
93
+ _id: str, rules: PricingRules, prices: typing.Optional[Iterable[PricingData]], snapshot
94
+ ):
95
+ """Tests billing processor (syrupy snapshot)"""
96
+ with open(TEST_GOOD_INPUT, "r", encoding="utf-8") as original_file:
97
+ data = utils.deserialize_dict(json.load(original_file))
98
+ processor = BillingProcessor(
99
+ {
100
+ "consumptions": data["consumptions"],
101
+ "contracts": data["contracts"],
102
+ "prices": prices,
103
+ "rules": rules,
104
+ }
105
+ )
106
+ assert utils.serialize_dict(processor.output) == snapshot
@@ -0,0 +1,14 @@
1
+ """Tests for REData (online)"""
2
+
3
+ from datetime import datetime, timedelta
4
+
5
+ from ..connectors.redata import REDataConnector
6
+
7
+ def test_get_realtime_prices():
8
+ """Test a successful 'get_realtime_prices' query"""
9
+ connector = REDataConnector()
10
+ yesterday = datetime.now().replace(hour=0, minute=0, second=0) - timedelta(days=1)
11
+ response = connector.get_realtime_prices(
12
+ yesterday, yesterday + timedelta(days=1) - timedelta(minutes=1), False
13
+ )
14
+ assert len(response) == 24