import cbor2
import pytest
from katzenpost_reticulum.pkimirror.cache import PkiCache
from katzenpost_reticulum.pkimirror.client import PkiMirrorClient, PkiResult
from katzenpost_reticulum.pkimirror.errors import (
PKIMIRROR_BAD_REQUEST,
PKIMIRROR_EPOCH_NOT_CACHED,
PKIMIRROR_OK,
PkiMirrorProtocolError,
encode_envelope,
)
class StubTransport:
def __init__(self) -> None:
self.calls: list[tuple[str, bytes]] = []
self._responses: dict[str, list[bytes]] = {}
self.closed = False
def queue(self, path: str, response: bytes) -> None:
self._responses.setdefault(path, []).append(response)
def request(self, path: str, data: bytes, timeout: float) -> bytes:
self.calls.append((path, data))
if not self._responses.get(path):
raise AssertionError(f"no canned response queued for {path}")
return self._responses[path].pop(0)
def close(self) -> None:
self.closed = True
def _make_client(transport: StubTransport) -> PkiMirrorClient:
return PkiMirrorClient(transport=transport)
def test_get_for_epoch_cache_hit_skips_network():
transport = StubTransport()
client = _make_client(transport)
client._cache.put(42, b"\xa1\x65Epoch\x18\x2a")
result = client.get_for_epoch(42)
assert isinstance(result, PkiResult)
assert result.code == PKIMIRROR_OK
assert result.epoch == 42
assert result.doc == b"\xa1\x65Epoch\x18\x2a"
assert result.stale is False
assert transport.calls == []
def test_get_for_epoch_cache_miss_fetches_then_caches():
transport = StubTransport()
transport.queue(
"/pki/epoch",
encode_envelope(PKIMIRROR_OK, epoch=7, doc=b"seven"),
)
client = _make_client(transport)
result = client.get_for_epoch(7)
assert result.code == PKIMIRROR_OK
assert result.doc == b"seven"
assert transport.calls == [("/pki/epoch", cbor2.dumps(7))]
result2 = client.get_for_epoch(7)
assert result2.doc == b"seven"
assert len(transport.calls) == 1
def test_get_for_epoch_use_cache_false_forces_fetch():
transport = StubTransport()
transport.queue(
"/pki/epoch",
encode_envelope(PKIMIRROR_OK, epoch=7, doc=b"seven-fresh"),
)
client = _make_client(transport)
client._cache.put(7, b"seven-stale")
result = client.get_for_epoch(7, use_cache=False)
assert result.doc == b"seven-fresh"
assert len(transport.calls) == 1
assert client._cache.get_for_epoch(7) == b"seven-fresh"
def test_get_for_epoch_error_response_not_cached():
transport = StubTransport()
transport.queue(
"/pki/epoch",
encode_envelope(
PKIMIRROR_EPOCH_NOT_CACHED, epoch=99, msg="epoch 7 not in cache"
),
)
client = _make_client(transport)
result = client.get_for_epoch(7)
assert result.code == PKIMIRROR_EPOCH_NOT_CACHED
assert result.epoch == 99
assert result.doc is None
assert client._cache.cached_epochs() == []
def test_get_current_skips_cache_by_default():
transport = StubTransport()
transport.queue(
"/pki/current",
encode_envelope(PKIMIRROR_OK, epoch=10, doc=b"latest"),
)
client = _make_client(transport)
client._cache.put(9, b"older")
result = client.get_current()
assert result.epoch == 10
assert result.doc == b"latest"
assert transport.calls == [("/pki/current", b"")]
assert client._cache.get_for_epoch(10) == b"latest"
def test_get_current_use_cache_returns_cached_when_present():
transport = StubTransport()
client = _make_client(transport)
client._cache.put(11, b"cached")
result = client.get_current(use_cache=True)
assert result.code == PKIMIRROR_OK
assert result.epoch == 11
assert result.doc == b"cached"
assert transport.calls == []
def test_get_current_use_cache_falls_through_when_empty():
transport = StubTransport()
transport.queue(
"/pki/current",
encode_envelope(PKIMIRROR_OK, epoch=1, doc=b"one"),
)
client = _make_client(transport)
result = client.get_current(use_cache=True)
assert result.doc == b"one"
assert len(transport.calls) == 1
def test_clear_cache_empties_local_store():
client = _make_client(StubTransport())
client._cache.put(1, b"one")
client._cache.put(2, b"two")
assert client.cached_epochs() == [1, 2]
client.clear_cache()
assert client.cached_epochs() == []
def test_protocol_error_on_malformed_response():
transport = StubTransport()
transport.queue("/pki/current", b"\xff\xff not cbor")
client = _make_client(transport)
with pytest.raises(PkiMirrorProtocolError):
client.get_current()
def test_protocol_error_on_unknown_code():
transport = StubTransport()
bad = encode_envelope(PKIMIRROR_OK, epoch=1, doc=b"x")
bad_dict = cbor2.loads(bad)
bad_dict["code"] = 999
transport.queue("/pki/current", cbor2.dumps(bad_dict))
client = _make_client(transport)
with pytest.raises(PkiMirrorProtocolError):
client.get_current()
def test_close_propagates_to_transport():
transport = StubTransport()
client = _make_client(transport)
client.close()
assert transport.closed is True