Files
Presa-Mano/tests/test_recipe_mesh_cache.py
T

102 lines
3.3 KiB
Python

"""Unit tests for RecipeMeshCache."""
from __future__ import annotations
from unittest.mock import MagicMock
import numpy as np
import pytest
from presa_mano.clients.open3dmodel_cache import RecipeMesh, RecipeMeshCache
def _verts_tris():
verts = np.array([[0.,0.,0.],[1.,0.,0.],[0.,1.,0.]])
tris = np.array([[0,1,2]], dtype=np.int32)
return verts, tris
def _mock_client(stl_bytes_by_id: dict[str, tuple[bytes, str]]):
"""Mock client whose get_stl_bytes returns the registered (bytes, etag)
for each recipe_id, and counts calls."""
m = MagicMock()
m._call_count = {}
def _get(recipe_id: str):
m._call_count[recipe_id] = m._call_count.get(recipe_id, 0) + 1
return stl_bytes_by_id[recipe_id]
m.get_stl_bytes.side_effect = _get
return m
def test_cache_miss_then_hit(monkeypatch):
verts, tris = _verts_tris()
monkeypatch.setattr(
"presa_mano.clients.open3dmodel_cache.parse_stl_bytes",
lambda b: (verts, tris),
)
client = _mock_client({"r1": (b"STL1", "etag-A")})
cache = RecipeMeshCache(client=client, capacity=4)
m1 = cache.get("r1")
assert isinstance(m1, RecipeMesh)
np.testing.assert_array_equal(m1.vertices, verts)
np.testing.assert_array_equal(m1.triangles, tris)
assert m1.etag == "etag-A"
assert client._call_count["r1"] == 1
# secondo accesso: la cache valida l'etag (refetch dei bytes, sempre)
# ma evita il parse -> identity preserved sull'oggetto RecipeMesh.
m2 = cache.get("r1")
assert m2 is m1
assert client._call_count["r1"] == 2
def test_cache_etag_change_refetches(monkeypatch):
verts, tris = _verts_tris()
monkeypatch.setattr(
"presa_mano.clients.open3dmodel_cache.parse_stl_bytes",
lambda b: (verts, tris),
)
client = MagicMock()
seq = iter([(b"STL_OLD", "etag-A"), (b"STL_NEW", "etag-B")])
client.get_stl_bytes.side_effect = lambda rid: next(seq)
cache = RecipeMeshCache(client=client, capacity=4)
cache.get("r1")
m2 = cache.get("r1")
assert m2.etag == "etag-B"
assert client.get_stl_bytes.call_count == 2
def test_cache_lru_evicts_oldest(monkeypatch):
verts, tris = _verts_tris()
monkeypatch.setattr(
"presa_mano.clients.open3dmodel_cache.parse_stl_bytes",
lambda b: (verts, tris),
)
client = _mock_client({
f"r{i}": (f"S{i}".encode(), f"e{i}") for i in range(10)
})
cache = RecipeMeshCache(client=client, capacity=3)
cache.get("r1"); cache.get("r2"); cache.get("r3")
# r1 e' il piu' vecchio
cache.get("r4") # evict r1
cache.get("r1") # miss -> re-fetch (call_count va a 2)
assert client._call_count["r1"] == 2
def test_cache_get_uses_overrideable_client(monkeypatch):
"""`get(recipe_id, client=other)` permette di passare un client fresco
(utile quando PUT /api/config cambia base_url)."""
verts, tris = _verts_tris()
monkeypatch.setattr(
"presa_mano.clients.open3dmodel_cache.parse_stl_bytes",
lambda b: (verts, tris),
)
default_client = _mock_client({"r1": (b"S1", "e1")})
override_client = _mock_client({"r1": (b"S2", "e2")})
cache = RecipeMeshCache(client=default_client, capacity=2)
m = cache.get("r1", client=override_client)
assert m.etag == "e2"
assert default_client._call_count.get("r1", 0) == 0
assert override_client._call_count["r1"] == 1