mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-20 23:21:06 +02:00
test(amazon): cover orchestrator flows
This commit is contained in:
parent
a7e854c815
commit
f1ee166f19
1 changed files with 199 additions and 0 deletions
199
surfsense_backend/tests/unit/platforms/amazon/test_flows.py
Normal file
199
surfsense_backend/tests/unit/platforms/amazon/test_flows.py
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from app.proprietary.platforms.amazon import AmazonScrapeInput, scrape_products, scraper
|
||||
from app.proprietary.platforms.amazon.fetch import FetchResult
|
||||
from app.proprietary.platforms.amazon.url_resolver import resolve_url
|
||||
|
||||
_FIXTURES = Path(__file__).parent / "fixtures"
|
||||
|
||||
|
||||
def _fixture(name: str) -> str:
|
||||
return (_FIXTURES / name).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def _response(url: str, html: str, status: int = 200) -> FetchResult:
|
||||
return FetchResult(status=status, html=html, url=url, cookies={})
|
||||
|
||||
|
||||
async def test_product_flow_emits_parsed_item(monkeypatch):
|
||||
async def fetch_page(url: str, **_kwargs):
|
||||
return _response(url, _fixture("product.html"))
|
||||
|
||||
monkeypatch.setattr(scraper, "fetch_page", fetch_page)
|
||||
items = await scrape_products(
|
||||
AmazonScrapeInput(
|
||||
categoryOrProductUrls=[{"url": "https://www.amazon.com/dp/B09V3KXJPB"}]
|
||||
)
|
||||
)
|
||||
|
||||
assert len(items) == 1
|
||||
assert items[0]["asin"] == "B09V3KXJPB"
|
||||
assert items[0]["title"] == "Example Wireless Headphones"
|
||||
|
||||
|
||||
async def test_product_flow_maps_not_found_to_error_item(monkeypatch):
|
||||
async def fetch_page(url: str, **_kwargs):
|
||||
return _response(url, "", status=404)
|
||||
|
||||
monkeypatch.setattr(scraper, "fetch_page", fetch_page)
|
||||
items = await scrape_products(
|
||||
AmazonScrapeInput(
|
||||
categoryOrProductUrls=[{"url": "https://www.amazon.com/dp/B09V3KXJPB"}]
|
||||
)
|
||||
)
|
||||
|
||||
assert items[0]["error"] == "product_not_found"
|
||||
|
||||
|
||||
async def test_search_flow_honors_cap_and_stops_on_empty_page(monkeypatch):
|
||||
calls: list[str] = []
|
||||
|
||||
async def fetch_page(url: str, **_kwargs):
|
||||
calls.append(url)
|
||||
html = _fixture("search.html") if "page=1" in url else "<html></html>"
|
||||
return _response(url, html)
|
||||
|
||||
monkeypatch.setattr(scraper, "fetch_page", fetch_page)
|
||||
items = await scrape_products(
|
||||
AmazonScrapeInput(
|
||||
categoryOrProductUrls=[{"url": "https://www.amazon.com/s?k=headphones"}],
|
||||
maxItemsPerStartUrl=1,
|
||||
maxSearchPagesPerStartUrl=5,
|
||||
scrapeProductDetails=False,
|
||||
)
|
||||
)
|
||||
|
||||
assert len(items) == 1
|
||||
assert items[0]["categoryPageData"]["position"] == 1
|
||||
assert len(calls) == 1
|
||||
|
||||
|
||||
async def test_search_flow_returns_no_results_error(monkeypatch):
|
||||
async def fetch_page(url: str, **_kwargs):
|
||||
return _response(url, "<html></html>")
|
||||
|
||||
monkeypatch.setattr(scraper, "fetch_page", fetch_page)
|
||||
items = await scrape_products(
|
||||
AmazonScrapeInput(
|
||||
categoryOrProductUrls=[{"url": "https://www.amazon.com/s?k=missing"}],
|
||||
scrapeProductDetails=False,
|
||||
)
|
||||
)
|
||||
|
||||
assert items[0]["error"] == "no_results_found"
|
||||
|
||||
|
||||
async def test_product_flow_enriches_offers_and_seller(monkeypatch):
|
||||
async def fetch_page(url: str, **_kwargs):
|
||||
return _response(url, _fixture("product.html"))
|
||||
|
||||
async def fetch_aod_html(*_args, **_kwargs):
|
||||
return _fixture("offers.html")
|
||||
|
||||
async def fetch_seller_html(*_args, **_kwargs):
|
||||
return _fixture("seller.html")
|
||||
|
||||
monkeypatch.setattr(scraper, "fetch_page", fetch_page)
|
||||
monkeypatch.setattr(scraper, "fetch_aod_html", fetch_aod_html)
|
||||
monkeypatch.setattr(scraper, "fetch_seller_html", fetch_seller_html)
|
||||
items = await scrape_products(
|
||||
AmazonScrapeInput(
|
||||
categoryOrProductUrls=[{"url": "https://www.amazon.com/dp/B09V3KXJPB"}],
|
||||
maxOffers=1,
|
||||
scrapeSellers=True,
|
||||
)
|
||||
)
|
||||
|
||||
assert len(items[0]["offers"]) == 1
|
||||
assert items[0]["offers"][0]["seller"]["averageRating"] == 4.8
|
||||
assert items[0]["seller"]["reviewsCount"] == 1875
|
||||
|
||||
|
||||
async def test_bestsellers_card_only_flow(monkeypatch):
|
||||
async def fetch_page(url: str, **_kwargs):
|
||||
return _response(url, _fixture("bestsellers.html"))
|
||||
|
||||
monkeypatch.setattr(scraper, "fetch_page", fetch_page)
|
||||
items = await scrape_products(
|
||||
AmazonScrapeInput(
|
||||
categoryOrProductUrls=[
|
||||
{
|
||||
"url": (
|
||||
"https://www.amazon.com/Best-Sellers-Electronics/"
|
||||
"zgbs/electronics"
|
||||
)
|
||||
}
|
||||
],
|
||||
maxItemsPerStartUrl=2,
|
||||
scrapeProductDetails=False,
|
||||
)
|
||||
)
|
||||
|
||||
assert [item["bestsellerPageData"]["rank"] for item in items] == [1, 2]
|
||||
|
||||
|
||||
async def test_shortlink_resolves_and_dispatches(monkeypatch):
|
||||
async def resolve(_url: str):
|
||||
return "https://www.amazon.com/dp/B09V3KXJPB"
|
||||
|
||||
async def fetch_page(url: str, **_kwargs):
|
||||
return _response(url, _fixture("product.html"))
|
||||
|
||||
monkeypatch.setattr(scraper, "resolve_shortlink", resolve)
|
||||
monkeypatch.setattr(scraper, "fetch_page", fetch_page)
|
||||
items = await scrape_products(
|
||||
AmazonScrapeInput(categoryOrProductUrls=[{"url": "https://a.co/d/example"}])
|
||||
)
|
||||
|
||||
assert items[0]["asin"] == "B09V3KXJPB"
|
||||
assert items[0]["input"] == "https://a.co/d/example"
|
||||
|
||||
|
||||
async def test_product_flow_expands_variants_and_attaches_prices(monkeypatch):
|
||||
async def fetch_page(url: str, **_kwargs):
|
||||
return _response(url, _fixture("product.html"))
|
||||
|
||||
monkeypatch.setattr(scraper, "fetch_page", fetch_page)
|
||||
items = await scrape_products(
|
||||
AmazonScrapeInput(
|
||||
categoryOrProductUrls=[{"url": "https://www.amazon.com/dp/B09V3KXJPB"}],
|
||||
maxProductVariantsAsSeparateResults=1,
|
||||
scrapeProductVariantPrices=True,
|
||||
)
|
||||
)
|
||||
|
||||
assert [item["asin"] for item in items] == ["B09V3KXJPB", "B09V3KXJP1"]
|
||||
assert items[1]["originalAsin"] == "B09V3KXJPB"
|
||||
variant = next(
|
||||
detail
|
||||
for detail in items[0]["variantDetails"]
|
||||
if detail["asin"] == "B09V3KXJP1"
|
||||
)
|
||||
assert variant["price"]["value"] == 49.99
|
||||
|
||||
|
||||
async def test_route_outside_location_allowlist_skips_session_mint(monkeypatch):
|
||||
calls = 0
|
||||
|
||||
async def get_location_session(*_args, **_kwargs):
|
||||
nonlocal calls
|
||||
calls += 1
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(scraper, "get_location_session", get_location_session)
|
||||
resolved = resolve_url("https://www.amazon.com/dp/B09V3KXJPB")
|
||||
assert resolved is not None
|
||||
context = await scraper._location_context(
|
||||
resolved,
|
||||
AmazonScrapeInput(
|
||||
categoryOrProductUrls=[{"url": resolved.url}],
|
||||
zipCode="10001",
|
||||
locationDeliverableRoutes=["OFFERS"],
|
||||
),
|
||||
"PRODUCT",
|
||||
)
|
||||
|
||||
assert context == (None, None, None, None)
|
||||
assert calls == 0
|
||||
Loading…
Add table
Add a link
Reference in a new issue