""" Tests pour pricewatch.app.stores.cdiscount.store Vérifie match(), canonicalize(), extract_reference() et parse() pour le store Cdiscount. """ import pytest from pricewatch.app.stores.cdiscount.store import CdiscountStore from pricewatch.app.core.schema import DebugStatus, StockStatus class TestCdiscountMatch: """Tests de la méthode match() pour Cdiscount.""" @pytest.fixture def store(self) -> CdiscountStore: """Fixture: CdiscountStore instance.""" return CdiscountStore() def test_match_cdiscount_com(self, store): """cdiscount.com doit retourner 0.9.""" score = store.match("https://www.cdiscount.com/informatique/example/f-123-sku.html") assert score == 0.9 def test_match_non_cdiscount(self, store): """URL non-Cdiscount doit retourner 0.0.""" score = store.match("https://www.amazon.fr/dp/B08N5WRWNW") assert score == 0.0 def test_match_empty_url(self, store): """URL vide doit retourner 0.0.""" score = store.match("") assert score == 0.0 def test_match_case_insensitive(self, store): """Match doit être insensible à la casse.""" score = store.match("https://www.CDISCOUNT.COM/product/f-123-sku.html") assert score == 0.9 class TestCdiscountCanonicalize: """Tests de la méthode canonicalize() pour Cdiscount.""" @pytest.fixture def store(self) -> CdiscountStore: """Fixture: CdiscountStore instance.""" return CdiscountStore() def test_canonicalize_with_query_params(self, store): """URL avec query params doit être normalisée.""" url = "https://www.cdiscount.com/informatique/pc/product/f-10709-sku.html?idOffre=123&sw=abc" canonical = store.canonicalize(url) assert canonical == "https://www.cdiscount.com/informatique/pc/product/f-10709-sku.html" assert "?" not in canonical def test_canonicalize_already_canonical(self, store): """URL déjà canonique ne change pas.""" url = "https://www.cdiscount.com/informatique/pc/f-10709-sku.html" canonical = store.canonicalize(url) assert canonical == url def test_canonicalize_with_fragment(self, store): """URL avec fragment doit être normalisée.""" url = "https://www.cdiscount.com/informatique/pc/f-10709-sku.html#mpos=2" canonical = store.canonicalize(url) assert canonical == "https://www.cdiscount.com/informatique/pc/f-10709-sku.html" assert "#" not in canonical def test_canonicalize_empty_url(self, store): """URL vide retourne URL vide.""" canonical = store.canonicalize("") assert canonical == "" class TestCdiscountExtractReference: """Tests de la méthode extract_reference() pour Cdiscount.""" @pytest.fixture def store(self) -> CdiscountStore: """Fixture: CdiscountStore instance.""" return CdiscountStore() def test_extract_reference_standard_format(self, store): """Extraction du SKU depuis format standard /f-{ID}-{SKU}.html.""" url = "https://www.cdiscount.com/informatique/pc/f-10709-tuf608umrv004.html" ref = store.extract_reference(url) assert ref == "10709-tuf608umrv004" def test_extract_reference_long_url(self, store): """Extraction du SKU depuis URL longue avec chemin complet.""" url = "https://www.cdiscount.com/informatique/ordinateurs-pc-portables/pc-portable-gamer-asus/f-10709-tuf608umrv004.html" ref = store.extract_reference(url) assert ref == "10709-tuf608umrv004" def test_extract_reference_with_query_params(self, store): """Extraction du SKU depuis URL avec query params.""" url = "https://www.cdiscount.com/informatique/pc/f-10709-sku123.html?idOffre=456" ref = store.extract_reference(url) assert ref == "10709-sku123" def test_extract_reference_invalid_url(self, store): """URL sans SKU retourne None.""" url = "https://www.cdiscount.com/informatique/" ref = store.extract_reference(url) assert ref is None def test_extract_reference_empty_url(self, store): """URL vide retourne None.""" ref = store.extract_reference("") assert ref is None class TestCdiscountParse: """Tests de la méthode parse() pour Cdiscount.""" @pytest.fixture def store(self) -> CdiscountStore: """Fixture: CdiscountStore instance.""" return CdiscountStore() @pytest.fixture def minimal_html(self) -> str: """Fixture: HTML Cdiscount minimal avec titre et prix.""" return """ Test Product

PC Portable Test

899,99 €
""" @pytest.fixture def complete_html(self) -> str: """Fixture: HTML Cdiscount plus complet.""" return """ Test Product

PC Portable Gamer ASUS

1299,99 €
PC Portable Gamer ASUS PC Portable Gamer ASUS """ @pytest.fixture def empty_html(self) -> str: """Fixture: HTML vide.""" return "" def test_parse_minimal_html(self, store, minimal_html): """Parse un HTML minimal avec titre et prix.""" url = "https://www.cdiscount.com/informatique/pc/f-10709-sku123.html" snapshot = store.parse(minimal_html, url) assert snapshot.source == "cdiscount" assert snapshot.url == "https://www.cdiscount.com/informatique/pc/f-10709-sku123.html" assert snapshot.title == "PC Portable Test" assert snapshot.price == 899.99 assert snapshot.currency == "EUR" assert snapshot.is_complete() is True def test_parse_complete_html(self, store, complete_html): """Parse un HTML plus complet avec images.""" url = "https://www.cdiscount.com/informatique/pc/f-10709-asus123.html" snapshot = store.parse(complete_html, url) assert snapshot.title == "PC Portable Gamer ASUS" assert snapshot.price == 1299.99 assert snapshot.reference == "10709-asus123" assert len(snapshot.images) >= 2 assert snapshot.is_complete() is True assert snapshot.debug.status == DebugStatus.SUCCESS def test_parse_empty_html(self, store, empty_html): """Parse un HTML vide doit retourner un snapshot partiel.""" url = "https://www.cdiscount.com/informatique/pc/f-10709-sku.html" snapshot = store.parse(empty_html, url) assert snapshot.source == "cdiscount" assert snapshot.title is None assert snapshot.price is None assert snapshot.is_complete() is False assert snapshot.debug.status == DebugStatus.PARTIAL def test_parse_canonicalizes_url(self, store, minimal_html): """Parse doit canonicaliser l'URL.""" url = "https://www.cdiscount.com/informatique/pc/f-10709-sku.html?idOffre=123#mpos=2" snapshot = store.parse(minimal_html, url) assert snapshot.url == "https://www.cdiscount.com/informatique/pc/f-10709-sku.html" assert "?" not in snapshot.url assert "#" not in snapshot.url def test_parse_extracts_reference_from_url(self, store, minimal_html): """Parse doit extraire le SKU depuis l'URL.""" url = "https://www.cdiscount.com/informatique/pc/f-10709-tuf608umrv004.html" snapshot = store.parse(minimal_html, url) assert snapshot.reference == "10709-tuf608umrv004" def test_parse_sets_fetched_at(self, store, minimal_html): """Parse doit définir fetched_at.""" url = "https://www.cdiscount.com/informatique/pc/f-10709-sku.html" snapshot = store.parse(minimal_html, url) assert snapshot.fetched_at is not None def test_parse_partial_status_without_title(self, store): """Parse sans titre doit avoir status PARTIAL.""" html = """
299,99 €
""" url = "https://www.cdiscount.com/informatique/pc/f-10709-sku.html" snapshot = store.parse(html, url) assert snapshot.debug.status == DebugStatus.PARTIAL assert snapshot.title is None assert snapshot.price == 299.99 def test_parse_partial_status_without_price(self, store): """Parse sans prix doit avoir status PARTIAL.""" html = """

Test Product

""" url = "https://www.cdiscount.com/informatique/pc/f-10709-sku.html" snapshot = store.parse(html, url) assert snapshot.debug.status == DebugStatus.PARTIAL assert snapshot.title == "Test Product" assert snapshot.price is None def test_parse_price_with_comma_separator(self, store): """Parse doit gérer les prix avec virgule (format français).""" html = """

Test

1199,99 €
""" url = "https://www.cdiscount.com/informatique/pc/f-10709-sku.html" snapshot = store.parse(html, url) assert snapshot.price == 1199.99 def test_parse_price_with_dot_separator(self, store): """Parse doit gérer les prix avec point (format international).""" html = """

Test

1199.99 €
""" url = "https://www.cdiscount.com/informatique/pc/f-10709-sku.html" snapshot = store.parse(html, url) assert snapshot.price == 1199.99 class TestCdiscountStoreInit: """Tests de l'initialisation du store Cdiscount.""" def test_store_id(self): """Le store_id doit être 'cdiscount'.""" store = CdiscountStore() assert store.store_id == "cdiscount" def test_selectors_loaded(self): """Les sélecteurs doivent être chargés depuis selectors.yml.""" store = CdiscountStore() assert isinstance(store.selectors, dict) assert len(store.selectors) > 0 def test_repr(self): """Test de la représentation string.""" store = CdiscountStore() repr_str = repr(store) assert "CdiscountStore" in repr_str assert "cdiscount" in repr_str