260 lines
8.8 KiB
Python
Executable File
260 lines
8.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Tests pour le store Backmarket."""
|
|
|
|
import pytest
|
|
from pathlib import Path
|
|
|
|
from pricewatch.app.stores.backmarket.store import BackmarketStore
|
|
|
|
|
|
class TestBackmarketStore:
|
|
"""Tests pour BackmarketStore."""
|
|
|
|
@pytest.fixture
|
|
def store(self):
|
|
"""Fixture du store Backmarket."""
|
|
return BackmarketStore()
|
|
|
|
# ========== Tests de match() ==========
|
|
|
|
def test_match_backmarket_fr(self, store):
|
|
"""URL backmarket.fr reconnue."""
|
|
url = "https://www.backmarket.fr/fr-fr/p/iphone-15-pro"
|
|
score = store.match(url)
|
|
assert score == 0.9
|
|
|
|
def test_match_backmarket_com(self, store):
|
|
"""URL backmarket.com reconnue (autres pays)."""
|
|
url = "https://www.backmarket.com/en-us/p/iphone-15-pro"
|
|
score = store.match(url)
|
|
assert score == 0.8
|
|
|
|
def test_match_other_site(self, store):
|
|
"""Autres sites non reconnus."""
|
|
urls = [
|
|
"https://www.amazon.fr/dp/ASIN",
|
|
"https://www.cdiscount.com/f-123-abc.html",
|
|
"https://www.fnac.com/produit",
|
|
"",
|
|
None,
|
|
]
|
|
for url in urls:
|
|
if url is not None:
|
|
score = store.match(url)
|
|
assert score == 0.0
|
|
|
|
def test_match_case_insensitive(self, store):
|
|
"""Match insensible à la casse."""
|
|
url = "https://WWW.BACKMARKET.FR/FR-FR/P/IPHONE"
|
|
score = store.match(url)
|
|
assert score == 0.9
|
|
|
|
# ========== Tests de canonicalize() ==========
|
|
|
|
def test_canonicalize_remove_query_params(self, store):
|
|
"""Canonicalize retire les paramètres de query."""
|
|
url = "https://www.backmarket.fr/fr-fr/p/iphone-15-pro?color=black"
|
|
canonical = store.canonicalize(url)
|
|
assert canonical == "https://www.backmarket.fr/fr-fr/p/iphone-15-pro"
|
|
|
|
def test_canonicalize_remove_fragment(self, store):
|
|
"""Canonicalize retire le fragment (#)."""
|
|
url = "https://www.backmarket.fr/fr-fr/p/iphone-15-pro#specs"
|
|
canonical = store.canonicalize(url)
|
|
assert canonical == "https://www.backmarket.fr/fr-fr/p/iphone-15-pro"
|
|
|
|
def test_canonicalize_keep_path(self, store):
|
|
"""Canonicalize garde le chemin complet."""
|
|
url = "https://www.backmarket.fr/fr-fr/p/iphone-15-pro"
|
|
canonical = store.canonicalize(url)
|
|
assert canonical == "https://www.backmarket.fr/fr-fr/p/iphone-15-pro"
|
|
|
|
def test_canonicalize_empty_url(self, store):
|
|
"""Canonicalize avec URL vide retourne la même."""
|
|
assert store.canonicalize("") == ""
|
|
assert store.canonicalize(None) is None
|
|
|
|
# ========== Tests de extract_reference() ==========
|
|
|
|
def test_extract_reference_standard_format(self, store):
|
|
"""Extraction du SKU depuis format standard /p/{slug}."""
|
|
url = "https://www.backmarket.fr/fr-fr/p/iphone-15-pro"
|
|
ref = store.extract_reference(url)
|
|
assert ref == "iphone-15-pro"
|
|
|
|
def test_extract_reference_with_query_params(self, store):
|
|
"""Extraction du SKU ignore les paramètres de query."""
|
|
url = "https://www.backmarket.fr/fr-fr/p/iphone-15-pro?color=black"
|
|
ref = store.extract_reference(url)
|
|
assert ref == "iphone-15-pro"
|
|
|
|
def test_extract_reference_different_locale(self, store):
|
|
"""Extraction du SKU fonctionne avec d'autres locales."""
|
|
url = "https://www.backmarket.com/en-us/p/macbook-air-m2"
|
|
ref = store.extract_reference(url)
|
|
assert ref == "macbook-air-m2"
|
|
|
|
def test_extract_reference_with_numbers(self, store):
|
|
"""Extraction du SKU avec chiffres dans le slug."""
|
|
url = "https://www.backmarket.fr/fr-fr/p/samsung-galaxy-s23"
|
|
ref = store.extract_reference(url)
|
|
assert ref == "samsung-galaxy-s23"
|
|
|
|
def test_extract_reference_invalid_url(self, store):
|
|
"""Extraction du SKU depuis URL invalide retourne None."""
|
|
urls = [
|
|
"https://www.backmarket.fr/fr-fr/collections/smartphones",
|
|
"https://www.backmarket.fr/",
|
|
"",
|
|
None,
|
|
]
|
|
for url in urls:
|
|
ref = store.extract_reference(url)
|
|
assert ref is None
|
|
|
|
# ========== Tests de parse() ==========
|
|
|
|
def test_parse_basic_html(self, store):
|
|
"""Parse HTML basique avec JSON-LD."""
|
|
html = """
|
|
<html>
|
|
<head>
|
|
<script type="application/ld+json">
|
|
{
|
|
"@type": "Product",
|
|
"name": "iPhone 15 Pro",
|
|
"offers": {
|
|
"@type": "Offer",
|
|
"price": "571.00",
|
|
"priceCurrency": "EUR"
|
|
},
|
|
"image": "https://example.com/image.jpg"
|
|
}
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<h1 class="heading-1">iPhone 15 Pro</h1>
|
|
</body>
|
|
</html>
|
|
"""
|
|
url = "https://www.backmarket.fr/fr-fr/p/iphone-15-pro"
|
|
snapshot = store.parse(html, url)
|
|
|
|
assert snapshot.source == "backmarket"
|
|
assert snapshot.url == "https://www.backmarket.fr/fr-fr/p/iphone-15-pro"
|
|
assert snapshot.title == "iPhone 15 Pro"
|
|
assert snapshot.price == 571.0
|
|
assert snapshot.currency == "EUR"
|
|
assert snapshot.reference == "iphone-15-pro"
|
|
assert len(snapshot.images) == 1
|
|
assert snapshot.is_complete()
|
|
|
|
def test_parse_without_json_ld(self, store):
|
|
"""Parse HTML sans JSON-LD utilise les sélecteurs CSS."""
|
|
html = """
|
|
<html>
|
|
<body>
|
|
<h1 class="heading-1">MacBook Air M2</h1>
|
|
<div data-test="price">799,99 €</div>
|
|
</body>
|
|
</html>
|
|
"""
|
|
url = "https://www.backmarket.fr/fr-fr/p/macbook-air-m2"
|
|
snapshot = store.parse(html, url)
|
|
|
|
assert snapshot.title == "MacBook Air M2"
|
|
assert snapshot.price == 799.99
|
|
assert snapshot.currency == "EUR"
|
|
assert snapshot.reference == "macbook-air-m2"
|
|
|
|
def test_parse_with_condition(self, store):
|
|
"""Parse extrait la condition du reconditionné."""
|
|
html = """
|
|
<html>
|
|
<head>
|
|
<script type="application/ld+json">
|
|
{
|
|
"@type": "Product",
|
|
"name": "iPhone 15",
|
|
"offers": {"price": "500", "priceCurrency": "EUR"}
|
|
}
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<h1>iPhone 15</h1>
|
|
<button data-test="condition-button">Excellent</button>
|
|
</body>
|
|
</html>
|
|
"""
|
|
url = "https://www.backmarket.fr/fr-fr/p/iphone-15"
|
|
snapshot = store.parse(html, url)
|
|
|
|
assert "Condition" in snapshot.specs
|
|
assert snapshot.specs["Condition"] == "Excellent"
|
|
assert any("reconditionné" in note.lower() for note in snapshot.debug.notes)
|
|
|
|
def test_parse_missing_title_and_price(self, store):
|
|
"""Parse avec titre et prix manquants → status PARTIAL."""
|
|
html = "<html><body><p>Contenu vide</p></body></html>"
|
|
url = "https://www.backmarket.fr/fr-fr/p/test"
|
|
snapshot = store.parse(html, url)
|
|
|
|
assert snapshot.title is None
|
|
assert snapshot.price is None
|
|
assert not snapshot.is_complete()
|
|
assert snapshot.debug.status == "partial"
|
|
|
|
def test_parse_stock_status_detection(self, store):
|
|
"""Parse détecte le statut de stock depuis le bouton add-to-cart."""
|
|
html = """
|
|
<html>
|
|
<head>
|
|
<script type="application/ld+json">
|
|
{
|
|
"@type": "Product",
|
|
"name": "Test Product",
|
|
"offers": {"price": "100", "priceCurrency": "EUR"}
|
|
}
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<button data-test="add-to-cart">Ajouter au panier</button>
|
|
</body>
|
|
</html>
|
|
"""
|
|
url = "https://www.backmarket.fr/fr-fr/p/test-product"
|
|
snapshot = store.parse(html, url)
|
|
|
|
assert snapshot.stock_status == "in_stock"
|
|
|
|
def test_parse_specs_from_definition_list(self, store):
|
|
"""Parse extrait les specs depuis les <dl>."""
|
|
html = """
|
|
<html>
|
|
<head>
|
|
<script type="application/ld+json">
|
|
{
|
|
"@type": "Product",
|
|
"name": "Test",
|
|
"offers": {"price": "100", "priceCurrency": "EUR"}
|
|
}
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<dl>
|
|
<dt>Mémoire</dt>
|
|
<dd>256 Go</dd>
|
|
<dt>Couleur</dt>
|
|
<dd>Noir</dd>
|
|
</dl>
|
|
</body>
|
|
</html>
|
|
"""
|
|
url = "https://www.backmarket.fr/fr-fr/p/test"
|
|
snapshot = store.parse(html, url)
|
|
|
|
assert "Mémoire" in snapshot.specs
|
|
assert snapshot.specs["Mémoire"] == "256 Go"
|
|
assert "Couleur" in snapshot.specs
|
|
assert snapshot.specs["Couleur"] == "Noir"
|