🚧 WIP
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
"""Unit tests for the sidecar resolver and the resolver chain (Sections 9-11)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime as dt
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from git_auto_tracker.authors.base import AuthorResult
|
||||
from git_auto_tracker.authors.resolver import ResolverChain
|
||||
from git_auto_tracker.authors.sidecar import SidecarMetadataResolver
|
||||
from git_auto_tracker.changes.models import Confidence
|
||||
from git_auto_tracker.config import AuthorResolutionConfig
|
||||
from git_auto_tracker.errors import ConfigurationError
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def resolver() -> SidecarMetadataResolver:
|
||||
return SidecarMetadataResolver()
|
||||
|
||||
|
||||
class TestSidecarResolver:
|
||||
def test_json_sidecar(self, tmp_path: Path, resolver: SidecarMetadataResolver) -> None:
|
||||
(tmp_path / "pump.xml").write_text("<x/>", encoding="utf-8")
|
||||
(tmp_path / "pump.xml.metadata").write_text(
|
||||
'{"last_modified_by": "DOMAIN\\\\jsmith",'
|
||||
' "name": "John Smith",'
|
||||
' "last_modified_at": "2026-09-11T14:31:22+02:00"}',
|
||||
encoding="utf-8",
|
||||
)
|
||||
result = resolver.resolve(tmp_path, "pump.xml")
|
||||
assert result.identifier == "DOMAIN\\jsmith"
|
||||
assert result.name == "John Smith"
|
||||
assert result.confidence is Confidence.PROBABLE
|
||||
expected = dt.datetime(
|
||||
2026, 9, 11, 14, 31, 22, tzinfo=dt.timezone(dt.timedelta(hours=2))
|
||||
)
|
||||
assert result.timestamp == expected
|
||||
|
||||
def test_keyvalue_sidecar(self, tmp_path: Path, resolver: SidecarMetadataResolver) -> None:
|
||||
(tmp_path / "pump.xml.metadata").write_text(
|
||||
"last_modified_by=DOMAIN\\adoe\n", encoding="utf-8"
|
||||
)
|
||||
result = resolver.resolve(tmp_path, "pump.xml")
|
||||
assert result.identifier == "DOMAIN\\adoe"
|
||||
|
||||
def test_missing_sidecar_is_unknown(
|
||||
self, tmp_path: Path, resolver: SidecarMetadataResolver
|
||||
) -> None:
|
||||
result = resolver.resolve(tmp_path, "absent.xml")
|
||||
assert result.identifier == "UNKNOWN"
|
||||
assert result.confidence is Confidence.UNKNOWN
|
||||
|
||||
def test_malformed_sidecar_is_unknown(
|
||||
self, tmp_path: Path, resolver: SidecarMetadataResolver
|
||||
) -> None:
|
||||
(tmp_path / "bad.xml.metadata").write_text("{not json", encoding="utf-8")
|
||||
result = resolver.resolve(tmp_path, "bad.xml")
|
||||
assert result.identifier == "UNKNOWN"
|
||||
|
||||
def test_sidecar_without_author_is_unknown(
|
||||
self, tmp_path: Path, resolver: SidecarMetadataResolver
|
||||
) -> None:
|
||||
(tmp_path / "partial.xml.metadata").write_text('{"revision": 7}', encoding="utf-8")
|
||||
result = resolver.resolve(tmp_path, "partial.xml")
|
||||
assert result.identifier == "UNKNOWN"
|
||||
|
||||
|
||||
class _FakeResolver:
|
||||
"""Test resolver with configurable results, registered under a name."""
|
||||
|
||||
def __init__(self, name: str, result: AuthorResult) -> None:
|
||||
self.name = name
|
||||
self._result = result
|
||||
self.calls = 0
|
||||
|
||||
def resolve(self, repository_root: Path, file_path: str) -> AuthorResult:
|
||||
self.calls += 1
|
||||
return self._result
|
||||
|
||||
|
||||
class TestResolverChain:
|
||||
def _chain(self, resolvers: list, minimum: str = "probable") -> ResolverChain:
|
||||
chain = ResolverChain(
|
||||
[r for r in resolvers],
|
||||
Confidence(minimum),
|
||||
AuthorResult.unknown("fallback"),
|
||||
)
|
||||
return chain
|
||||
|
||||
def test_first_usable_wins(self, tmp_path: Path) -> None:
|
||||
weak = _FakeResolver(
|
||||
"a", AuthorResult("DOMAIN\\weak", confidence=Confidence.WEAK, source="a")
|
||||
)
|
||||
strong = _FakeResolver(
|
||||
"b", AuthorResult("DOMAIN\\strong", confidence=Confidence.PROBABLE, source="b")
|
||||
)
|
||||
chain = self._chain([weak, strong])
|
||||
result = chain.resolve(tmp_path, "f.xml")
|
||||
assert result.identifier == "DOMAIN\\strong"
|
||||
assert weak.calls == 1
|
||||
assert strong.calls == 1 # weak skipped, strong consulted
|
||||
|
||||
def test_below_threshold_results_are_not_used(self, tmp_path: Path) -> None:
|
||||
weak = _FakeResolver(
|
||||
"a", AuthorResult("DOMAIN\\weak", confidence=Confidence.WEAK, source="a")
|
||||
)
|
||||
chain = self._chain([weak], minimum="probable")
|
||||
result = chain.resolve(tmp_path, "f.xml")
|
||||
assert result.identifier == "UNKNOWN" # fallback
|
||||
|
||||
def test_exception_in_resolver_continues_chain(self, tmp_path: Path) -> None:
|
||||
class _Boom:
|
||||
name = "boom"
|
||||
|
||||
def resolve(self, repository_root: Path, file_path: str) -> AuthorResult:
|
||||
raise RuntimeError("audit log unavailable")
|
||||
|
||||
good = _FakeResolver(
|
||||
"b", AuthorResult("DOMAIN\\ok", confidence=Confidence.AUTHORITATIVE, source="b")
|
||||
)
|
||||
chain = self._chain([_Boom(), good])
|
||||
result = chain.resolve(tmp_path, "f.xml")
|
||||
assert result.identifier == "DOMAIN\\ok"
|
||||
|
||||
def test_fallback_when_all_unknown(self, tmp_path: Path) -> None:
|
||||
unknown = _FakeResolver("a", AuthorResult.unknown("a"))
|
||||
chain = self._chain([unknown])
|
||||
result = chain.resolve(tmp_path, "f.xml")
|
||||
assert result.identifier == "UNKNOWN"
|
||||
|
||||
def test_unknown_resolver_name_rejected(self) -> None:
|
||||
config = AuthorResolutionConfig(resolvers=["nope"], minimum_confidence="probable")
|
||||
with pytest.raises(ConfigurationError):
|
||||
ResolverChain.from_config(config)
|
||||
|
||||
def test_sidecar_config_name(self) -> None:
|
||||
config = AuthorResolutionConfig(resolvers=["sidecar_metadata"])
|
||||
chain = ResolverChain.from_config(config)
|
||||
assert isinstance(chain._resolvers[0], SidecarMetadataResolver)
|
||||
Reference in New Issue
Block a user