-
Notifications
You must be signed in to change notification settings - Fork 94
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5ccdcd9
commit 0308a8b
Showing
1 changed file
with
184 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
import logging | ||
|
||
import numpy as np | ||
import pydantic | ||
import pytest | ||
from pydantic_core import ValidationError | ||
|
||
from calliope import config | ||
|
||
|
||
class TestUniqueList: | ||
@pytest.fixture(scope="module") | ||
def unique_list_model(self): | ||
return pydantic.create_model("Model", unique_list=(config.UniqueList, ...)) | ||
|
||
@pytest.fixture(scope="module") | ||
def unique_str_list_model(self): | ||
return pydantic.create_model("Model", unique_list=(config.UniqueList[str], ...)) | ||
|
||
@pytest.mark.parametrize( | ||
"valid_list", | ||
[[1, 2, 3], [1.0, 1.1, 1.2], ["1", "2", "3"], ["1", 1, "foo"], [None, np.nan]], | ||
) | ||
def test_unique_list(self, unique_list_model, valid_list): | ||
"When there's no fixed type for list entries, they just have to be unique _within_ types" | ||
model = unique_list_model(unique_list=valid_list) | ||
assert model.unique_list == valid_list | ||
|
||
@pytest.mark.parametrize("valid_list", [[1, 2, 3], ["1", "2", "3"], ["foo", "bar"]]) | ||
def test_unique_str_list(self, unique_list_model, valid_list): | ||
"When there's a fixed type for list entries, they have to be unique when coerced to that type" | ||
model = unique_list_model(unique_list=valid_list) | ||
assert model.unique_list == valid_list | ||
|
||
@pytest.mark.parametrize( | ||
"invalid_list", | ||
[[1, 1, 2], [1, 1.0], ["1", "foo", "foo"], [None, None], [1, True], [0, False]], | ||
) | ||
def test_not_unique_list(self, unique_list_model, invalid_list): | ||
"When there's no fixed type for list entries, duplicate entries of the _same_ type is not allowed (includes int == bool)" | ||
with pytest.raises(ValidationError, match="List must be unique"): | ||
unique_list_model(unique_list=invalid_list) | ||
|
||
@pytest.mark.parametrize( | ||
"invalid_list", [[1, 1, 2], ["foo", 1, "foo"], ["1", "foo", "foo"]] | ||
) | ||
def test_not_unique_str_list(self, unique_list_model, invalid_list): | ||
"When there's a fixed type for list entries, they have to be unique when coerced to that type" | ||
with pytest.raises(ValidationError, match="List must be unique"): | ||
unique_list_model(unique_list=invalid_list) | ||
|
||
|
||
class TestUpdate: | ||
@pytest.fixture(scope="module") | ||
def config_model_flat(self): | ||
return pydantic.create_model( | ||
"Model", | ||
__base__=config.ConfigBaseModel, | ||
model_config={"title": "TITLE"}, | ||
foo=(str, "bar"), | ||
foobar=(int, 1), | ||
) | ||
|
||
@pytest.fixture(scope="module") | ||
def config_model_nested(self, config_model_flat): | ||
return pydantic.create_model( | ||
"Model", | ||
__base__=config.ConfigBaseModel, | ||
model_config={"title": "TITLE 2"}, | ||
nested=(config_model_flat, config_model_flat()), | ||
top_level_foobar=(int, 10), | ||
) | ||
|
||
@pytest.fixture(scope="module") | ||
def config_model_double_nested(self, config_model_nested): | ||
return pydantic.create_model( | ||
"Model", | ||
__base__=config.ConfigBaseModel, | ||
model_config={"title": "TITLE 3"}, | ||
extra_nested=(config_model_nested, config_model_nested()), | ||
) | ||
|
||
@pytest.mark.parametrize( | ||
("to_update", "expected"), | ||
[ | ||
({"foo": "baz"}, {"foo": "baz", "foobar": 1}), | ||
({"foobar": 2}, {"foo": "bar", "foobar": 2}), | ||
({"foo": "baz", "foobar": 2}, {"foo": "baz", "foobar": 2}), | ||
], | ||
) | ||
def test_update_flat(self, config_model_flat, to_update, expected): | ||
model = config_model_flat() | ||
model_dict = model.model_dump() | ||
|
||
new_model = model.update(to_update) | ||
|
||
assert new_model.model_dump() == expected | ||
assert model.model_dump() == model_dict | ||
|
||
@pytest.mark.parametrize( | ||
("to_update", "expected"), | ||
[ | ||
( | ||
{"top_level_foobar": 20}, | ||
{"top_level_foobar": 20, "nested": {"foo": "bar", "foobar": 1}}, | ||
), | ||
( | ||
{"nested": {"foobar": 2}}, | ||
{"top_level_foobar": 10, "nested": {"foo": "bar", "foobar": 2}}, | ||
), | ||
( | ||
{"top_level_foobar": 20, "nested": {"foobar": 2}}, | ||
{"top_level_foobar": 20, "nested": {"foo": "bar", "foobar": 2}}, | ||
), | ||
( | ||
{"top_level_foobar": 20, "nested.foobar": 2}, | ||
{"top_level_foobar": 20, "nested": {"foo": "bar", "foobar": 2}}, | ||
), | ||
], | ||
) | ||
def test_update_nested(self, config_model_nested, to_update, expected): | ||
model = config_model_nested() | ||
model_dict = model.model_dump() | ||
|
||
new_model = model.update(to_update) | ||
|
||
assert new_model.model_dump() == expected | ||
assert model.model_dump() == model_dict | ||
|
||
@pytest.mark.parametrize( | ||
"to_update", | ||
[ | ||
{"extra_nested.nested.foobar": 2}, | ||
{"extra_nested": {"nested": {"foobar": 2}}}, | ||
], | ||
) | ||
def test_update_extra_nested(self, config_model_double_nested, to_update): | ||
model = config_model_double_nested() | ||
model_dict = model.model_dump() | ||
|
||
new_model = model.update(to_update) | ||
|
||
assert new_model.extra_nested.nested.foobar == 2 | ||
assert model.model_dump() == model_dict | ||
|
||
@pytest.mark.parametrize( | ||
"to_update", | ||
[ | ||
{"extra_nested.nested.foobar": "foo"}, | ||
{"extra_nested.top_level_foobar": "foo"}, | ||
], | ||
) | ||
def test_update_extra_nested_validation_error( | ||
self, config_model_double_nested, to_update | ||
): | ||
model = config_model_double_nested() | ||
|
||
with pytest.raises(ValidationError, match="1 validation error for TITLE"): | ||
model.update(to_update) | ||
|
||
@pytest.mark.parametrize( | ||
("to_update", "expected"), | ||
[ | ||
({"extra_nested.nested.foobar": 2}, ["Updating TITLE `foobar`: 1 -> 2"]), | ||
( | ||
{"extra_nested.top_level_foobar": 2}, | ||
["Updating TITLE 2 `top_level_foobar`: 10 -> 2"], | ||
), | ||
( | ||
{"extra_nested.nested.foobar": 2, "extra_nested.top_level_foobar": 3}, | ||
[ | ||
"Updating TITLE `foobar`: 1 -> 2", | ||
"Updating TITLE 2 `top_level_foobar`: 10 -> 3", | ||
], | ||
), | ||
], | ||
) | ||
def test_logging(self, caplog, config_model_double_nested, to_update, expected): | ||
caplog.set_level(logging.INFO) | ||
|
||
model = config_model_double_nested() | ||
model.update(to_update) | ||
|
||
assert all(log_text in caplog.text for log_text in expected) |