"""Unit tests for the listing processor.""" from datetime import datetime from unittest.mock import AsyncMock, MagicMock, patch import pytest from models.listing import FurnishType 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) mock_repo.mark_seen.assert_awaited_once_with(123) 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