From 73192230dc2907861a6de3e3bc29e40d5355dd90 Mon Sep 17 00:00:00 2001 From: Lars Kellogg-Stedman <lars@redhat.com> Date: Fri, 17 May 2024 15:10:31 -0400 Subject: [PATCH] Add validation for overlapping date ranges --- src/nerc_rates/models.py | 21 +++++++++++ src/nerc_rates/tests/test_rates.py | 60 +++++++++++++++++++++++------- 2 files changed, 68 insertions(+), 13 deletions(-) diff --git a/src/nerc_rates/models.py b/src/nerc_rates/models.py index ab03cbf..5f1cd86 100644 --- a/src/nerc_rates/models.py +++ b/src/nerc_rates/models.py @@ -35,6 +35,27 @@ class RateItem(Base): name: str history: list[RateValue] + @pydantic.model_validator(mode="after") + @classmethod + def validate_no_overlap(cls, data: Self): + for x in data.history: + for y in data.history: + if x is not y: + if ( + (x.date_until is None and y.date_until is None) + or ( + y.date_from >= x.date_from + and (x.date_until and y.date_from <= x.date_until) + ) + or ( + y.date_from <= x.date_from + and (y.date_until and y.date_until >= x.date_from) + ) + ): + raise ValueError("date ranges overlap") + + return data + RateItemDict = Annotated[ dict[str, RateItem], diff --git a/src/nerc_rates/tests/test_rates.py b/src/nerc_rates/tests/test_rates.py index 9a28d5b..87658ac 100644 --- a/src/nerc_rates/tests/test_rates.py +++ b/src/nerc_rates/tests/test_rates.py @@ -1,7 +1,7 @@ import pytest import requests_mock -from nerc_rates import load_from_url, rates +from nerc_rates import load_from_url, rates, models def test_load_from_url(): @@ -17,18 +17,52 @@ def test_load_from_url(): assert r.get_value_at("CPU SU Rate", "2023-06") == "0.013" -def test_invalid_dates(): - mock_response_text = """ - - name: CPU SU Rate - history: - - value: "0.013" - from: 2023-06 - until: 2023-04 - """ - with requests_mock.Mocker() as m: - m.get(rates.DEFAULT_URL, text=mock_response_text) - with pytest.raises(ValueError): - load_from_url() +def test_invalid_date_order(): + rate = ({"value": "1", "from": "2020-04", "until": "2020-03"},) + with pytest.raises(ValueError): + models.RateValue.model_validate(rate) + + +@pytest.mark.parametrize( + "rate", + [ + # Two values with no end date + { + "name": "Test Rate", + "history": [ + {"value": "1", "from": "2020-01"}, + {"value": "2", "from": "2020-03"}, + ], + }, + # Second value overlaps first value at end + { + "name": "Test Rate", + "history": [ + {"value": "1", "from": "2020-01", "until": "2020-04"}, + {"value": "2", "from": "2020-03"}, + ], + }, + # Second value overlaps first value at start + { + "name": "Test Rate", + "history": [ + {"value": "1", "from": "2020-04", "until": "2020-06"}, + {"value": "2", "from": "2020-03", "until": "2020-05"}, + ], + }, + # Second value is contained by first value + { + "name": "Test Rate", + "history": [ + {"value": "1", "from": "2020-01", "until": "2020-06"}, + {"value": "2", "from": "2020-03", "until": "2020-05"}, + ], + }, + ], +) +def test_invalid_date_overlap(rate): + with pytest.raises(ValueError): + models.RateItem.model_validate(rate) def test_rates_get_value_at():