diff --git a/surfsense_backend/tests/unit/capabilities/amazon/__init__.py b/surfsense_backend/tests/unit/capabilities/amazon/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/surfsense_backend/tests/unit/capabilities/amazon/__init__.py @@ -0,0 +1 @@ + diff --git a/surfsense_backend/tests/unit/capabilities/amazon/scrape/__init__.py b/surfsense_backend/tests/unit/capabilities/amazon/scrape/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/surfsense_backend/tests/unit/capabilities/amazon/scrape/__init__.py @@ -0,0 +1 @@ + diff --git a/surfsense_backend/tests/unit/capabilities/amazon/scrape/test_executor.py b/surfsense_backend/tests/unit/capabilities/amazon/scrape/test_executor.py new file mode 100644 index 000000000..806828f31 --- /dev/null +++ b/surfsense_backend/tests/unit/capabilities/amazon/scrape/test_executor.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +from app.capabilities.amazon.scrape.executor import build_scrape_executor +from app.capabilities.amazon.scrape.schemas import MAX_AMAZON_RESULTS, ScrapeInput +from app.proprietary.platforms.amazon import AmazonScrapeInput + + +class _FakeScraper: + def __init__(self) -> None: + self.calls: list[tuple[AmazonScrapeInput, int | None]] = [] + + async def __call__( + self, input_model: AmazonScrapeInput, *, limit: int | None = None + ) -> list[dict]: + self.calls.append((input_model, limit)) + return [{"asin": "B09V3KXJPB", "title": "Product"}] + + +async def test_executor_maps_agent_input_to_scraper_input(): + scraper = _FakeScraper() + execute = build_scrape_executor(scraper) + + output = await execute( + ScrapeInput( + search_terms=["wireless mouse"], + urls=["https://www.amazon.com/dp/B09V3KXJPB"], + max_items=5, + max_offers=2, + include_sellers=True, + zip_code="10001", + country_code="US", + ) + ) + + assert output.items[0].asin == "B09V3KXJPB" + input_model, limit = scraper.calls[0] + assert input_model.categoryOrProductUrls == [ + {"url": "https://www.amazon.com/dp/B09V3KXJPB"}, + {"url": "https://www.amazon.com/s?k=wireless+mouse"}, + ] + assert input_model.maxItemsPerStartUrl == 5 + assert input_model.maxOffers == 2 + assert input_model.scrapeSellers is True + assert input_model.zipCode == "10001" + assert limit == MAX_AMAZON_RESULTS diff --git a/surfsense_backend/tests/unit/capabilities/amazon/scrape/test_schemas.py b/surfsense_backend/tests/unit/capabilities/amazon/scrape/test_schemas.py new file mode 100644 index 000000000..34487ea2b --- /dev/null +++ b/surfsense_backend/tests/unit/capabilities/amazon/scrape/test_schemas.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +import pytest +from pydantic import ValidationError + +from app.capabilities.amazon.scrape.schemas import ( + MAX_AMAZON_RESULTS, + ScrapeInput, + ScrapeOutput, +) + + +def test_estimated_units_cover_search_and_direct_variant_fanout(): + payload = ScrapeInput( + search_terms=["mouse", "keyboard"], + urls=["https://www.amazon.com/dp/B09V3KXJPB"], + max_items=20, + max_variants=3, + ) + + assert payload.estimated_units == 44 + + +def test_estimated_units_respect_hard_run_ceiling(): + payload = ScrapeInput(search_terms=["x"] * 20, max_items=100) + assert payload.estimated_units == MAX_AMAZON_RESULTS + + +def test_at_least_one_source_is_required(): + with pytest.raises(ValidationError): + ScrapeInput() + + +def test_error_items_are_not_billable(): + output = ScrapeOutput( + items=[ + {"asin": "B09V3KXJPB", "title": "Product"}, + {"error": "product_not_found", "errorDescription": "Missing"}, + ] + ) + + assert output.billable_units == 1 diff --git a/surfsense_backend/tests/unit/capabilities/amazon/test_registry.py b/surfsense_backend/tests/unit/capabilities/amazon/test_registry.py new file mode 100644 index 000000000..bcebc1c60 --- /dev/null +++ b/surfsense_backend/tests/unit/capabilities/amazon/test_registry.py @@ -0,0 +1,13 @@ +from __future__ import annotations + +from app.capabilities.amazon.scrape.schemas import ScrapeInput, ScrapeOutput +from app.capabilities.core import BillingUnit +from app.capabilities.core.store import get_capability + + +def test_amazon_scrape_is_registered_and_metered(): + capability = get_capability("amazon.scrape") + + assert capability.input_schema is ScrapeInput + assert capability.output_schema is ScrapeOutput + assert capability.billing_unit is BillingUnit.AMAZON_PRODUCT