55d93894ac
feat(scripts): Add backfill script for content_hash in cache tables feat(scripts): Create recompute script for analysis_cache population test(tests): Implement comprehensive tests for analysis module functions fix(tests): Update CLI tests to assert errors on stderr instead of stdout fix(tests): Adjust MCP integration tests to pass context parameter correctly fix(tests): Modify service tests to return hash on save functions for consistency
188 lines
7.2 KiB
Python
188 lines
7.2 KiB
Python
"""Tests for the service layer (cache-aware fetching)."""
|
|
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from finn_eiendom.models import EiendomUnit, FinnAd, SimilarUnit
|
|
from finn_eiendom.service import (
|
|
get_or_fetch_ad,
|
|
get_or_fetch_eiendom_unit,
|
|
get_or_fetch_similar_units,
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_or_fetch_ad_uses_cache():
|
|
"""Test that get_or_fetch_ad returns cached ad without fetching."""
|
|
mock_ad = FinnAd(finnkode="123", url="http://example.com")
|
|
|
|
with (
|
|
patch("finn_eiendom.service.init_db"),
|
|
patch("finn_eiendom.service.get_finn_ad", return_value=mock_ad) as mock_get,
|
|
patch("finn_eiendom.service.fetch_ad_details") as mock_fetch,
|
|
):
|
|
result = await get_or_fetch_ad("123")
|
|
|
|
assert result.finnkode == "123"
|
|
mock_get.assert_called_once()
|
|
mock_fetch.assert_not_called()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_or_fetch_ad_fetches_when_cache_miss():
|
|
"""Test that get_or_fetch_ad fetches when cache is empty."""
|
|
mock_ad = FinnAd(finnkode="123", url="http://example.com")
|
|
|
|
with (
|
|
patch("finn_eiendom.service.init_db"),
|
|
patch("finn_eiendom.service.get_finn_ad", return_value=None),
|
|
patch("finn_eiendom.service.fetch_ad_details", return_value=mock_ad) as mock_fetch,
|
|
patch("finn_eiendom.service.save_finn_ad", return_value=("hash123", True)) as mock_save,
|
|
):
|
|
result = await get_or_fetch_ad("123")
|
|
|
|
assert result.finnkode == "123"
|
|
mock_fetch.assert_called_once_with("123")
|
|
mock_save.assert_called_once()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_or_fetch_ad_force_refresh():
|
|
"""Test that force_refresh=True bypasses cache."""
|
|
mock_ad = FinnAd(finnkode="123", url="http://example.com")
|
|
|
|
with (
|
|
patch("finn_eiendom.service.init_db"),
|
|
patch("finn_eiendom.service.get_finn_ad", return_value=mock_ad) as mock_get,
|
|
patch("finn_eiendom.service.fetch_ad_details", return_value=mock_ad) as mock_fetch,
|
|
patch("finn_eiendom.service.save_finn_ad", return_value=("hash123", True)) as mock_save,
|
|
):
|
|
result = await get_or_fetch_ad("123", force_refresh=True)
|
|
|
|
assert result.finnkode == "123"
|
|
mock_get.assert_not_called()
|
|
mock_fetch.assert_called_once_with("123")
|
|
mock_save.assert_called_once()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_or_fetch_eiendom_unit_uses_cache():
|
|
"""Test that get_or_fetch_eiendom_unit returns cached unit without fetching."""
|
|
mock_unit = EiendomUnit(unit_code="test-code")
|
|
|
|
with (
|
|
patch("finn_eiendom.service.init_db"),
|
|
patch("finn_eiendom.service.get_cached_eiendom_unit", return_value=mock_unit) as mock_get,
|
|
patch("finn_eiendom.service.get_unit") as mock_fetch,
|
|
):
|
|
result = await get_or_fetch_eiendom_unit("test-code")
|
|
|
|
assert result.unit_code == "test-code"
|
|
mock_get.assert_called_once()
|
|
mock_fetch.assert_not_called()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_or_fetch_eiendom_unit_fetches_when_cache_miss():
|
|
"""Test that get_or_fetch_eiendom_unit fetches when cache is empty."""
|
|
mock_unit = EiendomUnit(unit_code="test-code")
|
|
|
|
with (
|
|
patch("finn_eiendom.service.init_db"),
|
|
patch("finn_eiendom.service.get_cached_eiendom_unit", return_value=None),
|
|
patch("finn_eiendom.service.get_unit", return_value=mock_unit) as mock_fetch,
|
|
patch(
|
|
"finn_eiendom.service.save_eiendom_unit", return_value=("hash123", True)
|
|
) as mock_save,
|
|
):
|
|
result = await get_or_fetch_eiendom_unit("test-code")
|
|
|
|
assert result.unit_code == "test-code"
|
|
mock_fetch.assert_called_once_with("test-code")
|
|
mock_save.assert_called_once()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_or_fetch_similar_units_uses_cache():
|
|
"""Test that get_or_fetch_similar_units returns cached units without fetching."""
|
|
mock_unit = EiendomUnit(unit_code="test-code")
|
|
mock_similar = [SimilarUnit(unit_code="comp1"), SimilarUnit(unit_code="comp2")]
|
|
|
|
with (
|
|
patch("finn_eiendom.service.init_db"),
|
|
patch("finn_eiendom.service.get_or_fetch_eiendom_unit", return_value=mock_unit),
|
|
patch(
|
|
"finn_eiendom.service.get_cached_similar_units", return_value=mock_similar
|
|
) as mock_get,
|
|
patch("finn_eiendom.service.get_similar_units") as mock_fetch,
|
|
):
|
|
result = await get_or_fetch_similar_units("test-code", "RECENTLY_SOLD")
|
|
|
|
assert len(result) == 2
|
|
assert result[0].unit_code == "comp1"
|
|
mock_get.assert_called_once()
|
|
mock_fetch.assert_not_called()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_or_fetch_similar_units_fetches_when_cache_miss():
|
|
"""Test that get_or_fetch_similar_units fetches when cache is empty."""
|
|
mock_unit = EiendomUnit(unit_code="test-code")
|
|
mock_similar = [SimilarUnit(unit_code="comp1"), SimilarUnit(unit_code="comp2")]
|
|
|
|
with (
|
|
patch("finn_eiendom.service.init_db"),
|
|
patch("finn_eiendom.service.get_or_fetch_eiendom_unit", return_value=mock_unit),
|
|
patch("finn_eiendom.service.get_cached_similar_units", return_value=[]),
|
|
patch("finn_eiendom.service.build_unit_vector", return_value="vector_data"),
|
|
patch("finn_eiendom.service.get_similar_units", return_value=mock_similar) as mock_fetch,
|
|
patch(
|
|
"finn_eiendom.service.save_similar_units", return_value=("hash123", True)
|
|
) as mock_save,
|
|
):
|
|
result = await get_or_fetch_similar_units("test-code", "RECENTLY_SOLD")
|
|
|
|
assert len(result) == 2
|
|
assert result[0].unit_code == "comp1"
|
|
mock_fetch.assert_called_once_with("vector_data", listing_status="RECENTLY_SOLD")
|
|
mock_save.assert_called_once()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_or_fetch_similar_units_force_refresh():
|
|
"""Test that force_refresh=True bypasses cache."""
|
|
mock_unit = EiendomUnit(unit_code="test-code")
|
|
mock_similar = [SimilarUnit(unit_code="comp1"), SimilarUnit(unit_code="comp2")]
|
|
|
|
with (
|
|
patch("finn_eiendom.service.init_db"),
|
|
patch("finn_eiendom.service.get_or_fetch_eiendom_unit", return_value=mock_unit),
|
|
patch(
|
|
"finn_eiendom.service.get_cached_similar_units", return_value=mock_similar
|
|
) as mock_get,
|
|
patch("finn_eiendom.service.build_unit_vector", return_value="vector_data"),
|
|
patch("finn_eiendom.service.get_similar_units", return_value=mock_similar) as mock_fetch,
|
|
patch(
|
|
"finn_eiendom.service.save_similar_units", return_value=("hash123", True)
|
|
) as mock_save,
|
|
):
|
|
result = await get_or_fetch_similar_units("test-code", "RECENTLY_SOLD", force_refresh=True)
|
|
|
|
assert len(result) == 2
|
|
mock_get.assert_not_called()
|
|
mock_fetch.assert_called_once_with("vector_data", listing_status="RECENTLY_SOLD")
|
|
mock_save.assert_called_once()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_or_fetch_similar_units_handles_missing_unit():
|
|
"""Test that get_or_fetch_similar_units returns empty list when unit is missing."""
|
|
with (
|
|
patch("finn_eiendom.service.init_db"),
|
|
patch("finn_eiendom.service.get_or_fetch_eiendom_unit", return_value=None),
|
|
):
|
|
result = await get_or_fetch_similar_units("test-code", "RECENTLY_SOLD")
|
|
|
|
assert result == []
|