From e8341e5f3993f7a8c778b15660527b258560983f Mon Sep 17 00:00:00 2001 From: Mohamed Attia Date: Thu, 15 Feb 2024 09:12:51 +0100 Subject: [PATCH] Tests. --- src/enlyze/api_clients/timeseries/models.py | 2 +- src/enlyze/client.py | 11 ++- .../api_clients/timeseries/test_models.py | 76 +++++++++++++++++++ tests/enlyze/test_client.py | 74 ++++++++++++++++++ 4 files changed, 158 insertions(+), 5 deletions(-) create mode 100644 tests/enlyze/api_clients/timeseries/test_models.py diff --git a/src/enlyze/api_clients/timeseries/models.py b/src/enlyze/api_clients/timeseries/models.py index 84996be..59531db 100644 --- a/src/enlyze/api_clients/timeseries/models.py +++ b/src/enlyze/api_clients/timeseries/models.py @@ -84,7 +84,7 @@ def merge(self, other: "TimeseriesData") -> "TimeseriesData": f" with {slen} records." ) - self.columns.extend(other.columns) + self.columns.extend(other.columns[1:]) for s, o in zip(self.records, other.records): if s[0] != o[0]: diff --git a/src/enlyze/client.py b/src/enlyze/client.py index 243a6f3..59d518f 100644 --- a/src/enlyze/client.py +++ b/src/enlyze/client.py @@ -59,7 +59,7 @@ def _get_variables_sequence_and_query_parameter_list( validate_resampling_interval(resampling_interval) variables_sequence = [] variables_query_parameter_list = [] - for variable, resampling_method in variables.items(): + for variable, resampling_method in variables.items(): # type: ignore variables_sequence.append(variable) variables_query_parameter_list.append( f"{variable.uuid}" @@ -72,7 +72,7 @@ def _get_variables_sequence_and_query_parameter_list( ) return variables_sequence, variables_query_parameter_list - return variables, [str(v.uuid) for v in variables] + return variables, [str(v.uuid) for v in variables] # type: ignore class EnlyzeClient: @@ -254,9 +254,12 @@ def _get_timeseries( if not timeseries_data_chunked or None in timeseries_data_chunked: return None - timeseries_data = reduce(lambda x, y: x.merge(y), timeseries_data_chunked) + try: + timeseries_data = reduce(lambda x, y: x.merge(y), timeseries_data_chunked) # type: ignore # noqa + except ValueError as e: + raise EnlyzeError from e - return timeseries_data.to_user_model( + return timeseries_data.to_user_model( # type: ignore start=start, end=end, variables=variables_sequence, diff --git a/tests/enlyze/api_clients/timeseries/test_models.py b/tests/enlyze/api_clients/timeseries/test_models.py new file mode 100644 index 0000000..59ddd26 --- /dev/null +++ b/tests/enlyze/api_clients/timeseries/test_models.py @@ -0,0 +1,76 @@ +from datetime import datetime, timedelta, timezone + +import pytest + +from enlyze.api_clients.timeseries.models import TimeseriesData + + +@pytest.fixture +def timestamp(): + return datetime.now(tz=timezone.utc) + + +@pytest.fixture +def timeseries_data_1(timestamp): + return TimeseriesData( + columns=["time", "var1", "var2"], + records=[ + [timestamp.isoformat(), 1, 2], + [(timestamp - timedelta(minutes=10)).isoformat(), 3, 4], + ], + ) + + +@pytest.fixture +def timeseries_data_2(timestamp): + return TimeseriesData( + columns=["time", "var3"], + records=[ + [timestamp.isoformat(), 5], + [(timestamp - timedelta(minutes=10)).isoformat(), 6], + ], + ) + + +class TestTimeseriesData: + def test_merge(self, timeseries_data_1, timeseries_data_2): + timeseries_data_1_records_len = len(timeseries_data_1.records) + timeseries_data_1_columns_len = len(timeseries_data_1.columns) + timeseries_data_2_records_len = len(timeseries_data_2.records) + timeseries_data_2_columns_len = len(timeseries_data_2.columns) + expected_merged_record_len = len(timeseries_data_1.records[0]) + len( + timeseries_data_2.records[0][1:] + ) + + merged = timeseries_data_1.merge(timeseries_data_2) + + assert merged is timeseries_data_1 + assert ( + len(merged.records) + == timeseries_data_1_records_len + == timeseries_data_2_records_len + ) + assert ( + len(merged.columns) + == timeseries_data_1_columns_len + timeseries_data_2_columns_len - 1 + ) + + for r in merged.records: + assert len(r) == expected_merged_record_len + + def test_merge_raises_number_of_records_mismatch( + self, timeseries_data_1, timeseries_data_2 + ): + timeseries_data_2.records = timeseries_data_2.records[1:] + with pytest.raises( + ValueError, match="Number of records in both instances has to be the same" + ): + timeseries_data_1.merge(timeseries_data_2) + + def test_merge_raises_mismatched_timestamps( + self, timeseries_data_1, timeseries_data_2, timestamp + ): + timeseries_data_2.records[0][0] = (timestamp - timedelta(days=1)).isoformat() + + with pytest.raises(ValueError, match="mismatched timestamps"): + timeseries_data_1.merge(timeseries_data_2) diff --git a/tests/enlyze/test_client.py b/tests/enlyze/test_client.py index 09c50eb..733aa42 100644 --- a/tests/enlyze/test_client.py +++ b/tests/enlyze/test_client.py @@ -390,6 +390,80 @@ def test_get_timeseries_raises_api_returned_no_timestamps( client.get_timeseries(start_datetime, end_datetime, [variable]) +@given( + variable=st.builds(user_models.Variable), +) +@settings(suppress_health_check=[HealthCheck.function_scoped_fixture]) +def test__get_timeseries_raises_variables_without_resampling_method( + start_datetime, end_datetime, variable +): + """ + Test that `get_timeseries` will raise an `EnlyzeError` when a + `resampling_interval` is specified but variables don't have + resampling methods. + """ + client = make_client() + with pytest.raises(EnlyzeError) as exc_info: + client._get_timeseries(start_datetime, end_datetime, [variable], 30) + assert isinstance(exc_info.value.__cause__, ValueError) + + +@given( + variable=st.builds(user_models.Variable), +) +@settings(suppress_health_check=[HealthCheck.function_scoped_fixture]) +def test__get_timeseries_raises_on_chunk_value_error( + start_datetime, end_datetime, variable, monkeypatch +): + monkeypatch.setattr( + "enlyze.client.MAXIMUM_NUMBER_OF_VARIABLES_PER_TIMESERIES_REQUEST", 0 + ) + client = make_client() + with pytest.raises(EnlyzeError) as exc_info: + client._get_timeseries(start_datetime, end_datetime, [variable]) + assert isinstance(exc_info.value.__cause__, ValueError) + + +@given( + start=datetime_before_today_strategy, + end=datetime_today_until_now_strategy, + variable=st.builds( + user_models.Variable, + data_type=st.just("INTEGER"), + machine=st.builds(timeseries_api_models.Machine), + ), + records=st.lists( + st.tuples( + datetime_today_until_now_strategy.map(datetime.isoformat), + st.integers(), + ), + min_size=2, + ), +) +@settings(suppress_health_check=[HealthCheck.function_scoped_fixture]) +def test__get_timeseries_raises_on_merge_value_error( + start, end, variable, records, monkeypatch +): + client = make_client() + + def f(*args, **kwargs): + raise ValueError + + monkeypatch.setattr("enlyze.client.reduce", f) + + with respx_mock_with_base_url(TIMESERIES_API_SUB_PATH) as mock: + mock.get("timeseries").mock( + PaginatedTimeseriesApiResponse( + data=timeseries_api_models.TimeseriesData( + columns=["time", str(variable.uuid)], + records=records, + ).model_dump() + ) + ) + with pytest.raises(EnlyzeError): + client._get_timeseries(start, end, [variable]) + + @given( production_order=st.just(PRODUCTION_ORDER), product=st.one_of(