"""Unit tests for Listing models.""" from datetime import datetime import json import pytest from models.listing import ( BuyListing, FurnishType, ListingSite, PriceHistoryItem, RentListing, Listing, ) class TestListing: """Tests for the base Listing model.""" def test_price_per_square_meter_calculation(self) -> None: """Test that price_per_square_meter is calculated correctly.""" listing = RentListing( id=1, price=2000.0, number_of_bedrooms=2, square_meters=50.0, 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, ) assert listing.price_per_square_meter == 40.0 def test_price_per_square_meter_none_when_no_sqm(self) -> None: """Test that price_per_square_meter is None when square_meters is None.""" listing = RentListing( 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, ) assert listing.price_per_square_meter is None def test_price_per_square_meter_none_when_sqm_zero(self) -> None: """Test that price_per_square_meter is None when square_meters is 0.""" listing = RentListing( id=1, price=2000.0, number_of_bedrooms=2, square_meters=0.0, 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, ) assert listing.price_per_square_meter is None def test_url_property(self) -> None: """Test that url property returns correct Rightmove URL.""" listing = RentListing( id=123456789, price=2000.0, number_of_bedrooms=2, square_meters=50.0, 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, ) assert listing.url == "https://www.rightmove.co.uk/properties/123456789" def test_is_removed_property_visible(self) -> None: """Test that is_removed returns False when property is visible.""" listing = RentListing( id=1, price=2000.0, number_of_bedrooms=2, square_meters=50.0, 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, ) assert listing.is_removed is False def test_is_removed_property_not_visible(self) -> None: """Test that is_removed returns True when property is not visible.""" listing = RentListing( id=1, price=2000.0, number_of_bedrooms=2, square_meters=50.0, 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": False}}, routing_info_json=None, furnish_type=FurnishType.FURNISHED, available_from=None, ) assert listing.is_removed is True class TestPriceHistory: """Tests for price history serialization/deserialization.""" def test_price_history_serialization_roundtrip(self) -> None: """Test that price history can be serialized and deserialized.""" now = datetime.now() price_history = [ PriceHistoryItem( first_seen=now, last_seen=now, price=2000.0, ), PriceHistoryItem( first_seen=now, last_seen=now, price=2100.0, ), ] # Serialize serialized = Listing.serialize_price_history(price_history) assert isinstance(serialized, str) # Create listing with serialized history listing = RentListing( id=1, price=2100.0, number_of_bedrooms=2, square_meters=50.0, agency="Test", council_tax_band="C", longitude=0.0, latitude=0.0, price_history_json=serialized, listing_site=ListingSite.RIGHTMOVE, last_seen=now, photo_thumbnail=None, floorplan_image_paths=[], additional_info={"property": {"visible": True}}, routing_info_json=None, furnish_type=FurnishType.FURNISHED, available_from=None, ) # Deserialize deserialized = listing.price_history assert len(deserialized) == 2 assert deserialized[0].price == 2000.0 assert deserialized[1].price == 2100.0 def test_price_history_empty(self) -> None: """Test that empty price history works correctly.""" listing = RentListing( id=1, price=2000.0, number_of_bedrooms=2, square_meters=50.0, 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, ) assert listing.price_history == [] def test_price_history_item_to_dict(self) -> None: """Test PriceHistoryItem.to_dict() method.""" now = datetime.now() item = PriceHistoryItem( first_seen=now, last_seen=now, price=2500.0, ) result = item.to_dict() assert result["price"] == 2500.0 assert result["first_seen"] == now.isoformat() assert result["last_seen"] == now.isoformat() class TestRentListing: """Tests specific to RentListing model.""" def test_rent_listing_has_furnish_type(self) -> None: """Test that RentListing has furnish_type field.""" listing = RentListing( id=1, price=2000.0, number_of_bedrooms=2, square_meters=50.0, 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.PART_FURNISHED, available_from=None, ) assert listing.furnish_type == FurnishType.PART_FURNISHED def test_rent_listing_has_available_from(self) -> None: """Test that RentListing has available_from field.""" now = datetime.now() listing = RentListing( id=1, price=2000.0, number_of_bedrooms=2, square_meters=50.0, agency="Test", council_tax_band="C", longitude=0.0, latitude=0.0, price_history_json="[]", listing_site=ListingSite.RIGHTMOVE, last_seen=now, photo_thumbnail=None, floorplan_image_paths=[], additional_info={"property": {"visible": True}}, routing_info_json=None, furnish_type=FurnishType.FURNISHED, available_from=now, ) assert listing.available_from == now class TestBuyListing: """Tests specific to BuyListing model.""" def test_buy_listing_has_service_charge(self) -> None: """Test that BuyListing has service_charge field.""" listing = BuyListing( id=1, price=450000.0, number_of_bedrooms=3, square_meters=95.0, agency="Test", council_tax_band="D", 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, service_charge=2500.0, lease_left=85, ) assert listing.service_charge == 2500.0 def test_buy_listing_has_lease_left(self) -> None: """Test that BuyListing has lease_left field.""" listing = BuyListing( id=1, price=450000.0, number_of_bedrooms=3, square_meters=95.0, agency="Test", council_tax_band="D", 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, service_charge=None, lease_left=120, ) assert listing.lease_left == 120