2026-02-07 20:19:57 +00:00
|
|
|
"""Unit tests for the listing processor."""
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
|
import pytest
|
2026-02-07 23:34:08 +00:00
|
|
|
from models.listing import FurnishType, ListingType
|
2026-02-07 20:19:57 +00:00
|
|
|
from listing_processor import (
|
|
|
|
|
_parse_furnish_type,
|
|
|
|
|
_parse_available_from,
|
|
|
|
|
ListingProcessor,
|
|
|
|
|
FetchListingDetailsStep,
|
|
|
|
|
MAX_OCR_WORKERS,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestParseFurnishType:
|
|
|
|
|
"""Tests for _parse_furnish_type helper."""
|
|
|
|
|
|
|
|
|
|
def test_none_returns_unknown(self):
|
|
|
|
|
assert _parse_furnish_type(None) == FurnishType.UNKNOWN
|
|
|
|
|
|
|
|
|
|
def test_ask_landlord_variant(self):
|
|
|
|
|
assert _parse_furnish_type("Ask landlord") == FurnishType.ASK_LANDLORD
|
|
|
|
|
|
|
|
|
|
def test_furnished_lowercased(self):
|
|
|
|
|
assert _parse_furnish_type("Furnished") == FurnishType.FURNISHED
|
|
|
|
|
|
|
|
|
|
def test_unfurnished(self):
|
|
|
|
|
assert _parse_furnish_type("Unfurnished") == FurnishType.UNFURNISHED
|
|
|
|
|
|
|
|
|
|
def test_part_furnished(self):
|
|
|
|
|
assert _parse_furnish_type("Part Furnished") == FurnishType.PART_FURNISHED
|
|
|
|
|
|
|
|
|
|
def test_unknown_string_returns_unknown(self):
|
|
|
|
|
assert _parse_furnish_type("unknown") == FurnishType.UNKNOWN
|
|
|
|
|
|
|
|
|
|
def test_garbage_string_returns_unknown(self):
|
|
|
|
|
assert _parse_furnish_type("xyzzy") == FurnishType.UNKNOWN
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestParseAvailableFrom:
|
|
|
|
|
"""Tests for _parse_available_from helper."""
|
|
|
|
|
|
|
|
|
|
def test_none_returns_none(self):
|
|
|
|
|
assert _parse_available_from(None) is None
|
|
|
|
|
|
|
|
|
|
def test_now_returns_datetime(self):
|
|
|
|
|
result = _parse_available_from("Now")
|
|
|
|
|
assert isinstance(result, datetime)
|
|
|
|
|
|
|
|
|
|
def test_valid_date_string(self):
|
|
|
|
|
result = _parse_available_from("15/03/2024")
|
|
|
|
|
assert result is not None
|
|
|
|
|
assert result.day == 15
|
|
|
|
|
assert result.month == 3
|
|
|
|
|
|
|
|
|
|
def test_invalid_date_returns_none(self):
|
|
|
|
|
assert _parse_available_from("invalid") is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestListingProcessor:
|
|
|
|
|
"""Tests for ListingProcessor."""
|
|
|
|
|
|
|
|
|
|
async def test_process_listing_marks_seen(self):
|
|
|
|
|
"""Test that process_listing calls mark_seen."""
|
|
|
|
|
mock_repo = AsyncMock()
|
|
|
|
|
mock_repo.get_listings = AsyncMock(return_value=[MagicMock()])
|
|
|
|
|
processor = ListingProcessor(mock_repo)
|
|
|
|
|
# Mock all steps to not need processing
|
|
|
|
|
for step in processor.process_steps:
|
|
|
|
|
step.needs_processing = AsyncMock(return_value=False)
|
|
|
|
|
await processor.process_listing(123)
|
2026-02-07 23:34:08 +00:00
|
|
|
mock_repo.mark_seen.assert_awaited_once_with(123, ListingType.RENT)
|
2026-02-07 20:19:57 +00:00
|
|
|
|
|
|
|
|
async def test_process_listing_returns_none_on_step_failure(self):
|
|
|
|
|
"""Test that a step failure returns None."""
|
|
|
|
|
mock_repo = AsyncMock()
|
|
|
|
|
processor = ListingProcessor(mock_repo)
|
|
|
|
|
for step in processor.process_steps:
|
|
|
|
|
step.needs_processing = AsyncMock(return_value=True)
|
|
|
|
|
step.process = AsyncMock(side_effect=Exception("fail"))
|
|
|
|
|
result = await processor.process_listing(123)
|
|
|
|
|
assert result is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestOcrWorkersConfig:
|
|
|
|
|
def test_max_ocr_workers_positive(self):
|
|
|
|
|
assert MAX_OCR_WORKERS >= 1
|