skyline-apiserver/skyline_apiserver/tests/unit/config/test_base.py
Michal Arbet eea81cef1e Fix skyline-apiserver on python3.11
Bump pydantic version which fixes skyline-apiserver
when running under python3.11.

Closes-Bug: #2047145
Change-Id: Idb1897f64ffaf0cd0fbee4f7f198935386a5f599
2024-04-08 17:11:16 +08:00

674 lines
23 KiB
Python

# Copyright 2021 99cloud
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from dataclasses import asdict
from pathlib import Path
from typing import Any, Dict, List, Optional, Sequence, Tuple, Type
import pytest
from _pytest.fixtures import SubRequest
from pydantic import StrictBool, StrictFloat, StrictInt, StrictStr
from pydantic.error_wrappers import ValidationError
from skyline_apiserver.config.base import Configuration, Group, Opt
from skyline_apiserver.tests.fake import FAKER, FakeOptData
from skyline_apiserver.tests.models import ArgumentData, TestData
class TestOpt:
@pytest.mark.ddt(
TestData(
arguments=("opt_data", "expected_schema_type"),
argument_data_set=[
ArgumentData(
id="bool_opt",
values=(asdict(FakeOptData(schema=StrictBool)), "boolean"),
),
ArgumentData(
id="int_opt",
values=(asdict(FakeOptData(schema=StrictInt)), "integer"),
),
ArgumentData(
id="float_opt",
values=(asdict(FakeOptData(schema=StrictFloat)), "number"),
),
ArgumentData(
id="str_opt",
values=(asdict(FakeOptData(schema=StrictStr)), "string"),
),
ArgumentData(
id="list_opt",
values=(asdict(FakeOptData(schema=List[StrictStr])), "array"),
),
ArgumentData(
id="dict_opt",
values=(asdict(FakeOptData(schema=Dict[StrictStr, StrictStr])), "object"),
),
],
),
)
def test_opt_init(self, opt_data: Dict[str, Any], expected_schema_type: str) -> None:
opt = Opt(**opt_data)
opt_value_schema = opt._schema_model.schema().get("properties", {}).get("value", {})
assert opt_value_schema.get("type") == expected_schema_type
@pytest.mark.ddt(
TestData(
arguments=("opt_data", "expected_exception"),
argument_data_set=[
ArgumentData(
id="missing_parameters",
values=({"name": FAKER.text.word()}, TypeError),
),
ArgumentData(
id="unknown_schema",
values=(
{
"name": FAKER.text.word(),
"description": FAKER.text.word(),
"schema": RuntimeError,
},
RuntimeError,
),
),
],
),
)
def test_opt_init_error(
self,
opt_data: Dict[str, Any],
expected_exception: Type[Exception],
) -> None:
with pytest.raises(expected_exception):
Opt(**opt_data)
@pytest.mark.ddt(
TestData(
arguments=("opt_data",),
argument_data_set=[
ArgumentData(
id="when_has_default",
values=(
asdict(
FakeOptData(schema=Optional[StrictStr], default=FAKER.text.word()),
),
),
),
ArgumentData(
id="when_no_default",
values=(asdict(FakeOptData(schema=Optional[StrictStr])),),
),
],
),
TestData(
arguments=("opt_value",),
argument_data_set=[
ArgumentData(id="load_value", values=(FAKER.text.word(),)),
ArgumentData(id="load_none", values=(None,)),
],
),
)
def test_opt_load(self, opt_data: Dict[str, Any], opt_value: Optional[str]) -> None:
opt = Opt(**opt_data)
opt.load(opt_value)
if opt_value is not None:
expected_result = opt_value
else:
expected_result = opt.default
assert opt.value == expected_result
@pytest.mark.ddt(
TestData(
arguments=("opt_data",),
argument_data_set=[
ArgumentData(
id="deprecated_warning",
values=(asdict(FakeOptData(schema=Optional[StrictStr], deprecated=True)),),
),
],
),
)
def test_opt_deprecated(self, opt_data: Dict[str, Any]) -> None:
opt = Opt(**opt_data)
expected_warn = DeprecationWarning
with pytest.warns(expected_warn):
opt.load(None)
@pytest.mark.ddt(
TestData(
arguments=("opt_data", "opt_value"),
argument_data_set=[
ArgumentData(
id="validation_error",
values=(
asdict(FakeOptData(schema=StrictStr)),
FAKER.numbers.integer_number(),
),
),
],
),
)
def test_opt_schema_validation(self, opt_data: Dict[str, Any], opt_value: int) -> None:
opt = Opt(**opt_data)
expected_exception = ValidationError
with pytest.raises(expected_exception):
opt.load(opt_value)
class TestGroup:
@pytest.fixture
def group_opts(self, request: SubRequest) -> Sequence[Opt]:
count: int = request.param
opts = []
for _ in range(count):
opt_data = asdict(
FakeOptData(schema=StrictStr, default=FAKER.text.word()),
)
opt = Opt(**opt_data)
opt.load(None)
opts.append(opt)
return opts
@pytest.mark.ddt(
TestData(
arguments=("group_name", "group_opts"),
indirect=("group_opts",),
argument_data_set=[
ArgumentData(id="empty_group", values=(FAKER.text.word(), 0)),
ArgumentData(
id="normal_group",
values=(FAKER.text.word(), FAKER.numbers.integer_number(1, 10)),
),
],
),
)
def test_group_init(self, group_name: str, group_opts: Sequence[Opt]) -> None:
group = Group(group_name, group_opts)
for opt in group_opts:
assert opt.value == getattr(group, opt.name, None)
@pytest.mark.ddt(
TestData(
arguments=("group_name", "group_opts"),
indirect=("group_opts",),
argument_data_set=[
ArgumentData(id="access_non-existent_opt", values=(FAKER.text.word(), 1)),
],
),
)
def test_group_access_error(self, group_name: str, group_opts: Sequence[Opt]) -> None:
group = Group(group_name, group_opts)
expected_exception = AttributeError
with pytest.raises(expected_exception):
getattr(group, f"{FAKER.text.word()}-test")
@pytest.mark.ddt(
TestData(
arguments=("group_name", "group_opts"),
indirect=("group_opts",),
argument_data_set=[
ArgumentData(
id="normal_group",
values=(FAKER.text.word(), FAKER.numbers.integer_number(1, 10)),
),
],
),
)
def test_group_like_collection(self, group_name: str, group_opts: Sequence[Opt]) -> None:
group = Group(group_name, group_opts)
for opt in group_opts:
assert opt.name in group
assert len(group) == len(group_opts)
opt_names = {opt.name for opt in group_opts}
for item in group:
assert item in opt_names
@pytest.mark.ddt(
TestData(
arguments=("group_name", "group_opts"),
indirect=("group_opts",),
argument_data_set=[
ArgumentData(
id="normal_group",
values=(FAKER.text.word(), FAKER.numbers.integer_number(1, 10)),
),
],
),
)
def test_group_repr(self, group_name: str, group_opts: Sequence[Opt]) -> None:
group = Group(group_name, group_opts)
opt_template = "{}=Opt(name='{}')"
for opt in group_opts:
opt_str = opt_template.format(opt.name, opt.name)
assert opt_str in repr(group)
@pytest.mark.ddt(
TestData(
arguments=("group_name", "group_opts"),
indirect=("group_opts",),
argument_data_set=[
ArgumentData(
id="normal_group",
values=(FAKER.text.word(), FAKER.numbers.integer_number(1, 10)),
),
],
),
)
def test_group_keys(self, group_name: str, group_opts: Sequence[Opt]) -> None:
group = Group(group_name, group_opts)
opt_names = {opt.name for opt in group_opts}
for item in group.keys():
assert item in opt_names
@pytest.mark.ddt(
TestData(
arguments=("group_name", "group_opts"),
indirect=("group_opts",),
argument_data_set=[
ArgumentData(
id="normal_group",
values=(FAKER.text.word(), FAKER.numbers.integer_number(1, 10)),
),
],
),
)
def test_group_values(self, group_name: str, group_opts: Sequence[Opt]) -> None:
group = Group(group_name, group_opts)
opts = {opt for opt in group_opts}
opt_ids = {id(opt) for opt in group_opts}
for item in group.values():
assert item in opts
assert id(item) in opt_ids
@pytest.mark.ddt(
TestData(
arguments=("group_name", "group_opts"),
indirect=("group_opts",),
argument_data_set=[
ArgumentData(
id="normal_group",
values=(FAKER.text.word(), FAKER.numbers.integer_number(1, 10)),
),
],
),
)
def test_group_items(self, group_name: str, group_opts: Sequence[Opt]) -> None:
group = Group(group_name, group_opts)
opt_names = {opt.name for opt in group_opts}
opts = {opt for opt in group_opts}
opt_ids = {id(opt) for opt in group_opts}
for name, item in group.items():
assert name in opt_names
assert item in opts
assert id(item) in opt_ids
class TestConfiguration:
@pytest.fixture
def config_groups(self, request: SubRequest) -> Sequence[Group]:
count: int = request.param
groups = []
for _ in range(count):
opts = []
for __ in range(FAKER.numbers.integer_number(1, 10)):
opt_data = asdict(
FakeOptData(schema=StrictStr, default=FAKER.text.word()),
)
opt = Opt(**opt_data)
opt.load(None)
opts.append(opt)
group = Group(FAKER.text.word(), opts)
groups.append(group)
return groups
@pytest.fixture
def config_setup_params(
self,
request: SubRequest,
tmp_path: Path,
) -> Tuple[str, Dict[str, str]]:
project: str = request.param.get("project", "")
env: Dict[str, str] = request.param.get("env", "")
env["OS_CONFIG_DIR"] = tmp_path.as_posix()
tmp_path.joinpath(f"{project}.yaml").write_text("{}")
return (project, env)
@pytest.mark.ddt(
TestData(
arguments=("config_groups",),
indirect=("config_groups",),
argument_data_set=[
ArgumentData(id="empty_config", values=(0,)),
ArgumentData(
id="normal_config",
values=(FAKER.numbers.integer_number(1, 10),),
),
],
),
)
def test_configuration_init(self, config_groups: Sequence[Group]) -> None:
config = Configuration(config_groups)
for group in config_groups:
assert group is getattr(config, group.name, None)
assert id(group) == id(getattr(config, group.name, None))
@pytest.mark.ddt(
TestData(
arguments=("config_groups",),
indirect=("config_groups",),
argument_data_set=[
ArgumentData(
id="access_non-existent_group",
values=(1,),
),
],
),
)
def test_configuration_access_error(self, config_groups: Sequence[Group]) -> None:
config = Configuration(config_groups)
expected_exception = AttributeError
with pytest.raises(expected_exception):
getattr(config, f"{FAKER.text.word()}-test")
@pytest.mark.ddt(
TestData(
arguments=(
"project",
"env",
"expected_config_path",
),
argument_data_set=[
ArgumentData(
id="set_env_config_dir",
values=(
"fake_project_name",
{"OS_CONFIG_DIR": "env_config_dir"},
("env_config_dir", "env_config_dir/fake_project_name.yaml"),
),
),
ArgumentData(
id="no_set_env",
values=(
"fake_project_name",
{},
(
"/etc/fake_project_name",
"/etc/fake_project_name/fake_project_name.yaml",
),
),
),
],
),
)
def test_configuration_get_config_path(
self,
project: str,
env: Dict[str, str],
expected_config_path: Tuple[str, str],
) -> None:
assert Configuration.get_config_path(project, env) == expected_config_path
@pytest.mark.ddt(
TestData(
arguments=("config_setup_params",),
indirect=("config_setup_params",),
argument_data_set=[
ArgumentData(
id="set_env_config_dir",
values=(
{
"project": "fake_project_name",
"env": {"OS_CONFIG_DIR": ""},
},
),
),
],
),
)
def test_configuration_setup(self, config_setup_params: Tuple[str, Dict[str, str]]) -> None:
groups = []
for _ in range(FAKER.numbers.integer_number(1, 10)):
opts = []
for __ in range(FAKER.numbers.integer_number(1, 10)):
opt_data = asdict(
FakeOptData(schema=StrictStr, default=FAKER.text.word()),
)
opts.append(Opt(**opt_data))
groups.append(Group(FAKER.text.word(), opts))
config = Configuration(groups)
project = config_setup_params[0]
env = config_setup_params[1]
config.setup(project, env)
for group in config:
for opt in getattr(config, group):
opt_value = getattr(getattr(config, group, None), opt)
assert isinstance(opt_value, str)
@pytest.mark.ddt(
TestData(
arguments=("config_setup_params",),
indirect=("config_setup_params",),
argument_data_set=[
ArgumentData(
id="not_found_config_file",
values=(
{
"project": "fake_project_name",
"env": {"OS_CONFIG_DIR": ""},
},
),
),
],
),
)
def test_configuration_setup_non_existent_error(
self,
config_setup_params: Tuple[str, Dict[str, str]],
) -> None:
groups = []
for _ in range(FAKER.numbers.integer_number(1, 10)):
opts = []
for __ in range(FAKER.numbers.integer_number(1, 10)):
opt_data = asdict(
FakeOptData(schema=StrictStr, default=FAKER.text.word()),
)
opts.append(Opt(**opt_data))
groups.append(Group(FAKER.text.word(), opts))
config = Configuration(groups)
project = config_setup_params[0]
env = config_setup_params[1]
config_dir_path, config_file_path = config.get_config_path(project, env)
Path(config_file_path).unlink(missing_ok=True)
expected_exception = ValueError
with pytest.raises(expected_exception, match="Not found config file"):
config.setup(project, env)
@pytest.mark.ddt(
TestData(
arguments=("config_setup_params",),
indirect=("config_setup_params",),
argument_data_set=[
ArgumentData(
id="file_is_not_yaml",
values=(
{
"project": "fake_project_name",
"env": {"OS_CONFIG_DIR": ""},
},
),
),
],
),
)
def test_configuration_setup_yaml_format_error(
self,
config_setup_params: Tuple[str, Dict[str, str]],
) -> None:
groups = []
for _ in range(FAKER.numbers.integer_number(1, 10)):
opts = []
for __ in range(FAKER.numbers.integer_number(1, 10)):
opt_data = asdict(
FakeOptData(schema=StrictStr, default=FAKER.text.word()),
)
opts.append(Opt(**opt_data))
groups.append(Group(FAKER.text.word(), opts))
config = Configuration(groups)
project = config_setup_params[0]
env = config_setup_params[1]
config_dir_path, config_file_path = config.get_config_path(project, env)
Path(config_file_path).write_text("{")
expected_exception = ValueError
with pytest.raises(expected_exception, match="Load config file error"):
config.setup(project, env)
@pytest.mark.ddt(
TestData(
arguments=("config_groups",),
indirect=("config_groups",),
argument_data_set=[
ArgumentData(
id="normal_config",
values=(FAKER.numbers.integer_number(1, 10),),
),
],
),
)
def test_configuration_cleanup(self, config_groups: Sequence[Group]) -> None:
config = Configuration(config_groups)
assert len(config) == len(config_groups)
config.cleanup()
assert len(config) == 0
@pytest.mark.ddt(
TestData(
arguments=("config_groups",),
indirect=("config_groups",),
argument_data_set=[
ArgumentData(
id="normal_config",
values=(FAKER.numbers.integer_number(1, 10),),
),
],
),
)
def test_configuration_call(self, config_groups: Sequence[Group]) -> None:
config = Configuration()
config(config_groups)
for group in config_groups:
assert group is getattr(config, group.name, None)
assert id(group) == id(getattr(config, group.name, None))
@pytest.mark.ddt(
TestData(
arguments=("config_groups",),
indirect=("config_groups",),
argument_data_set=[
ArgumentData(
id="normal_config",
values=(FAKER.numbers.integer_number(1, 10),),
),
],
),
)
def test_configuration_like_collection(self, config_groups: Sequence[Group]) -> None:
config = Configuration(config_groups)
for group in config_groups:
assert group.name in config
assert len(config) == len(config_groups)
group_names = {group.name for group in config_groups}
for item in config:
assert item in group_names
@pytest.mark.ddt(
TestData(
arguments=("config_groups",),
indirect=("config_groups",),
argument_data_set=[
ArgumentData(
id="normal_config",
values=(FAKER.numbers.integer_number(1, 10),),
),
],
),
)
def test_configuration_repr(self, config_groups: Sequence[Group]) -> None:
config = Configuration(config_groups)
group_template = "{}=Group(name='{}')"
for group in config_groups:
group_str = group_template.format(group.name, group.name)
assert group_str in repr(config)
@pytest.mark.ddt(
TestData(
arguments=("config_groups",),
indirect=("config_groups",),
argument_data_set=[
ArgumentData(
id="normal_config",
values=(FAKER.numbers.integer_number(1, 10),),
),
],
),
)
def test_configuration_keys(self, config_groups: Sequence[Group]) -> None:
config = Configuration(config_groups)
group_names = {group.name for group in config_groups}
for item in config.keys():
assert item in group_names
@pytest.mark.ddt(
TestData(
arguments=("config_groups",),
indirect=("config_groups",),
argument_data_set=[
ArgumentData(
id="normal_config",
values=(FAKER.numbers.integer_number(1, 10),),
),
],
),
)
def test_configuration_values(self, config_groups: Sequence[Group]) -> None:
config = Configuration(config_groups)
groups = {group for group in config_groups}
group_ids = {id(group) for group in config_groups}
for item in config.values():
assert item in groups
assert id(item) in group_ids
@pytest.mark.ddt(
TestData(
arguments=("config_groups",),
indirect=("config_groups",),
argument_data_set=[
ArgumentData(
id="normal_config",
values=(FAKER.numbers.integer_number(1, 10),),
),
],
),
)
def test_configuration_items(self, config_groups: Sequence[Group]) -> None:
config = Configuration(config_groups)
group_names = {group.name for group in config_groups}
groups = {group for group in config_groups}
group_ids = {id(group) for group in config_groups}
for name, item in config.items():
assert name in group_names
assert item in groups
assert id(item) in group_ids