diff --git a/src/teampulse/resolver.py b/src/teampulse/resolver.py new file mode 100644 index 0000000..dfef0b9 --- /dev/null +++ b/src/teampulse/resolver.py @@ -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 "" + + 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 diff --git a/tests/test_resolver_cache.py b/tests/test_resolver_cache.py new file mode 100644 index 0000000..2ac37f9 --- /dev/null +++ b/tests/test_resolver_cache.py @@ -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 == "" + + +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()