"""Unit tests for services/listing_service.py.""" import pytest from unittest.mock import AsyncMock, MagicMock, patch from models.listing import QueryParameters, ListingType from services import listing_service class TestGetListings: async def test_empty_db_returns_zero(self, listing_repository): result = await listing_service.get_listings(listing_repository) assert result.total_count == 0 assert result.listings == [] async def test_with_results_returns_correct_count(self, listing_repository, sample_rent_listings): await listing_repository.upsert_listings(sample_rent_listings) result = await listing_service.get_listings(listing_repository) assert result.total_count == len(sample_rent_listings) assert len(result.listings) == len(sample_rent_listings) async def test_with_query_parameters_filters(self, listing_repository, sample_rent_listings): await listing_repository.upsert_listings(sample_rent_listings) params = QueryParameters(listing_type=ListingType.RENT, min_bedrooms=3) result = await listing_service.get_listings(listing_repository, query_parameters=params) for listing in result.listings: assert listing.number_of_bedrooms >= 3 async def test_limit_works(self, listing_repository, sample_rent_listings): await listing_repository.upsert_listings(sample_rent_listings) result = await listing_service.get_listings(listing_repository, limit=1) assert len(result.listings) <= 1 async def test_only_ids_works(self, listing_repository, sample_rent_listings): await listing_repository.upsert_listings(sample_rent_listings) target_ids = [sample_rent_listings[0].id] result = await listing_service.get_listings(listing_repository, only_ids=target_ids) assert all(l.id in target_ids for l in result.listings) class TestRefreshListings: async def test_async_mode_dispatches_celery_task(self, listing_repository): params = QueryParameters(listing_type=ListingType.RENT) with patch("tasks.listing_tasks.dump_listings_task") as mock_task: mock_task.apply_async.return_value = MagicMock(id="fake-task-id") result = await listing_service.refresh_listings( listing_repository, params, async_mode=True, user_email="test@example.com" ) mock_task.apply_async.assert_called_once() assert result.task_id == "fake-task-id" async def test_sync_mode_calls_fetcher(self, listing_repository): params = QueryParameters(listing_type=ListingType.RENT) with patch("services.listing_fetcher.dump_listings", new_callable=AsyncMock) as mock_dump: mock_dump.return_value = [] result = await listing_service.refresh_listings( listing_repository, params, async_mode=False ) mock_dump.assert_called_once() assert result.task_id is None async def test_full_mode_calls_dump_listings_full(self, listing_repository): params = QueryParameters(listing_type=ListingType.RENT) with patch("services.listing_fetcher.dump_listings_full", new_callable=AsyncMock) as mock_full: mock_full.return_value = [] result = await listing_service.refresh_listings( listing_repository, params, full=True, async_mode=False ) mock_full.assert_called_once() async def test_sync_returns_new_listings_count(self, listing_repository): params = QueryParameters(listing_type=ListingType.RENT) fake_listings = [MagicMock(), MagicMock(), MagicMock()] with patch("services.listing_fetcher.dump_listings", new_callable=AsyncMock) as mock_dump: mock_dump.return_value = fake_listings result = await listing_service.refresh_listings( listing_repository, params, async_mode=False ) assert result.new_listings_count == 3 async def test_async_result_has_message(self, listing_repository): params = QueryParameters(listing_type=ListingType.RENT) with patch("tasks.listing_tasks.dump_listings_task") as mock_task: mock_task.apply_async.return_value = MagicMock(id="fake-task-id") result = await listing_service.refresh_listings( listing_repository, params, async_mode=True ) assert result.message is not None assert len(result.message) > 0 class TestDownloadImages: async def test_calls_image_fetcher(self, listing_repository): with patch("services.image_fetcher.dump_images", new_callable=AsyncMock) as mock_dump: mock_dump.return_value = None result = await listing_service.download_images(listing_repository) mock_dump.assert_called_once() class TestDetectFloorplans: async def test_calls_floorplan_detector(self, listing_repository): with patch("services.floorplan_detector.detect_floorplan", new_callable=AsyncMock) as mock_detect: mock_detect.return_value = None result = await listing_service.detect_floorplans(listing_repository) mock_detect.assert_called_once() class TestCalculateRoutes: async def test_passes_correct_travel_mode(self, listing_repository): with patch("services.route_calculator.calculate_route", new_callable=AsyncMock) as mock_calc: mock_calc.return_value = None result = await listing_service.calculate_routes( listing_repository, destination_address="London Bridge", travel_mode="TRANSIT", limit=10, ) mock_calc.assert_called_once() async def test_passes_limit(self, listing_repository): with patch("services.route_calculator.calculate_route", new_callable=AsyncMock) as mock_calc: mock_calc.return_value = None result = await listing_service.calculate_routes( listing_repository, destination_address="Kings Cross", travel_mode="TRANSIT", limit=5, ) assert result == 5