wrongmove/tests/unit/test_floorplan_detector.py
Viktor Barzin eafbc1ac52
Flatten repo structure: move crawler/ to root, remove vqa/ and immoweb/
The crawler subdirectory was the only active project. Moving it to the
repo root simplifies paths and removes the unnecessary nesting. The
vqa/ and immoweb/ directories were legacy/unused and have been removed.

Updated .drone.yml, .gitignore, .claude/ docs, and skills to reflect
the new flat structure.
2026-02-07 23:01:20 +00:00

110 lines
4.2 KiB
Python

"""Unit tests for services/floorplan_detector.py."""
import asyncio
from datetime import datetime
from unittest.mock import AsyncMock, patch, MagicMock
from models.listing import RentListing, ListingSite, FurnishType
from services.floorplan_detector import _calculate_sqm_ocr, detect_floorplan
def _make_listing(**kwargs) -> RentListing: # type: ignore[no-untyped-def]
defaults = dict(
id=1,
price=2000.0,
number_of_bedrooms=2,
square_meters=None,
agency="Test",
council_tax_band="C",
longitude=0.0,
latitude=0.0,
price_history_json="[]",
listing_site=ListingSite.RIGHTMOVE,
last_seen=datetime.now(),
photo_thumbnail=None,
floorplan_image_paths=[],
additional_info={"property": {"visible": True}},
routing_info_json=None,
furnish_type=FurnishType.FURNISHED,
available_from=None,
)
defaults.update(kwargs)
return RentListing(**defaults)
class TestCalculateSqmOcr:
async def test_skips_listing_with_existing_square_meters(self) -> None:
listing = _make_listing(square_meters=50.0)
semaphore = asyncio.Semaphore(1)
result = await _calculate_sqm_ocr(listing, semaphore)
assert result is None
async def test_empty_floorplan_paths_returns_listing_with_zero(self) -> None:
listing = _make_listing(floorplan_image_paths=[])
semaphore = asyncio.Semaphore(1)
result = await _calculate_sqm_ocr(listing, semaphore)
assert result is not None
assert result.square_meters == 0
@patch("services.floorplan_detector.floorplan")
async def test_with_mocked_ocr_returning_value(self, mock_floorplan: MagicMock) -> None:
mock_floorplan.calculate_ocr.return_value = (85.0, "Total: 85 sq m")
listing = _make_listing(floorplan_image_paths=["/fake/path.png"])
semaphore = asyncio.Semaphore(1)
result = await _calculate_sqm_ocr(listing, semaphore)
assert result is not None
assert result.square_meters == 85.0
@patch("services.floorplan_detector.floorplan")
async def test_with_mocked_ocr_returning_none(self, mock_floorplan: MagicMock) -> None:
mock_floorplan.calculate_ocr.return_value = (None, "no data")
listing = _make_listing(floorplan_image_paths=["/fake/path.png"])
semaphore = asyncio.Semaphore(1)
result = await _calculate_sqm_ocr(listing, semaphore)
assert result is not None
assert result.square_meters == 0
@patch("services.floorplan_detector.floorplan")
async def test_picks_max_from_multiple_floorplans(self, mock_floorplan: MagicMock) -> None:
mock_floorplan.calculate_ocr.side_effect = [
(50.0, "50 sq m"),
(90.0, "90 sq m"),
]
listing = _make_listing(floorplan_image_paths=["/fake/a.png", "/fake/b.png"])
semaphore = asyncio.Semaphore(2)
result = await _calculate_sqm_ocr(listing, semaphore)
assert result is not None
assert result.square_meters == 90.0
class TestDetectFloorplan:
@patch("services.floorplan_detector.floorplan")
async def test_detect_floorplan_with_mocked_repository(self, mock_floorplan: MagicMock) -> None:
mock_floorplan.calculate_ocr.return_value = (75.0, "75 sq m")
listing = _make_listing(
floorplan_image_paths=["/fake/path.png"],
)
repository = MagicMock()
repository.get_listings = AsyncMock(return_value=[listing])
repository.upsert_listings = AsyncMock(return_value=[])
await detect_floorplan(repository)
repository.upsert_listings.assert_called_once()
upserted = repository.upsert_listings.call_args[0][0]
assert len(upserted) == 1
assert upserted[0].square_meters == 75.0
async def test_detect_floorplan_skips_already_processed(self) -> None:
listing = _make_listing(square_meters=50.0)
repository = MagicMock()
repository.get_listings = AsyncMock(return_value=[listing])
repository.upsert_listings = AsyncMock(return_value=[])
await detect_floorplan(repository)
repository.upsert_listings.assert_called_once()
upserted = repository.upsert_listings.call_args[0][0]
assert len(upserted) == 0