diff --git a/kiwi_scp/config.py b/kiwi_scp/config.py index 44fc137..23c471e 100644 --- a/kiwi_scp/config.py +++ b/kiwi_scp/config.py @@ -48,7 +48,7 @@ class _Project(BaseModel): @root_validator(pre=True) @classmethod - def unify_project(cls, values): + def unify_project(cls, values) -> Dict[str, Any]: """parse different project notations""" if "name" in values: @@ -91,7 +91,7 @@ class Config(BaseModel): version: constr(regex=RE_SEMVER) = "0.2.0" - shells: Optional[List[Path]] = [ + shells: List[Path] = [ Path("/bin/bash"), ] @@ -152,9 +152,32 @@ class Config(BaseModel): return yml_string + @validator("shells", pre=True) + @classmethod + def unify_shells(cls, value) -> List[str]: + """parse different shells notations""" + + if value is None: + return [] + + elif isinstance(value, list): + return value + + elif isinstance(value, dict): + return list(value) + + else: + # any other format (try to coerce to str first) + try: + return [str(value)] + + except Exception as e: + # undefined format + raise ValueError("Invalid Shells Format") + @validator("projects", pre=True) @classmethod - def unify_projects(cls, value): + def unify_projects(cls, value) -> List[Dict[str, str]]: """parse different projects notations""" if value is None: @@ -173,8 +196,13 @@ class Config(BaseModel): result.append(entry) else: - # handle single project name - result.append({"name": str(entry)}) + try: + # handle single project name + result.append({"name": str(entry)}) + + except Exception as e: + # undefined format + raise ValueError("Invalid Projects Format") return result @@ -183,8 +211,14 @@ class Config(BaseModel): return [value] else: - # handle single project name - return [{"name": str(value)}] + # any other format (try to coerce to str first) + try: + # handle as a single project name + return [{"name": str(value)}] + + except Exception as e: + # undefined format + raise ValueError("Invalid Projects Format") @validator("environment", pre=True) @classmethod @@ -215,20 +249,20 @@ class Config(BaseModel): result: Dict[str, Optional[str]] = {} for item in value: - key, value = parse_str(str(item)) - result[key] = value + try: + key, value = parse_str(str(item)) + result[key] = value + + except Exception as e: + # undefined format + raise ValueError("Invalid Environment Format") return result - elif isinstance(value, str): - # string format (single variable): - # "=" - - key, value = parse_str(value) - return {key: value} - else: # any other format (try to coerce to str first) + # string format (single variable): + # "=" try: key, value = parse_str(str(value)) return {key: value} diff --git a/tests/test_config.py b/tests/test_config.py index 4571946..8acc3ec 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -8,6 +8,12 @@ from pydantic import ValidationError from kiwi_scp.config import Config +class UnCoercible: + """A class that doesn't have a string representation""" + def __str__(self): + raise ValueError + + def test_default(): c = Config() version = toml.load("./pyproject.toml")["tool"]["poetry"]["version"] @@ -22,6 +28,70 @@ def test_default(): assert c.network.cidr == IPv4Network("10.22.46.0/24") +######## +# SHELLS +######## + +def test_shells_empty(): + c = Config(shells=None) + + assert c == Config(shells=[]) + + assert c.shells == [] + + +def test_shells_list(): + c = Config(shells=["/bin/sh", "sh"]) + + assert len(c.shells) == 2 + assert c.shells[0] == Path("/bin/sh") + assert c.shells[1] == Path("sh") + + c = Config(shells=["/bin/bash"]) + + assert len(c.shells) == 1 + assert c.shells[0] == Path("/bin/bash") + + +def test_shells_dict(): + c = Config(shells={"/bin/bash": None}) + + assert len(c.shells) == 1 + assert c.shells[0] == Path("/bin/bash") + + +def test_shells_coercible(): + c = Config(shells="/bin/bash") + + assert c == Config(shells=Path("/bin/bash")) + + assert len(c.shells) == 1 + assert c.shells[0] == Path("/bin/bash") + + c = Config(shells=123) + + assert len(c.shells) == 1 + assert c.shells[0] == Path("123") + + +def test_shells_uncoercible(): + with pytest.raises(ValidationError) as exc_info: + Config(shells=UnCoercible()) + + assert len(exc_info.value.errors()) == 1 + error = exc_info.value.errors()[0] + assert error["msg"] == "Invalid Shells Format" + assert error["type"] == "value_error" + + with pytest.raises(ValidationError) as exc_info: + Config(shells=["/bin/bash", UnCoercible()]) + + assert len(exc_info.value.errors()) == 1 + error = exc_info.value.errors()[0] + assert error["msg"] == "value is not a valid path" + assert error["type"] == "type_error.path" + + ########## # PROJECTS ########## @@ -29,9 +99,7 @@ def test_default(): def test_proj_empty(): c = Config(projects=None) - assert c.projects == [] - - c = Config(projects=[]) + assert c == Config(projects=[]) assert c.projects == [] @@ -75,7 +143,7 @@ def test_proj_dict(): assert p.override_storage is None -def test_proj_name(): +def test_proj_coercible(): c = Config(projects="project") assert c == Config(projects=["project"]) @@ -87,6 +155,24 @@ def test_proj_name(): assert p.override_storage is None +def test_proj_uncoercible(): + with pytest.raises(ValidationError) as exc_info: + Config(projects=["valid", UnCoercible()]) + + assert len(exc_info.value.errors()) == 1 + error = exc_info.value.errors()[0] + assert error["msg"] == "Invalid Projects Format" + assert error["type"] == "value_error" + + with pytest.raises(ValidationError) as exc_info: + Config(projects=UnCoercible()) + + assert len(exc_info.value.errors()) == 1 + error = exc_info.value.errors()[0] + assert error["msg"] == "Invalid Projects Format" + assert error["type"] == "value_error" + + ############# # ENVIRONMENT ############# @@ -139,7 +225,7 @@ def test_env_list(): assert c.environment["123"] is None -def test_env_str(): +def test_env_coercible(): c = Config(environment="variable=value") assert len(c.environment) == 1 @@ -152,8 +238,6 @@ def test_env_str(): assert "variable" in c.environment assert c.environment["variable"] is None - -def test_env_coercible(): c = Config(environment=123) assert len(c.environment) == 1 @@ -167,11 +251,7 @@ def test_env_coercible(): assert c.environment["123.4"] is None -def test_env_undef(): - class UnCoercible: - def __str__(self): - raise ValueError - +def test_env_uncoercible(): with pytest.raises(ValidationError) as exc_info: Config(environment=UnCoercible()) @@ -179,3 +259,11 @@ def test_env_undef(): error = exc_info.value.errors()[0] assert error["msg"] == "Invalid Environment Format" assert error["type"] == "value_error" + + with pytest.raises(ValidationError) as exc_info: + Config(environment=["valid", UnCoercible()]) + + assert len(exc_info.value.errors()) == 1 + error = exc_info.value.errors()[0] + assert error["msg"] == "Invalid Environment Format" + assert error["type"] == "value_error"