feat: resolver cache layer

This commit is contained in:
Oliver Hofmann 2026-05-16 17:20:17 +02:00
parent 08db8b8fb1
commit 7758eb21fa
2 changed files with 107 additions and 0 deletions

38
src/teampulse/resolver.py Normal file
View File

@ -0,0 +1,38 @@
import json
from pathlib import Path
from playwright.sync_api import Page
class Resolver:
def __init__(self, cache_path: Path, page: Page):
self._cache_path = cache_path
self._page = page
self._cache: dict[str, str] = self._load_cache()
def resolve(self, display_name: str) -> str:
if display_name in self._cache:
return self._cache[display_name]
email = self._extract_email_from_profile(display_name)
if email is None:
return "<nicht auflösbar>"
self._cache[display_name] = email
self._save_cache()
return email
def _load_cache(self) -> dict[str, str]:
if not self._cache_path.exists():
return {}
return json.loads(self._cache_path.read_text(encoding="utf-8"))
def _save_cache(self) -> None:
self._cache_path.write_text(
json.dumps(self._cache, ensure_ascii=False, indent=2),
encoding="utf-8",
)
def _extract_email_from_profile(self, display_name: str) -> str | None:
# Implemented in Task 8 — browser interaction with Teams profile cards
raise NotImplementedError

View File

@ -0,0 +1,69 @@
import json
from pathlib import Path
from unittest.mock import MagicMock
import pytest
from teampulse.resolver import Resolver
@pytest.fixture
def cache_path(tmp_path):
return tmp_path / "cache.json"
def test_resolve_returns_email_from_cache(cache_path):
cache_path.write_text(json.dumps({"Max Mustermann": "m.mustermann@company.com"}))
resolver = Resolver(cache_path=cache_path, page=MagicMock())
assert resolver.resolve("Max Mustermann") == "m.mustermann@company.com"
def test_resolve_unknown_name_calls_browser(cache_path):
cache_path.write_text(json.dumps({}))
mock_page = MagicMock()
resolver = Resolver(cache_path=cache_path, page=mock_page)
resolver._extract_email_from_profile = MagicMock(return_value="k.huber@company.com")
result = resolver.resolve("Klaus Huber")
resolver._extract_email_from_profile.assert_called_once_with("Klaus Huber")
assert result == "k.huber@company.com"
def test_resolve_caches_newly_resolved_email(cache_path):
cache_path.write_text(json.dumps({}))
resolver = Resolver(cache_path=cache_path, page=MagicMock())
resolver._extract_email_from_profile = MagicMock(return_value="k.huber@company.com")
resolver.resolve("Klaus Huber")
saved = json.loads(cache_path.read_text())
assert saved["Klaus Huber"] == "k.huber@company.com"
def test_resolve_unresolvable_returns_placeholder(cache_path):
cache_path.write_text(json.dumps({}))
resolver = Resolver(cache_path=cache_path, page=MagicMock())
resolver._extract_email_from_profile = MagicMock(return_value=None)
result = resolver.resolve("Unknown Person")
assert result == "<nicht auflösbar>"
def test_resolve_does_not_cache_unresolvable(cache_path):
cache_path.write_text(json.dumps({}))
resolver = Resolver(cache_path=cache_path, page=MagicMock())
resolver._extract_email_from_profile = MagicMock(return_value=None)
resolver.resolve("Unknown Person")
saved = json.loads(cache_path.read_text())
assert "Unknown Person" not in saved
def test_creates_cache_file_if_missing(tmp_path):
cache_path = tmp_path / "nonexistent.json"
resolver = Resolver(cache_path=cache_path, page=MagicMock())
resolver._extract_email_from_profile = MagicMock(return_value="a@b.com")
resolver.resolve("Test User")
assert cache_path.exists()