test(core): add unit tests for untested core modules
Some checks failed
Test before pr merge / test-lint (pull_request) Failing after 13s
Test before pr merge / test (pull_request) Successful in 40s

Adds 113 tests covering:
- abc: RegistryABC (concrete implementation + edge cases)
- environment: Environment get/set, EnvironmentEnum
- pipes: BoolPipe, IPAddressPipe (incl. roundtrip + error cases)
- time: Cron (next(), intervals, invalid expression)
- utils: Cache (TTL, expiry, cleanup), get_value (incl. bug
  documentation: cast result not returned for string->typed values),
  JSONProcessor (nested objects, enums, defaults)
- property: classproperty (class access, instance access, subclass)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
clu
2026-04-13 18:40:01 +02:00
parent bcca7090d3
commit ca58f636ee
13 changed files with 605 additions and 0 deletions

View File

@@ -0,0 +1,125 @@
import time
import pytest
from cpl.core.utils.cache import Cache
def test_set_and_get():
cache = Cache()
cache.set("key1", "value1")
assert cache.get("key1") == "value1"
cache.stop()
def test_get_missing_key_returns_none():
cache = Cache()
assert cache.get("nonexistent") is None
cache.stop()
def test_has_existing_key():
cache = Cache()
cache.set("key", "val")
assert cache.has("key") is True
cache.stop()
def test_has_missing_key():
cache = Cache()
assert cache.has("missing") is False
cache.stop()
def test_delete():
cache = Cache()
cache.set("key", "val")
cache.delete("key")
assert cache.get("key") is None
assert cache.has("key") is False
cache.stop()
def test_clear():
cache = Cache()
cache.set("a", 1)
cache.set("b", 2)
cache.clear()
assert cache.get("a") is None
assert cache.get("b") is None
cache.stop()
def test_get_all():
cache = Cache()
cache.set("x", 10)
cache.set("y", 20)
values = cache.get_all()
assert set(values) == {10, 20}
cache.stop()
def test_get_all_empty():
cache = Cache()
assert cache.get_all() == []
cache.stop()
def test_ttl_expiry():
cache = Cache()
cache.set("temp", "data", ttl=1)
assert cache.get("temp") == "data"
time.sleep(1.1)
assert cache.get("temp") is None
cache.stop()
def test_has_after_ttl_expiry():
cache = Cache()
cache.set("temp", "data", ttl=1)
time.sleep(1.1)
assert cache.has("temp") is False
cache.stop()
def test_default_ttl():
cache = Cache(default_ttl=1)
cache.set("key", "value")
assert cache.get("key") == "value"
time.sleep(1.1)
assert cache.get("key") is None
cache.stop()
def test_per_key_ttl_overrides_default():
cache = Cache(default_ttl=60)
cache.set("short", "val", ttl=1)
time.sleep(1.1)
assert cache.get("short") is None
cache.stop()
def test_get_all_excludes_expired():
cache = Cache()
cache.set("permanent", "keep")
cache.set("temporary", "gone", ttl=1)
time.sleep(1.1)
values = cache.get_all()
assert "keep" in values
assert "gone" not in values
cache.stop()
def test_overwrite_key():
cache = Cache()
cache.set("key", "old")
cache.set("key", "new")
assert cache.get("key") == "new"
cache.stop()
def test_cleanup_removes_expired():
cache = Cache(cleanup_interval=9999)
cache.set("exp", "x", ttl=1)
time.sleep(1.1)
cache.cleanup()
assert cache.get("exp") is None
cache.stop()

View File

@@ -0,0 +1,64 @@
import pytest
from typing import List
from cpl.core.utils.get_value import get_value
def test_get_existing_str():
assert get_value({"key": "hello"}, "key", str) == "hello"
def test_get_existing_int_already_typed():
# Value already has the correct type -> returned directly
assert get_value({"count": 42}, "count", int) == 42
def test_get_existing_float_already_typed():
# Value already has the correct type -> returned directly
assert get_value({"pi": 3.14}, "pi", float) == 3.14
# NOTE: get_value calls cast() internally but does NOT return the cast result
# (the return value is lost). String "42" -> int returns None instead of 42.
# This is a known bug in get_value.
def test_get_str_to_int_returns_none_bug():
result = get_value({"count": "42"}, "count", int)
assert result is None # Bug: should be 42
def test_get_missing_key_returns_none():
assert get_value({}, "missing", str) is None
def test_get_missing_key_returns_default():
assert get_value({}, "missing", str, "fallback") == "fallback"
def test_get_value_already_correct_type():
assert get_value({"x": 5}, "x", int) == 5
def test_get_list_already_correct_type():
result = get_value({"items": [1, 2, 3]}, "items", List[int])
assert result == [1, 2, 3]
def test_get_list_of_str_already_correct_type():
result = get_value({"tags": ["a", "b"]}, "tags", List[str])
assert result == ["a", "b"]
def test_get_bool_already_typed():
# Value is already bool -> returned directly
assert get_value({"flag": True}, "flag", bool) is True
assert get_value({"flag": False}, "flag", bool) is False
# NOTE: Same bug string "true" -> bool returns None
def test_get_bool_from_str_returns_none_bug():
result = get_value({"flag": "true"}, "flag", bool)
assert result is None # Bug: should be True
def test_cast_failure_returns_default():
result = get_value({"val": "not_a_number"}, "val", int, -1)
assert result == -1

View File

@@ -0,0 +1,68 @@
import pytest
from enum import Enum
from cpl.core.utils.json_processor import JSONProcessor
class Color(Enum):
RED = "RED"
GREEN = "GREEN"
class Point:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
class Named:
def __init__(self, name: str, value: int = 0):
self.name = name
self.value = value
class Nested:
def __init__(self, point: Point, label: str):
self.point = point
self.label = label
class WithEnum:
def __init__(self, color: Color):
self.color = color
def test_simple_object():
obj = JSONProcessor.process(Point, {"X": 3, "Y": 7})
assert obj.x == 3
assert obj.y == 7
def test_camel_case_keys():
obj = JSONProcessor.process(Named, {"Name": "Alice", "Value": 42})
assert obj.name == "Alice"
assert obj.value == 42
def test_default_value_used():
obj = JSONProcessor.process(Named, {"Name": "Bob"})
assert obj.name == "Bob"
assert obj.value == 0
def test_none_for_missing_required():
obj = JSONProcessor.process(Named, {})
assert obj.name is None
def test_nested_object():
data = {"Point": {"X": 1, "Y": 2}, "Label": "origin"}
obj = JSONProcessor.process(Nested, data)
assert isinstance(obj.point, Point)
assert obj.point.x == 1
assert obj.point.y == 2
assert obj.label == "origin"
def test_enum_value():
obj = JSONProcessor.process(WithEnum, {"Color": "RED"})
assert obj.color == Color.RED