Files
cartsnitch-fork-test/common/tests/test_pipeline_price.py
T

283 lines
8.6 KiB
Python

"""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