343 lines
11 KiB
Python
343 lines
11 KiB
Python
"""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
|