"""Tests for price history tracking pipeline.""" import uuid from datetime import UTC, date, datetime from decimal import Decimal from cartsnitch_common.constants import PriceSource, StoreSlug from cartsnitch_common.models.price import PriceHistory from cartsnitch_common.models.product import NormalizedProduct from cartsnitch_common.models.store import Store from cartsnitch_common.pipeline.price_tracking import ( PriceDelta, get_latest_price, get_price_trend, record_price_from_item, ) def _make_store(session, slug=StoreSlug.MEIJER) -> Store: store = Store( id=uuid.uuid4(), name="Meijer", slug=slug, created_at=datetime.now(UTC), updated_at=datetime.now(UTC), ) session.add(store) session.flush() return store def _make_product(session, name="Test Product") -> NormalizedProduct: product = NormalizedProduct( id=uuid.uuid4(), canonical_name=name, created_at=datetime.now(UTC), updated_at=datetime.now(UTC), ) session.add(product) session.flush() return product class TestGetLatestPrice: def test_no_history(self, session): product = _make_product(session) store = _make_store(session) result = get_latest_price(session, product.id, store.id) assert result is None def test_returns_newest(self, session): product = _make_product(session) store = _make_store(session) # Add two entries old = PriceHistory( id=uuid.uuid4(), normalized_product_id=product.id, store_id=store.id, observed_date=date(2026, 3, 1), regular_price=Decimal("3.99"), source=PriceSource.RECEIPT, ) new = PriceHistory( id=uuid.uuid4(), normalized_product_id=product.id, store_id=store.id, observed_date=date(2026, 3, 10), regular_price=Decimal("4.29"), source=PriceSource.RECEIPT, ) session.add_all([old, new]) session.flush() result = get_latest_price(session, product.id, store.id) assert result is not None assert result.regular_price == Decimal("4.29") class TestRecordPriceFromItem: def test_first_price_no_delta(self, session): product = _make_product(session) store = _make_store(session) entry, delta = record_price_from_item( session, product_id=product.id, store_id=store.id, observed_date=date(2026, 3, 15), regular_price=Decimal("3.99"), ) assert entry is not None assert entry.regular_price == Decimal("3.99") assert entry.source == PriceSource.RECEIPT assert delta is None def test_price_increase_detected(self, session): product = _make_product(session) store = _make_store(session) # First price record_price_from_item( session, product_id=product.id, store_id=store.id, observed_date=date(2026, 3, 1), regular_price=Decimal("3.99"), ) # Price increase entry, delta = record_price_from_item( session, product_id=product.id, store_id=store.id, observed_date=date(2026, 3, 15), regular_price=Decimal("4.49"), ) assert delta is not None assert delta.old_price == Decimal("3.99") assert delta.new_price == Decimal("4.49") assert delta.change_amount == Decimal("0.50") assert delta.is_increase is True assert delta.is_decrease is False assert delta.change_percent > Decimal("0") def test_price_decrease_detected(self, session): product = _make_product(session) store = _make_store(session) record_price_from_item( session, product_id=product.id, store_id=store.id, observed_date=date(2026, 3, 1), regular_price=Decimal("5.00"), ) _, delta = record_price_from_item( session, product_id=product.id, store_id=store.id, observed_date=date(2026, 3, 15), regular_price=Decimal("4.00"), ) assert delta is not None assert delta.is_decrease is True assert delta.change_amount == Decimal("-1.00") def test_same_price_no_delta(self, session): product = _make_product(session) store = _make_store(session) record_price_from_item( session, product_id=product.id, store_id=store.id, observed_date=date(2026, 3, 1), regular_price=Decimal("3.99"), ) _, delta = record_price_from_item( session, product_id=product.id, store_id=store.id, observed_date=date(2026, 3, 15), regular_price=Decimal("3.99"), ) assert delta is None def test_sale_and_loyalty_prices_recorded(self, session): product = _make_product(session) store = _make_store(session) entry, _ = record_price_from_item( session, product_id=product.id, store_id=store.id, observed_date=date(2026, 3, 15), regular_price=Decimal("5.99"), sale_price=Decimal("4.99"), loyalty_price=Decimal("4.49"), coupon_price=Decimal("3.99"), ) assert entry.sale_price == Decimal("4.99") assert entry.loyalty_price == Decimal("4.49") assert entry.coupon_price == Decimal("3.99") def test_custom_source(self, session): product = _make_product(session) store = _make_store(session) entry, _ = record_price_from_item( session, product_id=product.id, store_id=store.id, observed_date=date(2026, 3, 15), regular_price=Decimal("3.99"), source=PriceSource.CATALOG, ) assert entry.source == PriceSource.CATALOG class TestGetPriceTrend: def test_empty_trend(self, session): product = _make_product(session) store = _make_store(session) trend = get_price_trend(session, product.id, store.id) assert trend == [] def test_returns_newest_first(self, session): product = _make_product(session) store = _make_store(session) for day in [1, 5, 10, 15]: session.add( PriceHistory( id=uuid.uuid4(), normalized_product_id=product.id, store_id=store.id, observed_date=date(2026, 3, day), regular_price=Decimal(str(3 + day * 0.1)), source=PriceSource.RECEIPT, ) ) session.flush() trend = get_price_trend(session, product.id, store.id) assert len(trend) == 4 assert trend[0].observed_date == date(2026, 3, 15) assert trend[-1].observed_date == date(2026, 3, 1) def test_respects_limit(self, session): product = _make_product(session) store = _make_store(session) for day in range(1, 11): session.add( PriceHistory( id=uuid.uuid4(), normalized_product_id=product.id, store_id=store.id, observed_date=date(2026, 3, day), regular_price=Decimal("3.99"), source=PriceSource.RECEIPT, ) ) session.flush() trend = get_price_trend(session, product.id, store.id, limit=3) assert len(trend) == 3 class TestPriceDelta: def test_delta_properties(self): delta = PriceDelta( product_id=uuid.uuid4(), store_id=uuid.uuid4(), old_price=Decimal("3.99"), new_price=Decimal("4.49"), change_amount=Decimal("0.50"), change_percent=Decimal("12.53"), old_date=date(2026, 3, 1), new_date=date(2026, 3, 15), ) assert delta.is_increase is True assert delta.is_decrease is False def test_decrease_properties(self): delta = PriceDelta( product_id=uuid.uuid4(), store_id=uuid.uuid4(), old_price=Decimal("4.49"), new_price=Decimal("3.99"), change_amount=Decimal("-0.50"), change_percent=Decimal("-11.14"), old_date=date(2026, 3, 1), new_date=date(2026, 3, 15), ) assert delta.is_decrease is True assert delta.is_increase is False