Refactor codebase following Clean Code principles and add 229 tests
- Extract helpers to reduce function sizes (listing_tasks, app.py, query.py, listing_fetcher) - Replace nonlocal mutations with _PipelineState dataclass in listing_tasks - Fix bugs: isinstance→equality check in repository, verify_exp for OIDC tokens - Consolidate duplicate filter methods in listing_repository - Move hardcoded config to env vars with backward-compatible defaults - Simplify CLI decorator to auto-build QueryParameters - Add deprecation docstring to data_access.py - Test count: 158 → 387 (all passing)
This commit is contained in:
parent
7e05b3c971
commit
150342bb9e
48 changed files with 5029 additions and 990 deletions
|
|
@ -1,5 +1,6 @@
|
|||
"""Unit tests for ListingRepository."""
|
||||
from datetime import datetime, timedelta
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
import pytest
|
||||
from sqlalchemy import Engine
|
||||
|
||||
|
|
@ -225,3 +226,156 @@ class TestListingRepositoryFilters:
|
|||
listings = await listing_repository.get_listings(query_parameters=query_params)
|
||||
# Should match listings with 1-2 bedrooms in price range
|
||||
assert len(listings) == 2
|
||||
|
||||
|
||||
class TestListingRepositoryStreaming:
|
||||
"""Tests for streaming and optimized query methods."""
|
||||
|
||||
async def test_count_listings_empty_db(
|
||||
self, listing_repository: ListingRepository
|
||||
) -> None:
|
||||
"""Test count returns 0 for empty database."""
|
||||
count = listing_repository.count_listings()
|
||||
assert count == 0
|
||||
|
||||
async def test_count_listings_with_data(
|
||||
self,
|
||||
listing_repository: ListingRepository,
|
||||
sample_rent_listings: list[RentListing],
|
||||
) -> None:
|
||||
"""Test count returns correct number."""
|
||||
await listing_repository.upsert_listings(sample_rent_listings)
|
||||
count = listing_repository.count_listings()
|
||||
assert count == 3
|
||||
|
||||
async def test_count_listings_with_filters(
|
||||
self,
|
||||
listing_repository: ListingRepository,
|
||||
sample_rent_listings: list[RentListing],
|
||||
) -> None:
|
||||
"""Test count respects query parameters."""
|
||||
await listing_repository.upsert_listings(sample_rent_listings)
|
||||
|
||||
query_params = QueryParameters(
|
||||
listing_type=ListingType.RENT,
|
||||
min_bedrooms=2,
|
||||
max_bedrooms=3,
|
||||
)
|
||||
count = listing_repository.count_listings(query_parameters=query_params)
|
||||
assert count == 2
|
||||
|
||||
async def test_stream_listings_optimized_returns_dicts(
|
||||
self,
|
||||
listing_repository: ListingRepository,
|
||||
sample_rent_listings: list[RentListing],
|
||||
) -> None:
|
||||
"""Test optimized streaming returns dict rows."""
|
||||
await listing_repository.upsert_listings(sample_rent_listings)
|
||||
|
||||
results = list(listing_repository.stream_listings_optimized())
|
||||
assert len(results) == 3
|
||||
# Each result should be a dict
|
||||
for row in results:
|
||||
assert isinstance(row, dict)
|
||||
assert "id" in row
|
||||
assert "price" in row
|
||||
assert "number_of_bedrooms" in row
|
||||
|
||||
async def test_stream_listings_optimized_respects_limit(
|
||||
self,
|
||||
listing_repository: ListingRepository,
|
||||
sample_rent_listings: list[RentListing],
|
||||
) -> None:
|
||||
"""Test streaming limit parameter."""
|
||||
await listing_repository.upsert_listings(sample_rent_listings)
|
||||
|
||||
results = list(listing_repository.stream_listings_optimized(limit=2))
|
||||
assert len(results) == 2
|
||||
|
||||
async def test_get_listing_ids(
|
||||
self,
|
||||
listing_repository: ListingRepository,
|
||||
sample_rent_listings: list[RentListing],
|
||||
) -> None:
|
||||
"""Test get_listing_ids returns set of IDs."""
|
||||
await listing_repository.upsert_listings(sample_rent_listings)
|
||||
|
||||
ids = listing_repository.get_listing_ids()
|
||||
assert isinstance(ids, set)
|
||||
assert ids == {1, 2, 3}
|
||||
|
||||
async def test_get_listing_ids_empty_db(
|
||||
self,
|
||||
listing_repository: ListingRepository,
|
||||
) -> None:
|
||||
"""Test get_listing_ids returns empty set for empty database."""
|
||||
ids = listing_repository.get_listing_ids()
|
||||
assert isinstance(ids, set)
|
||||
assert len(ids) == 0
|
||||
|
||||
|
||||
class TestFurnishTypeParsing:
|
||||
"""Tests for _parse_furnish_type helper."""
|
||||
|
||||
def test_parse_furnish_type_none_detailobject(self) -> None:
|
||||
"""Test that None detailobject returns UNKNOWN."""
|
||||
listing = MagicMock()
|
||||
listing.detailobject = None
|
||||
result = ListingRepository._parse_furnish_type(listing)
|
||||
assert result == FurnishType.UNKNOWN
|
||||
|
||||
def test_parse_furnish_type_missing_property_key(self) -> None:
|
||||
"""Test that missing 'property' key returns UNKNOWN."""
|
||||
listing = MagicMock()
|
||||
listing.detailobject = {}
|
||||
result = ListingRepository._parse_furnish_type(listing)
|
||||
assert result == FurnishType.UNKNOWN
|
||||
|
||||
def test_parse_furnish_type_missing_let_furnish_type(self) -> None:
|
||||
"""Test that missing 'letFurnishType' key returns UNKNOWN."""
|
||||
listing = MagicMock()
|
||||
listing.detailobject = {"property": {}}
|
||||
result = ListingRepository._parse_furnish_type(listing)
|
||||
assert result == FurnishType.UNKNOWN
|
||||
|
||||
def test_parse_furnish_type_null_value(self) -> None:
|
||||
"""Test that null letFurnishType value returns UNKNOWN."""
|
||||
listing = MagicMock()
|
||||
listing.detailobject = {"property": {"letFurnishType": None}}
|
||||
result = ListingRepository._parse_furnish_type(listing)
|
||||
assert result == FurnishType.UNKNOWN
|
||||
|
||||
def test_parse_furnish_type_furnished(self) -> None:
|
||||
"""Test that 'Furnished' is parsed correctly."""
|
||||
listing = MagicMock()
|
||||
listing.detailobject = {"property": {"letFurnishType": "Furnished"}}
|
||||
result = ListingRepository._parse_furnish_type(listing)
|
||||
assert result == FurnishType.FURNISHED
|
||||
|
||||
def test_parse_furnish_type_unfurnished(self) -> None:
|
||||
"""Test that 'Unfurnished' is parsed correctly."""
|
||||
listing = MagicMock()
|
||||
listing.detailobject = {"property": {"letFurnishType": "Unfurnished"}}
|
||||
result = ListingRepository._parse_furnish_type(listing)
|
||||
assert result == FurnishType.UNFURNISHED
|
||||
|
||||
def test_parse_furnish_type_part_furnished(self) -> None:
|
||||
"""Test that 'Part Furnished' is parsed correctly."""
|
||||
listing = MagicMock()
|
||||
listing.detailobject = {"property": {"letFurnishType": "Part Furnished"}}
|
||||
result = ListingRepository._parse_furnish_type(listing)
|
||||
assert result == FurnishType.PART_FURNISHED
|
||||
|
||||
def test_parse_furnish_type_landlord_variant(self) -> None:
|
||||
"""Test that landlord variants map to ASK_LANDLORD."""
|
||||
listing = MagicMock()
|
||||
listing.detailobject = {"property": {"letFurnishType": "Ask Landlord Please"}}
|
||||
result = ListingRepository._parse_furnish_type(listing)
|
||||
assert result == FurnishType.ASK_LANDLORD
|
||||
|
||||
def test_parse_furnish_type_landlord_case_insensitive(self) -> None:
|
||||
"""Test that landlord check is case-insensitive."""
|
||||
listing = MagicMock()
|
||||
listing.detailobject = {"property": {"letFurnishType": "LANDLORD decides"}}
|
||||
result = ListingRepository._parse_furnish_type(listing)
|
||||
assert result == FurnishType.ASK_LANDLORD
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue