229 lines
7.8 KiB
Python
229 lines
7.8 KiB
Python
import uuid
|
|
|
|
from app.models.property import Property, PropertyPhoto
|
|
|
|
|
|
def _make_property(slug: str, featured: bool = True, **kwargs) -> Property:
|
|
defaults = dict(
|
|
id=uuid.uuid4(),
|
|
title=f"Imóvel {slug}",
|
|
slug=slug,
|
|
address="Rua Teste, 1",
|
|
price="500000.00",
|
|
type="venda",
|
|
bedrooms=2,
|
|
bathrooms=1,
|
|
area_m2=80,
|
|
is_featured=featured,
|
|
is_active=True,
|
|
)
|
|
defaults.update(kwargs)
|
|
return Property(**defaults)
|
|
|
|
|
|
def test_get_properties_featured_returns_200_with_array(client, db):
|
|
"""GET /api/v1/properties?featured=true returns 200 with an array."""
|
|
prop = _make_property("imovel-featured-1")
|
|
db.session.add(prop)
|
|
db.session.flush()
|
|
|
|
photo = PropertyPhoto(
|
|
property_id=prop.id,
|
|
url="https://picsum.photos/seed/test/800/450",
|
|
alt_text="Foto de teste",
|
|
display_order=0,
|
|
)
|
|
db.session.add(photo)
|
|
db.session.commit()
|
|
|
|
response = client.get("/api/v1/properties?featured=true")
|
|
assert response.status_code == 200
|
|
|
|
data = response.get_json()
|
|
assert isinstance(data, list)
|
|
assert len(data) >= 1
|
|
|
|
|
|
def test_get_properties_response_contains_required_fields(client, db):
|
|
"""Response contains all required fields: id, title, slug, price, type, etc."""
|
|
prop = _make_property("imovel-fields-check")
|
|
db.session.add(prop)
|
|
db.session.flush()
|
|
|
|
photo = PropertyPhoto(
|
|
property_id=prop.id,
|
|
url="https://picsum.photos/seed/fields/800/450",
|
|
alt_text="Foto de campos",
|
|
display_order=0,
|
|
)
|
|
db.session.add(photo)
|
|
db.session.commit()
|
|
|
|
response = client.get("/api/v1/properties?featured=true")
|
|
assert response.status_code == 200
|
|
|
|
data = response.get_json()
|
|
assert len(data) >= 1
|
|
|
|
item = data[0]
|
|
required_fields = [
|
|
"id",
|
|
"title",
|
|
"slug",
|
|
"price",
|
|
"type",
|
|
"bedrooms",
|
|
"bathrooms",
|
|
"area_m2",
|
|
"photos",
|
|
]
|
|
for field in required_fields:
|
|
assert field in item, f"Missing field: {field}"
|
|
|
|
assert isinstance(item["photos"], list)
|
|
|
|
|
|
def test_get_properties_featured_empty_returns_empty_array(client, db):
|
|
"""GET /api/v1/properties?featured=true returns [] when no featured properties exist."""
|
|
# Add a non-featured active property
|
|
prop = _make_property("imovel-not-featured", featured=False)
|
|
db.session.add(prop)
|
|
db.session.commit()
|
|
|
|
response = client.get("/api/v1/properties?featured=true")
|
|
assert response.status_code == 200
|
|
|
|
data = response.get_json()
|
|
assert isinstance(data, list)
|
|
assert data == []
|
|
|
|
|
|
# ── Text search (q param) ─────────────────────────────────────────────────────
|
|
|
|
def test_q_matches_title(client, db):
|
|
"""?q=<word> returns properties whose title contains that word."""
|
|
p1 = _make_property("casa-praia", title="Casa na Praia", price="400000.00")
|
|
p2 = _make_property("apto-centro", title="Apartamento Centro", price="300000.00")
|
|
db.session.add_all([p1, p2])
|
|
db.session.commit()
|
|
|
|
response = client.get("/api/v1/properties?q=praia")
|
|
assert response.status_code == 200
|
|
data = response.get_json()
|
|
ids = [item["id"] for item in data["items"]]
|
|
assert str(p1.id) in ids
|
|
assert str(p2.id) not in ids
|
|
|
|
|
|
def test_q_is_case_insensitive(client, db):
|
|
"""?q search is case-insensitive."""
|
|
p = _make_property("casa-jardins", title="Casa nos Jardins", price="600000.00")
|
|
db.session.add(p)
|
|
db.session.commit()
|
|
|
|
for term in ("jardins", "JARDINS", "Jardins"):
|
|
response = client.get(f"/api/v1/properties?q={term}")
|
|
assert response.status_code == 200
|
|
ids = [item["id"] for item in response.get_json()["items"]]
|
|
assert str(p.id) in ids, f"Expected to find property with q={term!r}"
|
|
|
|
|
|
def test_q_matches_address(client, db):
|
|
"""?q search matches against the address field."""
|
|
p = _make_property("rua-flores", address="Rua das Flores, 42", title="Imóvel Especial", price="350000.00")
|
|
db.session.add(p)
|
|
db.session.commit()
|
|
|
|
response = client.get("/api/v1/properties?q=Flores")
|
|
assert response.status_code == 200
|
|
ids = [item["id"] for item in response.get_json()["items"]]
|
|
assert str(p.id) in ids
|
|
|
|
|
|
def test_q_no_match_returns_empty(client, db):
|
|
"""?q with a term that matches nothing returns total=0 and empty items."""
|
|
p = _make_property("imovel-comum", title="Imóvel Comum", price="200000.00")
|
|
db.session.add(p)
|
|
db.session.commit()
|
|
|
|
response = client.get("/api/v1/properties?q=xyznaomatch999")
|
|
assert response.status_code == 200
|
|
data = response.get_json()
|
|
assert data["total"] == 0
|
|
assert data["items"] == []
|
|
|
|
|
|
def test_q_truncated_to_200_chars(client, db):
|
|
"""?q longer than 200 chars is accepted without error (truncated server-side)."""
|
|
long_q = "a" * 300
|
|
response = client.get(f"/api/v1/properties?q={long_q}")
|
|
assert response.status_code == 200
|
|
|
|
|
|
# ── Sort param ────────────────────────────────────────────────────────────────
|
|
|
|
def _add_props_for_sort(db):
|
|
"""Helper: add three properties with different price/area for sort tests."""
|
|
props = [
|
|
_make_property("sort-cheap", title="Barato", price="100000.00", area_m2=50),
|
|
_make_property("sort-mid", title="Médio", price="300000.00", area_m2=80),
|
|
_make_property("sort-exp", title="Caro", price="600000.00", area_m2=120),
|
|
]
|
|
db.session.add_all(props)
|
|
db.session.commit()
|
|
return props
|
|
|
|
|
|
def test_sort_price_asc(client, db):
|
|
"""?sort=price_asc returns properties ordered from cheapest to most expensive."""
|
|
_add_props_for_sort(db)
|
|
response = client.get("/api/v1/properties?sort=price_asc")
|
|
assert response.status_code == 200
|
|
items = response.get_json()["items"]
|
|
prices = [float(item["price"]) for item in items]
|
|
assert prices == sorted(prices), "Items should be sorted price ascending"
|
|
|
|
|
|
def test_sort_price_desc(client, db):
|
|
"""?sort=price_desc returns properties from most to least expensive."""
|
|
_add_props_for_sort(db)
|
|
response = client.get("/api/v1/properties?sort=price_desc")
|
|
assert response.status_code == 200
|
|
items = response.get_json()["items"]
|
|
prices = [float(item["price"]) for item in items]
|
|
assert prices == sorted(prices, reverse=True), "Items should be sorted price descending"
|
|
|
|
|
|
def test_sort_area_desc(client, db):
|
|
"""?sort=area_desc returns properties from largest to smallest area."""
|
|
_add_props_for_sort(db)
|
|
response = client.get("/api/v1/properties?sort=area_desc")
|
|
assert response.status_code == 200
|
|
items = response.get_json()["items"]
|
|
areas = [item["area_m2"] for item in items]
|
|
assert areas == sorted(areas, reverse=True), "Items should be sorted area descending"
|
|
|
|
|
|
def test_sort_unknown_value_falls_back_gracefully(client, db):
|
|
"""?sort=<invalid> returns 200 (falls back to default sort)."""
|
|
p = _make_property("sort-fallback", price="200000.00")
|
|
db.session.add(p)
|
|
db.session.commit()
|
|
|
|
response = client.get("/api/v1/properties?sort=invalid_value")
|
|
assert response.status_code == 200
|
|
|
|
|
|
def test_paginated_response_shape(client, db):
|
|
"""Paginated listing endpoint returns items, total, page, per_page, pages."""
|
|
p = _make_property("paginated-shape", price="250000.00")
|
|
db.session.add(p)
|
|
db.session.commit()
|
|
|
|
response = client.get("/api/v1/properties?per_page=5")
|
|
assert response.status_code == 200
|
|
data = response.get_json()
|
|
for key in ("items", "total", "page", "per_page", "pages"):
|
|
assert key in data, f"Missing key in paginated response: {key}"
|
|
assert isinstance(data["items"], list)
|
|
assert data["total"] >= 1
|