phase 2
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
"""Tests for formatting module."""
|
||||
|
||||
import pytest
|
||||
|
||||
from finn_eiendom import formatting
|
||||
|
||||
|
||||
def test_render_ad_json():
|
||||
"""Test rendering ad as JSON."""
|
||||
ad = {
|
||||
"finnkode": "123456",
|
||||
"title": "Nice apartment",
|
||||
"address": "Hovedstreet 1",
|
||||
"asking_price": 5000000,
|
||||
"area_m2": 80,
|
||||
}
|
||||
result = formatting.render_ad(ad, "json")
|
||||
assert "123456" in result
|
||||
assert "Nice apartment" in result
|
||||
assert isinstance(result, str)
|
||||
|
||||
|
||||
def test_render_ad_markdown():
|
||||
"""Test rendering ad as Markdown."""
|
||||
ad = {
|
||||
"finnkode": "123456",
|
||||
"title": "Nice apartment",
|
||||
"address": "Hovedstreet 1",
|
||||
"asking_price": 5000000,
|
||||
"area_m2": 80,
|
||||
}
|
||||
result = formatting.render_ad(ad, "markdown")
|
||||
assert "Nice apartment" in result
|
||||
assert "Hovedstreet 1" in result
|
||||
assert "#" in result # Markdown headers
|
||||
|
||||
|
||||
def test_render_shortlist_json():
|
||||
"""Test rendering shortlist as JSON."""
|
||||
data = {
|
||||
"shortlist": [
|
||||
{
|
||||
"finnkode": "1",
|
||||
"title": "Ad 1",
|
||||
"address": "Street 1",
|
||||
"asking_price": 5000000,
|
||||
"area_m2": 80,
|
||||
},
|
||||
{
|
||||
"finnkode": "2",
|
||||
"title": "Ad 2",
|
||||
"address": "Street 2",
|
||||
"asking_price": 4500000,
|
||||
"area_m2": 75,
|
||||
},
|
||||
]
|
||||
}
|
||||
result = formatting.render_shortlist(data, "json")
|
||||
assert "Ad 1" in result
|
||||
assert "Ad 2" in result
|
||||
|
||||
|
||||
def test_render_shortlist_markdown():
|
||||
"""Test rendering shortlist as Markdown."""
|
||||
data = {
|
||||
"shortlist": [
|
||||
{
|
||||
"finnkode": "1",
|
||||
"title": "Ad 1",
|
||||
"address": "Street 1",
|
||||
"asking_price": 5000000,
|
||||
"area_m2": 80,
|
||||
},
|
||||
]
|
||||
}
|
||||
result = formatting.render_shortlist(data, "markdown")
|
||||
assert "Ad 1" in result
|
||||
assert "Street 1" in result
|
||||
|
||||
|
||||
def test_render_shortlist_table():
|
||||
"""Test rendering shortlist as table."""
|
||||
data = {
|
||||
"shortlist": [
|
||||
{
|
||||
"finnkode": "1",
|
||||
"title": "Ad 1",
|
||||
"address": "Street 1",
|
||||
"asking_price": 5000000,
|
||||
"area_m2": 80,
|
||||
},
|
||||
]
|
||||
}
|
||||
result = formatting.render_shortlist(data, "table")
|
||||
assert "|" in result # Table format
|
||||
|
||||
|
||||
def test_render_comparison():
|
||||
"""Test rendering comparison."""
|
||||
data = {
|
||||
"listings": [
|
||||
{
|
||||
"finnkode": "1",
|
||||
"title": "Ad 1",
|
||||
"address": "Street 1",
|
||||
"asking_price": 5000000,
|
||||
"area_m2": 80,
|
||||
},
|
||||
{
|
||||
"finnkode": "2",
|
||||
"title": "Ad 2",
|
||||
"address": "Street 2",
|
||||
"asking_price": 4500000,
|
||||
"area_m2": 75,
|
||||
},
|
||||
]
|
||||
}
|
||||
result = formatting.render_comparison(data, "markdown")
|
||||
assert "Comparison" in result
|
||||
assert "Ad 1" in result
|
||||
assert "Ad 2" in result
|
||||
|
||||
|
||||
def test_render_diff():
|
||||
"""Test rendering diff."""
|
||||
data = {
|
||||
"new_ads": [
|
||||
{
|
||||
"title": "New Ad",
|
||||
"address": "New Street",
|
||||
"asking_price": 5000000,
|
||||
},
|
||||
],
|
||||
"removed_ads": [],
|
||||
"changed_ads": [],
|
||||
}
|
||||
result = formatting.render_diff(data, "markdown")
|
||||
assert "New" in result
|
||||
assert "New Ad" in result
|
||||
|
||||
|
||||
def test_render_cache_stats():
|
||||
"""Test rendering cache stats."""
|
||||
data = {
|
||||
"total_finn_ads": 100,
|
||||
"total_eiendom_units": 50,
|
||||
"total_search_runs": 10,
|
||||
"cache_size_mb": 25.5,
|
||||
"last_updated": "2024-01-01",
|
||||
}
|
||||
result = formatting.render_cache_stats(data, "json")
|
||||
assert "100" in result
|
||||
assert "50" in result
|
||||
|
||||
|
||||
def test_format_validation():
|
||||
"""Test that invalid formats raise ValueError."""
|
||||
with pytest.raises(ValueError):
|
||||
formatting.render_ad({}, "invalid")
|
||||
|
||||
|
||||
def test_render_unit():
|
||||
"""Test rendering Eiendom unit."""
|
||||
unit = {
|
||||
"unit_code": "code123",
|
||||
"address": "Hovedstreet 1",
|
||||
"rooms": 3,
|
||||
"estimated_selling_price": 5000000,
|
||||
}
|
||||
result = formatting.render_unit(unit, "markdown")
|
||||
assert "Hovedstreet 1" in result
|
||||
assert "3" in result
|
||||
@@ -0,0 +1,265 @@
|
||||
"""Tests for Pydantic models in finn_eiendom.models."""
|
||||
|
||||
import json
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from finn_eiendom.models import (
|
||||
EiendomUnit,
|
||||
FinnAd,
|
||||
FinnSearchCard,
|
||||
SimilarUnit,
|
||||
UnitVector,
|
||||
)
|
||||
|
||||
|
||||
def test_finn_search_card_json_roundtrip():
|
||||
"""Test JSON roundtrip for FinnSearchCard model."""
|
||||
data = {
|
||||
"finnkode": "123456789",
|
||||
"url": "https://www.finn.no/realestate/homes/ad.html?finnkode=123456789",
|
||||
"title": "Beautiful apartment in Oslo",
|
||||
"address": "Karl Johans gate 1",
|
||||
"area_m2": 80,
|
||||
"asking_price": 5000000,
|
||||
"total_price": 5200000,
|
||||
"common_costs": 2000,
|
||||
"property_type": "APARTMENT",
|
||||
"ownership_type": "FREEHOLD",
|
||||
"bedrooms": 2,
|
||||
"floor": "3/5",
|
||||
"broker_company": "Estate AS",
|
||||
}
|
||||
|
||||
# Create model instance
|
||||
model = FinnSearchCard(**data)
|
||||
|
||||
# Convert to JSON
|
||||
json_str = model.model_dump_json()
|
||||
|
||||
# Parse JSON back to dict
|
||||
parsed_data = json.loads(json_str)
|
||||
|
||||
# Create new instance from parsed data
|
||||
model_from_json = FinnSearchCard(**parsed_data)
|
||||
|
||||
# Assert they're equal
|
||||
assert model == model_from_json
|
||||
|
||||
|
||||
def test_finn_ad_json_roundtrip():
|
||||
"""Test JSON roundtrip for FinnAd model."""
|
||||
now = datetime.now(UTC)
|
||||
data = {
|
||||
"finnkode": "123456789",
|
||||
"url": "https://www.finn.no/realestate/homes/ad.html?finnkode=123456789",
|
||||
"title": "Beautiful apartment in Oslo",
|
||||
"address": "Karl Johans gate 1",
|
||||
"postal_area": "0150",
|
||||
"district": "Sentrum",
|
||||
"property_type": "APARTMENT",
|
||||
"ownership_type": "FREEHOLD",
|
||||
"asking_price": 5000000,
|
||||
"total_price": 5200000,
|
||||
"shared_debt": 100000,
|
||||
"common_costs": 2000,
|
||||
"municipal_fee": 500,
|
||||
"other_fees": 300,
|
||||
"area_m2": 80,
|
||||
"rooms": 3,
|
||||
"bedrooms": 2,
|
||||
"floor": "3/5",
|
||||
"construction_year": 2000,
|
||||
"energy_rating": "C",
|
||||
"heating": "FJERNVARME",
|
||||
"has_balcony": True,
|
||||
"has_terrace": False,
|
||||
"has_elevator": True,
|
||||
"has_parking": False,
|
||||
"has_garage": False,
|
||||
"listing_description": "A lovely apartment with great views",
|
||||
"broker_name": "John Doe",
|
||||
"broker_company": "Estate AS",
|
||||
"first_seen_at": now,
|
||||
"last_seen_at": now,
|
||||
"detail_fetched_at": now,
|
||||
"eiendom_unit_code": "EI-987654321",
|
||||
}
|
||||
|
||||
# Create model instance
|
||||
model = FinnAd(**data)
|
||||
|
||||
# Convert to JSON
|
||||
json_str = model.model_dump_json()
|
||||
|
||||
# Parse JSON back to dict
|
||||
parsed_data = json.loads(json_str)
|
||||
|
||||
# Create new instance from parsed data
|
||||
model_from_json = FinnAd(**parsed_data)
|
||||
|
||||
# Assert they're equal
|
||||
assert model == model_from_json
|
||||
|
||||
|
||||
def test_eiendom_unit_json_roundtrip():
|
||||
"""Test JSON roundtrip for EiendomUnit model."""
|
||||
now = datetime.now(UTC)
|
||||
data = {
|
||||
"unit_code": "EI-987654321",
|
||||
"address": "Karl Johans gate 1",
|
||||
"lat": 59.9139,
|
||||
"lng": 10.7522,
|
||||
"property_type": "APARTMENT",
|
||||
"floor": 3,
|
||||
"rooms": 3,
|
||||
"construction_year": 2000,
|
||||
"usable_area": 80,
|
||||
"estimated_selling_price": 5500000,
|
||||
"estimated_selling_price_lower": 5200000,
|
||||
"estimated_selling_price_upper": 5800000,
|
||||
"listing_price": 5000000,
|
||||
"listing_sqm_price": 62500,
|
||||
"common_costs": 2000,
|
||||
"days_on_market": 45,
|
||||
"sale_status": "FOR_SALE",
|
||||
"market_placement_score": "GOOD",
|
||||
"unit_vector": "base64encodedvector===",
|
||||
"fetched_at": now,
|
||||
}
|
||||
|
||||
# Create model instance
|
||||
model = EiendomUnit(**data)
|
||||
|
||||
# Convert to JSON
|
||||
json_str = model.model_dump_json()
|
||||
|
||||
# Parse JSON back to dict
|
||||
parsed_data = json.loads(json_str)
|
||||
|
||||
# Create new instance from parsed data
|
||||
model_from_json = EiendomUnit(**parsed_data)
|
||||
|
||||
# Assert they're equal
|
||||
assert model == model_from_json
|
||||
|
||||
|
||||
def test_similar_unit_json_roundtrip():
|
||||
"""Test JSON roundtrip for SimilarUnit model."""
|
||||
data = {
|
||||
"unit_code": "EI-123456789",
|
||||
"address": "Karl Johans gate 2",
|
||||
"lat": 59.9140,
|
||||
"lng": 10.7523,
|
||||
"property_type": "APARTMENT",
|
||||
"floor": 4,
|
||||
"rooms": 3,
|
||||
"construction_year": 2005,
|
||||
"usable_area": 90,
|
||||
"listing_price": 5800000,
|
||||
"selling_price": 5700000,
|
||||
"shared_debt": 120000,
|
||||
"common_costs": 2200,
|
||||
"sqm_price": 63333,
|
||||
"days_on_market": 30,
|
||||
"sale_status": "SOLD",
|
||||
"finalized_at": datetime.now(UTC),
|
||||
"listing_status": "RECENTLY_SOLD",
|
||||
}
|
||||
|
||||
# Create model instance
|
||||
model = SimilarUnit(**data)
|
||||
|
||||
# Convert to JSON
|
||||
json_str = model.model_dump_json()
|
||||
|
||||
# Parse JSON back to dict
|
||||
parsed_data = json.loads(json_str)
|
||||
|
||||
# Create new instance from parsed data
|
||||
model_from_json = SimilarUnit(**parsed_data)
|
||||
|
||||
# Assert they're equal
|
||||
assert model == model_from_json
|
||||
|
||||
|
||||
def test_unit_vector_json_roundtrip():
|
||||
"""Test JSON roundtrip for UnitVector model."""
|
||||
data = {
|
||||
"lon": 10.7522,
|
||||
"lat": 59.9139,
|
||||
"ptype": "APARTMENT",
|
||||
"floor": 3,
|
||||
"rooms": 3,
|
||||
"built": 2000,
|
||||
"area": 80,
|
||||
"price": 5000000,
|
||||
}
|
||||
|
||||
# Create model instance
|
||||
model = UnitVector(**data)
|
||||
|
||||
# Convert to JSON
|
||||
json_str = model.model_dump_json()
|
||||
|
||||
# Parse JSON back to dict
|
||||
parsed_data = json.loads(json_str)
|
||||
|
||||
# Create new instance from parsed data
|
||||
model_from_json = UnitVector(**parsed_data)
|
||||
|
||||
# Assert they're equal
|
||||
assert model == model_from_json
|
||||
|
||||
|
||||
def test_finn_ad_serializer_datetime():
|
||||
"""Test that FinnAd datetime serialization works correctly."""
|
||||
now = datetime.now(UTC)
|
||||
data = {
|
||||
"finnkode": "123456789",
|
||||
"url": "https://www.finn.no/realestate/homes/ad.html?finnkode=123456789",
|
||||
"first_seen_at": now,
|
||||
"last_seen_at": now,
|
||||
}
|
||||
|
||||
model = FinnAd(**data)
|
||||
json_str = model.model_dump_json()
|
||||
parsed_data = json.loads(json_str)
|
||||
|
||||
# Check that datetimes are serialized as ISO strings
|
||||
assert isinstance(parsed_data["first_seen_at"], str)
|
||||
assert isinstance(parsed_data["last_seen_at"], str)
|
||||
assert "T" in parsed_data["first_seen_at"] # ISO format contains T
|
||||
assert "Z" in parsed_data["first_seen_at"] or "+" in parsed_data["first_seen_at"] # TZ info
|
||||
|
||||
|
||||
def test_eiendom_unit_serializer_datetime():
|
||||
"""Test that EiendomUnit datetime serialization works correctly."""
|
||||
now = datetime.now(UTC)
|
||||
data = {
|
||||
"unit_code": "EI-987654321",
|
||||
"fetched_at": now,
|
||||
}
|
||||
|
||||
model = EiendomUnit(**data)
|
||||
json_str = model.model_dump_json()
|
||||
parsed_data = json.loads(json_str)
|
||||
|
||||
# Check that datetimes are serialized as ISO strings
|
||||
assert isinstance(parsed_data["fetched_at"], str)
|
||||
assert "T" in parsed_data["fetched_at"] # ISO format contains T
|
||||
assert "Z" in parsed_data["fetched_at"] or "+" in parsed_data["fetched_at"] # Timezone info
|
||||
|
||||
|
||||
def test_similar_unit_serializer_datetime():
|
||||
"""Test that SimilarUnit datetime serialization works correctly (handles None)."""
|
||||
data = {
|
||||
"unit_code": "EI-123456789",
|
||||
"finalized_at": None, # This should serialize to null
|
||||
}
|
||||
|
||||
model = SimilarUnit(**data)
|
||||
json_str = model.model_dump_json()
|
||||
parsed_data = json.loads(json_str)
|
||||
|
||||
# Check that None datetimes serialize to null
|
||||
assert parsed_data["finalized_at"] is None
|
||||
Reference in New Issue
Block a user