e-data 2.0.0b1__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.
- e_data-2.0.0b1.dist-info/METADATA +214 -0
- e_data-2.0.0b1.dist-info/RECORD +21 -0
- e_data-2.0.0b1.dist-info/WHEEL +5 -0
- e_data-2.0.0b1.dist-info/licenses/LICENSE +674 -0
- e_data-2.0.0b1.dist-info/top_level.txt +1 -0
- edata/__init__.py +0 -0
- edata/const.py +56 -0
- edata/helpers.py +312 -0
- edata/tests/__init__.py +0 -0
- edata/tests/connectors/__init__.py +0 -0
- edata/tests/connectors/test_datadis_connector.py +178 -0
- edata/tests/connectors/test_redata_connector.py +29 -0
- edata/tests/services/__init__.py +0 -0
- edata/tests/services/test_billing_service.py +1169 -0
- edata/tests/services/test_consumption_service.py +955 -0
- edata/tests/services/test_contract_service.py +216 -0
- edata/tests/services/test_database_service.py +415 -0
- edata/tests/services/test_maximeter_service.py +83 -0
- edata/tests/services/test_supply_service.py +251 -0
- edata/tests/test_helpers.py +612 -0
- edata/utils.py +164 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""Tests for ContractService."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from unittest.mock import AsyncMock, Mock, patch
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
import pytest_asyncio
|
|
8
|
+
|
|
9
|
+
from edata.models.database import ContractModel
|
|
10
|
+
from edata.services.contract import ContractService
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest_asyncio.fixture
|
|
14
|
+
async def contract_service():
|
|
15
|
+
"""Create a contract service with mocked dependencies."""
|
|
16
|
+
with patch("edata.services.contract.get_database_service") as mock_db_factory:
|
|
17
|
+
mock_db = Mock()
|
|
18
|
+
# Hacer que los métodos de la base de datos retornen AsyncMock
|
|
19
|
+
mock_db.get_contracts = AsyncMock(return_value=[])
|
|
20
|
+
mock_db.save_contract = AsyncMock(return_value=Mock())
|
|
21
|
+
mock_db_factory.return_value = mock_db
|
|
22
|
+
|
|
23
|
+
# Create a mock DatadisConnector
|
|
24
|
+
mock_datadis_connector = Mock()
|
|
25
|
+
service = ContractService(datadis_connector=mock_datadis_connector)
|
|
26
|
+
service._db_service = mock_db
|
|
27
|
+
return service
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.fixture
|
|
31
|
+
def sample_contracts():
|
|
32
|
+
"""Sample contract data for testing."""
|
|
33
|
+
return [
|
|
34
|
+
ContractModel(
|
|
35
|
+
cups="ES0012345678901234567890AB",
|
|
36
|
+
date_start=datetime(2023, 1, 1),
|
|
37
|
+
date_end=datetime(2023, 12, 31),
|
|
38
|
+
marketer="Test Marketer 1",
|
|
39
|
+
distributor_code="123",
|
|
40
|
+
power_p1=4.6,
|
|
41
|
+
power_p2=4.6,
|
|
42
|
+
),
|
|
43
|
+
ContractModel(
|
|
44
|
+
cups="ES0012345678901234567890AB",
|
|
45
|
+
date_start=datetime(2024, 1, 1),
|
|
46
|
+
date_end=datetime(2024, 12, 31),
|
|
47
|
+
marketer="Test Marketer 2",
|
|
48
|
+
distributor_code="123",
|
|
49
|
+
power_p1=5.0,
|
|
50
|
+
power_p2=5.0,
|
|
51
|
+
),
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class TestContractService:
|
|
56
|
+
"""Test class for ContractService."""
|
|
57
|
+
|
|
58
|
+
@pytest.mark.asyncio
|
|
59
|
+
async def test_update_contracts_success(self, contract_service, sample_contracts):
|
|
60
|
+
"""Test successful contract update."""
|
|
61
|
+
# Setup mocks - now returns Pydantic models instead of dicts
|
|
62
|
+
contract_service._datadis.get_contract_detail = AsyncMock(
|
|
63
|
+
return_value=[
|
|
64
|
+
ContractModel(
|
|
65
|
+
cups="ES0012345678901234567890AB",
|
|
66
|
+
date_start=datetime(2024, 1, 1),
|
|
67
|
+
date_end=datetime(2024, 12, 31),
|
|
68
|
+
marketer="Test Marketer",
|
|
69
|
+
distributor_code="123",
|
|
70
|
+
power_p1=5.0,
|
|
71
|
+
power_p2=5.0,
|
|
72
|
+
)
|
|
73
|
+
]
|
|
74
|
+
)
|
|
75
|
+
contract_service._db_service.get_contracts.return_value = []
|
|
76
|
+
contract_service._db_service.save_contract.return_value = sample_contracts[0]
|
|
77
|
+
|
|
78
|
+
# Execute
|
|
79
|
+
result = await contract_service.update_contracts(
|
|
80
|
+
cups="ES0012345678901234567890AB", distributor_code="123"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Verify
|
|
84
|
+
assert result["success"] is True
|
|
85
|
+
assert result["stats"]["fetched"] == 1
|
|
86
|
+
assert result["stats"]["saved"] == 1
|
|
87
|
+
assert result["stats"]["updated"] == 0
|
|
88
|
+
contract_service._datadis.get_contract_detail.assert_called_once()
|
|
89
|
+
contract_service._db_service.save_contract.assert_called_once()
|
|
90
|
+
|
|
91
|
+
@pytest.mark.asyncio
|
|
92
|
+
async def test_get_contracts(self, contract_service, sample_contracts):
|
|
93
|
+
"""Test getting contracts."""
|
|
94
|
+
# Setup mocks
|
|
95
|
+
contract_service._db_service.get_contracts.return_value = sample_contracts
|
|
96
|
+
|
|
97
|
+
# Execute
|
|
98
|
+
result = await contract_service.get_contracts("ES0012345678901234567890AB")
|
|
99
|
+
|
|
100
|
+
# Verify
|
|
101
|
+
assert len(result) == 2
|
|
102
|
+
assert result[0].power_p1 == 4.6
|
|
103
|
+
assert result[1].power_p1 == 5.0
|
|
104
|
+
contract_service._db_service.get_contracts.assert_called_once_with(
|
|
105
|
+
cups="ES0012345678901234567890AB"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
@pytest.mark.asyncio
|
|
109
|
+
async def test_get_active_contract(self, contract_service, sample_contracts):
|
|
110
|
+
"""Test getting active contract."""
|
|
111
|
+
# Setup mocks
|
|
112
|
+
contract_service._db_service.get_contracts.return_value = sample_contracts
|
|
113
|
+
|
|
114
|
+
# Execute - test with date in 2024
|
|
115
|
+
result = await contract_service.get_active_contract(
|
|
116
|
+
"ES0012345678901234567890AB", datetime(2024, 6, 15)
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Verify
|
|
120
|
+
assert result is not None
|
|
121
|
+
assert result.power_p1 == 5.0 # Should return 2024 contract
|
|
122
|
+
assert result.date_start.year == 2024
|
|
123
|
+
|
|
124
|
+
@pytest.mark.asyncio
|
|
125
|
+
async def test_get_most_recent_contract(self, contract_service, sample_contracts):
|
|
126
|
+
"""Test getting most recent contract."""
|
|
127
|
+
# Setup mocks
|
|
128
|
+
contract_service._db_service.get_contracts.return_value = sample_contracts
|
|
129
|
+
|
|
130
|
+
# Execute - use the correct method name
|
|
131
|
+
result = await contract_service.get_latest_contract(
|
|
132
|
+
"ES0012345678901234567890AB"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Verify
|
|
136
|
+
assert result is not None
|
|
137
|
+
assert result.power_p1 == 5.0 # Should return 2024 contract (most recent)
|
|
138
|
+
assert result.date_start.year == 2024
|
|
139
|
+
|
|
140
|
+
@pytest.mark.asyncio
|
|
141
|
+
async def test_get_contract_stats(self, contract_service, sample_contracts):
|
|
142
|
+
"""Test getting contract statistics."""
|
|
143
|
+
# Setup mocks
|
|
144
|
+
contract_service._db_service.get_contracts.return_value = sample_contracts
|
|
145
|
+
|
|
146
|
+
# Execute
|
|
147
|
+
result = await contract_service.get_contract_stats("ES0012345678901234567890AB")
|
|
148
|
+
|
|
149
|
+
# Verify
|
|
150
|
+
assert result["total_contracts"] == 2
|
|
151
|
+
assert result["power_ranges"]["p1_kw"]["min"] == 4.6
|
|
152
|
+
assert result["power_ranges"]["p1_kw"]["max"] == 5.0
|
|
153
|
+
assert result["power_ranges"]["p2_kw"]["min"] == 4.6
|
|
154
|
+
assert result["power_ranges"]["p2_kw"]["max"] == 5.0
|
|
155
|
+
assert result["date_range"]["earliest_start"] == datetime(2023, 1, 1)
|
|
156
|
+
assert result["date_range"]["latest_end"] == datetime(2024, 12, 31)
|
|
157
|
+
|
|
158
|
+
@pytest.mark.asyncio
|
|
159
|
+
async def test_update_contracts_no_data(self, contract_service):
|
|
160
|
+
"""Test contract update with no data returned."""
|
|
161
|
+
# Setup mocks
|
|
162
|
+
contract_service._datadis.get_contract_detail = AsyncMock(return_value=[])
|
|
163
|
+
|
|
164
|
+
# Execute
|
|
165
|
+
result = await contract_service.update_contracts(
|
|
166
|
+
cups="ES0012345678901234567890AB", distributor_code="123"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Verify
|
|
170
|
+
assert result["success"] is True
|
|
171
|
+
assert result["stats"]["fetched"] == 0
|
|
172
|
+
assert result["stats"]["saved"] == 0
|
|
173
|
+
|
|
174
|
+
@pytest.mark.asyncio
|
|
175
|
+
async def test_update_contracts_error(self, contract_service):
|
|
176
|
+
"""Test contract update with error."""
|
|
177
|
+
# Setup mocks
|
|
178
|
+
contract_service._datadis.get_contract_detail = AsyncMock(
|
|
179
|
+
side_effect=Exception("API Error")
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Execute
|
|
183
|
+
result = await contract_service.update_contracts(
|
|
184
|
+
cups="ES0012345678901234567890AB", distributor_code="123"
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# Verify
|
|
188
|
+
assert result["success"] is False
|
|
189
|
+
assert "error" in result
|
|
190
|
+
assert result["error"] == "API Error"
|
|
191
|
+
|
|
192
|
+
@pytest.mark.asyncio
|
|
193
|
+
async def test_get_contract_summary(self, contract_service, sample_contracts):
|
|
194
|
+
"""Test getting contract summary."""
|
|
195
|
+
# Setup mocks
|
|
196
|
+
contract_service._db_service.get_contracts.return_value = sample_contracts
|
|
197
|
+
|
|
198
|
+
# Execute
|
|
199
|
+
result = await contract_service.get_contract_summary("ES001234567890123456AB")
|
|
200
|
+
|
|
201
|
+
# Verify
|
|
202
|
+
assert result["contract_p1_kW"] == 5.0 # From the most recent contract (2024)
|
|
203
|
+
assert result["contract_p2_kW"] == 5.0
|
|
204
|
+
|
|
205
|
+
@pytest.mark.asyncio
|
|
206
|
+
async def test_get_contract_summary_no_data(self, contract_service):
|
|
207
|
+
"""Test getting contract summary with no data."""
|
|
208
|
+
# Setup mocks
|
|
209
|
+
contract_service._db_service.get_contracts.return_value = []
|
|
210
|
+
|
|
211
|
+
# Execute
|
|
212
|
+
result = await contract_service.get_contract_summary("ES001234567890123456AB")
|
|
213
|
+
|
|
214
|
+
# Verify
|
|
215
|
+
assert result["contract_p1_kW"] is None
|
|
216
|
+
assert result["contract_p2_kW"] is None
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
"""Tests for DatabaseService."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import tempfile
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from unittest.mock import patch
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
import pytest_asyncio
|
|
11
|
+
|
|
12
|
+
from edata.models.database import (
|
|
13
|
+
ConsumptionModel,
|
|
14
|
+
ContractModel,
|
|
15
|
+
MaxPowerModel,
|
|
16
|
+
SupplyModel,
|
|
17
|
+
)
|
|
18
|
+
from edata.services.database import get_database_service
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TestDatabaseService:
|
|
22
|
+
"""Test suite for DatabaseService."""
|
|
23
|
+
|
|
24
|
+
@pytest.fixture
|
|
25
|
+
def temp_dir(self):
|
|
26
|
+
"""Create a temporary directory for tests."""
|
|
27
|
+
temp_dir = tempfile.mkdtemp()
|
|
28
|
+
yield temp_dir
|
|
29
|
+
shutil.rmtree(temp_dir)
|
|
30
|
+
|
|
31
|
+
@pytest_asyncio.fixture
|
|
32
|
+
async def db_service(self, temp_dir):
|
|
33
|
+
"""Create a database service for testing."""
|
|
34
|
+
# Create a new instance directly instead of using the global singleton
|
|
35
|
+
from edata.services.database import DatabaseService
|
|
36
|
+
|
|
37
|
+
db_service = DatabaseService(temp_dir)
|
|
38
|
+
await db_service.create_tables()
|
|
39
|
+
yield db_service
|
|
40
|
+
|
|
41
|
+
@pytest.fixture
|
|
42
|
+
def sample_supply_data(self):
|
|
43
|
+
"""Sample supply data for testing."""
|
|
44
|
+
return {
|
|
45
|
+
"cups": "ES1234567890123456789",
|
|
46
|
+
"date_start": datetime(2024, 1, 1),
|
|
47
|
+
"date_end": datetime(2024, 12, 31),
|
|
48
|
+
"address": "Test Address 123",
|
|
49
|
+
"postal_code": "12345",
|
|
50
|
+
"province": "Test Province",
|
|
51
|
+
"municipality": "Test Municipality",
|
|
52
|
+
"distributor": "Test Distributor",
|
|
53
|
+
"point_type": 5,
|
|
54
|
+
"distributor_code": "123",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@pytest.fixture
|
|
58
|
+
def sample_contract_data(self):
|
|
59
|
+
"""Sample contract data for testing."""
|
|
60
|
+
return {
|
|
61
|
+
"cups": "ES1234567890123456789",
|
|
62
|
+
"date_start": datetime(2024, 1, 1),
|
|
63
|
+
"date_end": datetime(2024, 12, 31),
|
|
64
|
+
"marketer": "Test Marketer",
|
|
65
|
+
"distributor_code": "123",
|
|
66
|
+
"power_p1": 4.4,
|
|
67
|
+
"power_p2": 4.4,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@pytest.fixture
|
|
71
|
+
def sample_consumption_data(self):
|
|
72
|
+
"""Sample consumption data for testing."""
|
|
73
|
+
return {
|
|
74
|
+
"cups": "ES1234567890123456789",
|
|
75
|
+
"datetime": datetime(2024, 6, 15, 12, 0),
|
|
76
|
+
"delta_h": 1.0,
|
|
77
|
+
"value_kwh": 0.5,
|
|
78
|
+
"surplus_kwh": 0.0,
|
|
79
|
+
"real": True,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@pytest.fixture
|
|
83
|
+
def sample_maxpower_data(self):
|
|
84
|
+
"""Sample maxpower data for testing."""
|
|
85
|
+
return {
|
|
86
|
+
"cups": "ES1234567890123456789",
|
|
87
|
+
"datetime": datetime(2024, 6, 15, 15, 30),
|
|
88
|
+
"value_kw": 3.2,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@pytest.mark.asyncio
|
|
92
|
+
async def test_database_initialization(self, temp_dir):
|
|
93
|
+
"""Test database service initialization."""
|
|
94
|
+
service = await get_database_service(storage_dir=temp_dir)
|
|
95
|
+
|
|
96
|
+
# Check that database file was created
|
|
97
|
+
expected_db_path = os.path.join(temp_dir, "edata.db")
|
|
98
|
+
assert os.path.exists(expected_db_path)
|
|
99
|
+
|
|
100
|
+
# Check that we can get a session
|
|
101
|
+
session = service.get_session()
|
|
102
|
+
assert session is not None
|
|
103
|
+
await session.close()
|
|
104
|
+
|
|
105
|
+
@pytest.mark.asyncio
|
|
106
|
+
async def test_save_and_get_supply(self, db_service, sample_supply_data):
|
|
107
|
+
"""Test saving and retrieving supply data."""
|
|
108
|
+
# Save supply
|
|
109
|
+
saved_supply = await db_service.save_supply(sample_supply_data)
|
|
110
|
+
|
|
111
|
+
assert saved_supply.cups == sample_supply_data["cups"]
|
|
112
|
+
assert saved_supply.distributor == sample_supply_data["distributor"]
|
|
113
|
+
assert saved_supply.point_type == sample_supply_data["point_type"]
|
|
114
|
+
assert saved_supply.created_at is not None
|
|
115
|
+
assert saved_supply.updated_at is not None
|
|
116
|
+
|
|
117
|
+
# Get supply
|
|
118
|
+
retrieved_supply = await db_service.get_supply(sample_supply_data["cups"])
|
|
119
|
+
|
|
120
|
+
assert retrieved_supply is not None
|
|
121
|
+
assert retrieved_supply.cups == sample_supply_data["cups"]
|
|
122
|
+
assert retrieved_supply.distributor == sample_supply_data["distributor"]
|
|
123
|
+
|
|
124
|
+
@pytest.mark.asyncio
|
|
125
|
+
async def test_update_existing_supply(self, db_service, sample_supply_data):
|
|
126
|
+
"""Test updating an existing supply."""
|
|
127
|
+
# Save initial supply
|
|
128
|
+
await db_service.save_supply(sample_supply_data)
|
|
129
|
+
|
|
130
|
+
# Update supply data
|
|
131
|
+
updated_data = sample_supply_data.copy()
|
|
132
|
+
updated_data["distributor"] = "Updated Distributor"
|
|
133
|
+
|
|
134
|
+
# Save updated supply
|
|
135
|
+
updated_supply = await db_service.save_supply(updated_data)
|
|
136
|
+
|
|
137
|
+
assert updated_supply.distributor == "Updated Distributor"
|
|
138
|
+
assert updated_supply.cups == sample_supply_data["cups"]
|
|
139
|
+
|
|
140
|
+
# Verify only one supply exists
|
|
141
|
+
retrieved_supply = await db_service.get_supply(sample_supply_data["cups"])
|
|
142
|
+
assert retrieved_supply.distributor == "Updated Distributor"
|
|
143
|
+
|
|
144
|
+
@pytest.mark.asyncio
|
|
145
|
+
async def test_save_and_get_contract(
|
|
146
|
+
self, db_service, sample_supply_data, sample_contract_data
|
|
147
|
+
):
|
|
148
|
+
"""Test saving and retrieving contract data."""
|
|
149
|
+
# Save supply first (foreign key dependency)
|
|
150
|
+
await db_service.save_supply(sample_supply_data)
|
|
151
|
+
|
|
152
|
+
# Save contract
|
|
153
|
+
saved_contract = await db_service.save_contract(sample_contract_data)
|
|
154
|
+
|
|
155
|
+
assert saved_contract.cups == sample_contract_data["cups"]
|
|
156
|
+
assert saved_contract.marketer == sample_contract_data["marketer"]
|
|
157
|
+
assert saved_contract.power_p1 == sample_contract_data["power_p1"]
|
|
158
|
+
assert saved_contract.id is not None
|
|
159
|
+
|
|
160
|
+
# Get contracts
|
|
161
|
+
contracts = await db_service.get_contracts(sample_contract_data["cups"])
|
|
162
|
+
|
|
163
|
+
assert len(contracts) == 1
|
|
164
|
+
assert contracts[0].marketer == sample_contract_data["marketer"]
|
|
165
|
+
|
|
166
|
+
@pytest.mark.asyncio
|
|
167
|
+
async def test_contract_unique_constraint(
|
|
168
|
+
self, db_service, sample_supply_data, sample_contract_data
|
|
169
|
+
):
|
|
170
|
+
"""Test that contract unique constraint works (cups + date_start)."""
|
|
171
|
+
# Save supply first
|
|
172
|
+
await db_service.save_supply(sample_supply_data)
|
|
173
|
+
|
|
174
|
+
# Save first contract
|
|
175
|
+
await db_service.save_contract(sample_contract_data)
|
|
176
|
+
|
|
177
|
+
# Try to save contract with same cups + date_start but different data
|
|
178
|
+
updated_contract_data = sample_contract_data.copy()
|
|
179
|
+
updated_contract_data["marketer"] = "Different Marketer"
|
|
180
|
+
updated_contract_data["power_p1"] = 6.6
|
|
181
|
+
|
|
182
|
+
# This should update, not create new
|
|
183
|
+
await db_service.save_contract(updated_contract_data)
|
|
184
|
+
|
|
185
|
+
# Should still have only one contract, but with updated data
|
|
186
|
+
contracts = await db_service.get_contracts(sample_contract_data["cups"])
|
|
187
|
+
assert len(contracts) == 1
|
|
188
|
+
assert contracts[0].marketer == "Different Marketer"
|
|
189
|
+
assert contracts[0].power_p1 == 6.6
|
|
190
|
+
|
|
191
|
+
@pytest.mark.asyncio
|
|
192
|
+
async def test_save_and_get_consumption(
|
|
193
|
+
self, db_service, sample_supply_data, sample_consumption_data
|
|
194
|
+
):
|
|
195
|
+
"""Test saving and retrieving consumption data."""
|
|
196
|
+
# Save supply first
|
|
197
|
+
await db_service.save_supply(sample_supply_data)
|
|
198
|
+
|
|
199
|
+
# Save consumption
|
|
200
|
+
saved_consumption = await db_service.save_consumption(sample_consumption_data)
|
|
201
|
+
|
|
202
|
+
assert saved_consumption.cups == sample_consumption_data["cups"]
|
|
203
|
+
assert saved_consumption.value_kwh == sample_consumption_data["value_kwh"]
|
|
204
|
+
assert saved_consumption.real == sample_consumption_data["real"]
|
|
205
|
+
assert saved_consumption.id is not None
|
|
206
|
+
|
|
207
|
+
# Get consumptions
|
|
208
|
+
consumptions = await db_service.get_consumptions(
|
|
209
|
+
sample_consumption_data["cups"]
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
assert len(consumptions) == 1
|
|
213
|
+
assert consumptions[0].value_kwh == sample_consumption_data["value_kwh"]
|
|
214
|
+
|
|
215
|
+
@pytest.mark.asyncio
|
|
216
|
+
async def test_get_consumptions_with_date_filter(
|
|
217
|
+
self, db_service, sample_supply_data, sample_consumption_data
|
|
218
|
+
):
|
|
219
|
+
"""Test getting consumptions with date range filter."""
|
|
220
|
+
# Save supply first
|
|
221
|
+
await db_service.save_supply(sample_supply_data)
|
|
222
|
+
|
|
223
|
+
# Save multiple consumptions with different dates
|
|
224
|
+
consumption1 = sample_consumption_data.copy()
|
|
225
|
+
consumption1["datetime"] = datetime(2024, 6, 15, 10, 0)
|
|
226
|
+
|
|
227
|
+
consumption2 = sample_consumption_data.copy()
|
|
228
|
+
consumption2["datetime"] = datetime(2024, 6, 16, 10, 0)
|
|
229
|
+
|
|
230
|
+
consumption3 = sample_consumption_data.copy()
|
|
231
|
+
consumption3["datetime"] = datetime(2024, 6, 17, 10, 0)
|
|
232
|
+
|
|
233
|
+
await db_service.save_consumption(consumption1)
|
|
234
|
+
await db_service.save_consumption(consumption2)
|
|
235
|
+
await db_service.save_consumption(consumption3)
|
|
236
|
+
|
|
237
|
+
# Get consumptions with date filter
|
|
238
|
+
start_date = datetime(2024, 6, 15, 12, 0) # After first consumption
|
|
239
|
+
end_date = datetime(2024, 6, 16, 12, 0) # Before third consumption
|
|
240
|
+
|
|
241
|
+
filtered_consumptions = await db_service.get_consumptions(
|
|
242
|
+
cups=sample_consumption_data["cups"],
|
|
243
|
+
start_date=start_date,
|
|
244
|
+
end_date=end_date,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
# Should only get the second consumption
|
|
248
|
+
assert len(filtered_consumptions) == 1
|
|
249
|
+
assert filtered_consumptions[0].datetime == datetime(2024, 6, 16, 10, 0)
|
|
250
|
+
|
|
251
|
+
@pytest.mark.asyncio
|
|
252
|
+
async def test_save_and_get_maxpower(
|
|
253
|
+
self, db_service, sample_supply_data, sample_maxpower_data
|
|
254
|
+
):
|
|
255
|
+
"""Test saving and retrieving maxpower data."""
|
|
256
|
+
# Save supply first
|
|
257
|
+
await db_service.save_supply(sample_supply_data)
|
|
258
|
+
|
|
259
|
+
# Save maxpower
|
|
260
|
+
saved_maxpower = await db_service.save_maxpower(sample_maxpower_data)
|
|
261
|
+
|
|
262
|
+
assert saved_maxpower.cups == sample_maxpower_data["cups"]
|
|
263
|
+
assert saved_maxpower.value_kw == sample_maxpower_data["value_kw"]
|
|
264
|
+
assert saved_maxpower.id is not None
|
|
265
|
+
|
|
266
|
+
# Get maxpower readings
|
|
267
|
+
maxpower_readings = await db_service.get_maxpower_readings(
|
|
268
|
+
sample_maxpower_data["cups"]
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
assert len(maxpower_readings) == 1
|
|
272
|
+
assert maxpower_readings[0].value_kw == sample_maxpower_data["value_kw"]
|
|
273
|
+
|
|
274
|
+
@pytest.mark.asyncio
|
|
275
|
+
async def test_consumption_unique_constraint(
|
|
276
|
+
self, db_service, sample_supply_data, sample_consumption_data
|
|
277
|
+
):
|
|
278
|
+
"""Test that consumption unique constraint works (cups + datetime)."""
|
|
279
|
+
# Save supply first
|
|
280
|
+
await db_service.save_supply(sample_supply_data)
|
|
281
|
+
|
|
282
|
+
# Save first consumption
|
|
283
|
+
await db_service.save_consumption(sample_consumption_data)
|
|
284
|
+
|
|
285
|
+
# Try to save consumption with same cups + datetime but different value
|
|
286
|
+
updated_consumption = sample_consumption_data.copy()
|
|
287
|
+
updated_consumption["value_kwh"] = 1.5
|
|
288
|
+
|
|
289
|
+
# This should update, not create new
|
|
290
|
+
await db_service.save_consumption(updated_consumption)
|
|
291
|
+
|
|
292
|
+
# Should still have only one consumption, but with updated value
|
|
293
|
+
consumptions = await db_service.get_consumptions(
|
|
294
|
+
sample_consumption_data["cups"]
|
|
295
|
+
)
|
|
296
|
+
assert len(consumptions) == 1
|
|
297
|
+
assert consumptions[0].value_kwh == 1.5
|
|
298
|
+
|
|
299
|
+
@pytest.mark.asyncio
|
|
300
|
+
async def test_save_from_pydantic_models(self, db_service):
|
|
301
|
+
"""Test saving data from Pydantic models."""
|
|
302
|
+
cups = "ES1234567890123456789"
|
|
303
|
+
|
|
304
|
+
# Create Pydantic models
|
|
305
|
+
supply = SupplyModel(
|
|
306
|
+
cups=cups,
|
|
307
|
+
date_start=datetime(2024, 1, 1),
|
|
308
|
+
date_end=datetime(2024, 12, 31),
|
|
309
|
+
address="Test Address",
|
|
310
|
+
postal_code="12345",
|
|
311
|
+
province="Test Province",
|
|
312
|
+
municipality="Test Municipality",
|
|
313
|
+
distributor="Test Distributor",
|
|
314
|
+
point_type=5,
|
|
315
|
+
distributor_code="123",
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
contract = ContractModel(
|
|
319
|
+
cups=cups,
|
|
320
|
+
date_start=datetime(2024, 1, 1),
|
|
321
|
+
date_end=datetime(2024, 12, 31),
|
|
322
|
+
marketer="Test Marketer",
|
|
323
|
+
distributor_code="123",
|
|
324
|
+
power_p1=4.4,
|
|
325
|
+
power_p2=4.4,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
consumption = ConsumptionModel(
|
|
329
|
+
cups=cups, datetime=datetime(2024, 6, 15, 12, 0), delta_h=1.0, value_kwh=0.5
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
maxpower = MaxPowerModel(
|
|
333
|
+
cups=cups, datetime=datetime(2024, 6, 15, 15, 30), value_kw=3.2
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
# Save using the batch method
|
|
337
|
+
await db_service.save_from_pydantic_models(
|
|
338
|
+
cups=cups,
|
|
339
|
+
supplies=[supply],
|
|
340
|
+
contracts=[contract],
|
|
341
|
+
consumptions=[consumption],
|
|
342
|
+
maximeter=[maxpower],
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
# Verify data was saved
|
|
346
|
+
saved_supply = await db_service.get_supply(cups)
|
|
347
|
+
assert saved_supply is not None
|
|
348
|
+
assert saved_supply.cups == cups
|
|
349
|
+
|
|
350
|
+
saved_contracts = await db_service.get_contracts(cups)
|
|
351
|
+
assert len(saved_contracts) == 1
|
|
352
|
+
assert saved_contracts[0].marketer == "Test Marketer"
|
|
353
|
+
|
|
354
|
+
saved_consumptions = await db_service.get_consumptions(cups)
|
|
355
|
+
assert len(saved_consumptions) == 1
|
|
356
|
+
assert saved_consumptions[0].value_kwh == 0.5
|
|
357
|
+
|
|
358
|
+
saved_maxpower = await db_service.get_maxpower_readings(cups)
|
|
359
|
+
assert len(saved_maxpower) == 1
|
|
360
|
+
assert saved_maxpower[0].value_kw == 3.2
|
|
361
|
+
|
|
362
|
+
@pytest.mark.asyncio
|
|
363
|
+
async def test_database_relationships(
|
|
364
|
+
self, db_service, sample_supply_data, sample_contract_data
|
|
365
|
+
):
|
|
366
|
+
"""Test that database relationships work correctly."""
|
|
367
|
+
# Save supply and contract
|
|
368
|
+
await db_service.save_supply(sample_supply_data)
|
|
369
|
+
await db_service.save_contract(sample_contract_data)
|
|
370
|
+
|
|
371
|
+
# Get supply with relationships (this would work if we load with relationships)
|
|
372
|
+
supply = await db_service.get_supply(sample_supply_data["cups"])
|
|
373
|
+
assert supply is not None
|
|
374
|
+
assert supply.cups == sample_supply_data["cups"]
|
|
375
|
+
|
|
376
|
+
# Verify foreign key constraint works
|
|
377
|
+
contracts = await db_service.get_contracts(sample_supply_data["cups"])
|
|
378
|
+
assert len(contracts) == 1
|
|
379
|
+
assert contracts[0].cups == sample_supply_data["cups"]
|
|
380
|
+
|
|
381
|
+
@pytest.mark.asyncio
|
|
382
|
+
async def test_invalid_cups_foreign_key(self, db_service, sample_contract_data):
|
|
383
|
+
"""Test that foreign key constraint prevents orphaned records."""
|
|
384
|
+
# Try to save contract without supply (should fail or handle gracefully)
|
|
385
|
+
# Note: This depends on SQLite foreign key enforcement
|
|
386
|
+
try:
|
|
387
|
+
await db_service.save_contract(sample_contract_data)
|
|
388
|
+
# If it doesn't raise an error, verify the record wasn't actually saved
|
|
389
|
+
# or that the database handles it appropriately
|
|
390
|
+
except Exception:
|
|
391
|
+
# Expected if foreign key constraints are enforced
|
|
392
|
+
pass
|
|
393
|
+
|
|
394
|
+
@pytest.mark.asyncio
|
|
395
|
+
async def test_default_storage_dir(self):
|
|
396
|
+
"""Test that default storage directory is used when none provided."""
|
|
397
|
+
import tempfile
|
|
398
|
+
|
|
399
|
+
test_dir = tempfile.mkdtemp()
|
|
400
|
+
|
|
401
|
+
try:
|
|
402
|
+
# Reset the global singleton to allow testing default directory
|
|
403
|
+
import edata.services.database
|
|
404
|
+
|
|
405
|
+
edata.services.database._db_service = None
|
|
406
|
+
|
|
407
|
+
with patch("edata.services.database.DEFAULT_STORAGE_DIR", test_dir):
|
|
408
|
+
service = await get_database_service()
|
|
409
|
+
# Check that service was created with the correct directory
|
|
410
|
+
assert service._db_dir == test_dir
|
|
411
|
+
assert os.path.exists(service._db_dir)
|
|
412
|
+
finally:
|
|
413
|
+
# Clean up
|
|
414
|
+
if os.path.exists(test_dir):
|
|
415
|
+
shutil.rmtree(test_dir)
|