forked from cartsnitch/cartsnitch
226 lines
6.5 KiB
Python
226 lines
6.5 KiB
Python
"""Tests for Pydantic v2 schemas."""
|
|
|
|
import uuid
|
|
from datetime import UTC, date, datetime
|
|
from decimal import Decimal
|
|
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
from cartsnitch_common.constants import (
|
|
AccountStatus,
|
|
DiscountType,
|
|
EventType,
|
|
PriceSource,
|
|
ProductCategory,
|
|
SizeUnit,
|
|
StoreSlug,
|
|
)
|
|
from cartsnitch_common.schemas import (
|
|
CouponCreate,
|
|
EventEnvelope,
|
|
NormalizedProductCreate,
|
|
PriceHistoryCreate,
|
|
PurchaseCreate,
|
|
PurchaseItemCreate,
|
|
ShrinkflationEventCreate,
|
|
StoreCreate,
|
|
StoreLocationCreate,
|
|
StoreRead,
|
|
UserCreate,
|
|
UserStoreAccountCreate,
|
|
)
|
|
|
|
|
|
class TestStoreSchemas:
|
|
def test_store_create_valid(self):
|
|
s = StoreCreate(name="Meijer", slug=StoreSlug.MEIJER)
|
|
assert s.slug == StoreSlug.MEIJER
|
|
|
|
def test_store_create_invalid_slug(self):
|
|
with pytest.raises(ValidationError):
|
|
StoreCreate(name="Walmart", slug="walmart")
|
|
|
|
def test_store_read_from_attributes(self):
|
|
data = {
|
|
"id": uuid.uuid4(),
|
|
"name": "Kroger",
|
|
"slug": StoreSlug.KROGER,
|
|
"logo_url": None,
|
|
"website_url": None,
|
|
"created_at": datetime.now(UTC),
|
|
"updated_at": datetime.now(UTC),
|
|
}
|
|
s = StoreRead(**data)
|
|
assert s.slug == StoreSlug.KROGER
|
|
|
|
|
|
class TestStoreLocationSchemas:
|
|
def test_location_create(self):
|
|
loc = StoreLocationCreate(
|
|
store_id=uuid.uuid4(),
|
|
address="456 Oak Ave",
|
|
city="Detroit",
|
|
state="MI",
|
|
zip="48201",
|
|
)
|
|
assert loc.city == "Detroit"
|
|
|
|
|
|
class TestUserSchemas:
|
|
def test_user_create_valid(self):
|
|
u = UserCreate(email="test@example.com", password="secret123")
|
|
assert u.email == "test@example.com"
|
|
|
|
def test_user_create_invalid_email(self):
|
|
with pytest.raises(ValidationError):
|
|
UserCreate(email="not-an-email", password="secret123")
|
|
|
|
|
|
class TestUserStoreAccountSchemas:
|
|
def test_account_create_with_status(self):
|
|
a = UserStoreAccountCreate(
|
|
user_id=uuid.uuid4(),
|
|
store_id=uuid.uuid4(),
|
|
status=AccountStatus.EXPIRED,
|
|
)
|
|
assert a.status == AccountStatus.EXPIRED
|
|
|
|
def test_account_create_default_status(self):
|
|
a = UserStoreAccountCreate(
|
|
user_id=uuid.uuid4(),
|
|
store_id=uuid.uuid4(),
|
|
)
|
|
assert a.status == AccountStatus.ACTIVE
|
|
|
|
def test_account_create_invalid_status(self):
|
|
with pytest.raises(ValidationError):
|
|
UserStoreAccountCreate(
|
|
user_id=uuid.uuid4(),
|
|
store_id=uuid.uuid4(),
|
|
status="invalid_status",
|
|
)
|
|
|
|
|
|
class TestPurchaseSchemas:
|
|
def test_purchase_create_with_items(self):
|
|
p = PurchaseCreate(
|
|
user_id=uuid.uuid4(),
|
|
store_id=uuid.uuid4(),
|
|
receipt_id="RCP-001",
|
|
purchase_date=date(2026, 3, 15),
|
|
total=Decimal("42.50"),
|
|
items=[
|
|
PurchaseItemCreate(
|
|
product_name_raw="Milk",
|
|
unit_price=Decimal("3.49"),
|
|
extended_price=Decimal("3.49"),
|
|
),
|
|
],
|
|
)
|
|
assert len(p.items) == 1
|
|
assert p.items[0].quantity == Decimal("1")
|
|
|
|
|
|
class TestNormalizedProductSchemas:
|
|
def test_product_create_with_enums(self):
|
|
p = NormalizedProductCreate(
|
|
canonical_name="Whole Milk, 1 Gallon",
|
|
category=ProductCategory.DAIRY,
|
|
size_unit=SizeUnit.FL_OZ,
|
|
upc_variants=["0041250000001"],
|
|
)
|
|
assert p.category == ProductCategory.DAIRY
|
|
|
|
def test_product_create_invalid_category(self):
|
|
with pytest.raises(ValidationError):
|
|
NormalizedProductCreate(
|
|
canonical_name="Test",
|
|
category="invalid_category",
|
|
)
|
|
|
|
|
|
class TestPriceHistorySchemas:
|
|
def test_price_create(self):
|
|
p = PriceHistoryCreate(
|
|
normalized_product_id=uuid.uuid4(),
|
|
store_id=uuid.uuid4(),
|
|
observed_date=date(2026, 3, 15),
|
|
regular_price=Decimal("4.99"),
|
|
source=PriceSource.RECEIPT,
|
|
)
|
|
assert p.source == PriceSource.RECEIPT
|
|
|
|
def test_price_create_invalid_source(self):
|
|
with pytest.raises(ValidationError):
|
|
PriceHistoryCreate(
|
|
normalized_product_id=uuid.uuid4(),
|
|
store_id=uuid.uuid4(),
|
|
observed_date=date(2026, 3, 15),
|
|
regular_price=Decimal("4.99"),
|
|
source="invalid_source",
|
|
)
|
|
|
|
|
|
class TestCouponSchemas:
|
|
def test_coupon_create(self):
|
|
c = CouponCreate(
|
|
store_id=uuid.uuid4(),
|
|
title="BOGO Chips",
|
|
discount_type=DiscountType.BOGO,
|
|
)
|
|
assert c.discount_type == DiscountType.BOGO
|
|
|
|
def test_coupon_create_invalid_discount_type(self):
|
|
with pytest.raises(ValidationError):
|
|
CouponCreate(
|
|
store_id=uuid.uuid4(),
|
|
title="Test",
|
|
discount_type="free_stuff",
|
|
)
|
|
|
|
|
|
class TestShrinkflationEventSchemas:
|
|
def test_shrinkflation_create(self):
|
|
s = ShrinkflationEventCreate(
|
|
normalized_product_id=uuid.uuid4(),
|
|
detected_date=date(2026, 3, 10),
|
|
old_size="18",
|
|
new_size="15.4",
|
|
old_unit=SizeUnit.OZ,
|
|
new_unit=SizeUnit.OZ,
|
|
confidence=Decimal("0.95"),
|
|
)
|
|
assert s.old_unit == SizeUnit.OZ
|
|
|
|
def test_shrinkflation_create_invalid_unit(self):
|
|
with pytest.raises(ValidationError):
|
|
ShrinkflationEventCreate(
|
|
normalized_product_id=uuid.uuid4(),
|
|
detected_date=date(2026, 3, 10),
|
|
old_size="18",
|
|
new_size="15.4",
|
|
old_unit="bushels",
|
|
new_unit=SizeUnit.OZ,
|
|
)
|
|
|
|
|
|
class TestEventEnvelope:
|
|
def test_valid_event(self):
|
|
e = EventEnvelope(
|
|
event_type=EventType.RECEIPTS_INGESTED,
|
|
timestamp=datetime.now(UTC),
|
|
service="receiptwitness",
|
|
payload={"receipt_id": "RCP-001"},
|
|
)
|
|
assert e.event_type == EventType.RECEIPTS_INGESTED
|
|
|
|
def test_invalid_event_type(self):
|
|
with pytest.raises(ValidationError):
|
|
EventEnvelope(
|
|
event_type="invalid.event",
|
|
timestamp=datetime.now(UTC),
|
|
service="test",
|
|
payload={},
|
|
)
|