# Copyright (c) 2026 Patrick Motsch # All rights reserved. """Unit tests for ``RedmineStatsCache``. Verifies TTL expiry, key composition, instance invalidation and process-wide singleton behaviour. """ from __future__ import annotations import time from modules.features.redmine.serviceRedmineStatsCache import ( RedmineStatsCache, _getStatsCache, ) class TestRedmineStatsCache: def test_getReturnsNoneOnMiss(self) -> None: c = RedmineStatsCache(ttlSeconds=60) key = c.buildKey("inst-a", "2026-01-01", "2026-01-31", "week", [1, 2]) assert c.get(key) is None def test_setAndGetRoundTrip(self) -> None: c = RedmineStatsCache(ttlSeconds=60) key = c.buildKey("inst-a", None, None, "week", []) c.set(key, {"answer": 42}) assert c.get(key) == {"answer": 42} def test_keyIsOrderInsensitiveForTrackerIds(self) -> None: c = RedmineStatsCache() k1 = c.buildKey("inst-a", None, None, "week", [3, 1, 2]) k2 = c.buildKey("inst-a", None, None, "week", [1, 2, 3]) assert k1 == k2 def test_ttlExpiry(self) -> None: c = RedmineStatsCache(ttlSeconds=0.05) key = c.buildKey("inst-a", None, None, "week", []) c.set(key, "value") time.sleep(0.06) assert c.get(key) is None def test_invalidateInstanceDropsAllKeysForThatInstance(self) -> None: c = RedmineStatsCache(ttlSeconds=60) c.set(c.buildKey("inst-a", None, None, "week", []), "v1") c.set(c.buildKey("inst-a", "2026-01-01", "2026-01-31", "month", [1]), "v2") c.set(c.buildKey("inst-b", None, None, "week", []), "v3") dropped = c.invalidateInstance("inst-a") assert dropped == 2 assert c.get(c.buildKey("inst-a", None, None, "week", [])) is None assert c.get(c.buildKey("inst-b", None, None, "week", [])) == "v3" def test_singletonIsStable(self) -> None: a = _getStatsCache() b = _getStatsCache() assert a is b