BuyingAgent/backend/tests/conftest.py
Lars b276758ee1
All checks were successful
Deploy Development / deploy (push) Successful in 1m19s
Test Suite / pytest-backend (push) Successful in 31s
Test Suite / smoke-dev (push) Successful in 0s
Test Suite / frontend-checks (push) Successful in 28s
Stelle den Frontend-MVP und die v0.3-API auf develop bereit, damit Dev API und PWA zusammen deployen.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-04 13:04:59 +02:00

151 lines
5.2 KiB
Python

import json
from datetime import datetime, timezone
from decimal import Decimal
from pathlib import Path
from uuid import UUID, uuid4
import pytest
from app.domain.enums import Availability, NormalizationStatus, OfferCondition, ValueType
from app.domain.models import (
AttributeDefinition,
AttributeStatement,
Offer,
PriceObservation,
Product,
ProductVariant,
)
from app.domain.validation import parse_profile, parse_scoring_model
ROOT = Path(__file__).resolve().parents[1]
AS_OF = datetime(2026, 10, 3, 12, 0, tzinfo=timezone.utc)
CUTOFF = datetime(2026, 10, 3, 12, 0, tzinfo=timezone.utc)
@pytest.fixture
def scoring_model():
definition = json.loads((ROOT / "rules" / "generic-v1.json").read_text(encoding="utf-8"))
return parse_scoring_model(definition)
@pytest.fixture
def definitions() -> dict[str, AttributeDefinition]:
return {
"ram_gb": AttributeDefinition("ram_gb", ValueType.NUMBER, "GB", Decimal("0"), None, None),
"storage_gb": AttributeDefinition("storage_gb", ValueType.NUMBER, "GB", Decimal("0"), None, None),
"weight_kg": AttributeDefinition("weight_kg", ValueType.NUMBER, "kg", Decimal("0"), None, None),
"battery_score": AttributeDefinition("battery_score", ValueType.NUMBER, None, Decimal("0"), Decimal("100"), None),
"display_score": AttributeDefinition("display_score", ValueType.NUMBER, None, Decimal("0"), Decimal("100"), None),
"cpu_score": AttributeDefinition("cpu_score", ValueType.NUMBER, None, Decimal("0"), Decimal("100"), None),
"ports": AttributeDefinition("ports", ValueType.STRING_LIST, None, None, None, None),
"color": AttributeDefinition("color", ValueType.STRING, None, None, None, None),
"has_lte": AttributeDefinition("has_lte", ValueType.BOOLEAN, None, None, None, None),
}
@pytest.fixture
def profile(scoring_model, definitions):
raw = json.loads((ROOT / "profiles" / "mobile-development-notebook.json").read_text(encoding="utf-8"))
return parse_profile(
raw,
attribute_definitions=definitions,
scoring_versions={scoring_model.version},
supported_strategies=set(scoring_model.supported_preference_strategies),
)
def utc(year, month, day, hour=10, minute=0) -> datetime:
return datetime(year, month, day, hour, minute, tzinfo=timezone.utc)
def statement(attribute: str, value, *, confidence="1.0", status="normalized", observed=None, ingested=None, row_id=None):
return AttributeStatement(
id=row_id or uuid4(),
variant_id=UUID("22222222-2222-4222-8222-222222222222"),
attribute=attribute,
raw_value=value,
normalized_value=None if status != "normalized" else value,
unit="GB" if attribute.endswith("_gb") else ("kg" if attribute.endswith("_kg") else None),
normalization_status=NormalizationStatus(status),
normalizer_version="manual-v1",
confidence=Decimal(str(confidence)),
source_id="manual",
source_reference=f"test/{attribute}",
observed_at=observed or utc(2026, 10, 3, 9),
ingested_at=ingested or utc(2026, 10, 3, 9, 1),
)
def product_a() -> Product:
return Product(
id=UUID("11111111-1111-4111-8111-111111111111"),
category="notebook",
brand="Example",
name="Example Mobile 13",
model="EM13",
created_at=utc(2026, 10, 1, 8),
)
def variant_a() -> ProductVariant:
return ProductVariant(
id=UUID("22222222-2222-4222-8222-222222222222"),
product_id=UUID("11111111-1111-4111-8111-111111111111"),
name="32/1024",
manufacturer_sku="EM13-32-1T",
identifiers={},
created_at=utc(2026, 10, 1, 8),
)
def offer_a() -> Offer:
return Offer(
id=UUID("33333333-3333-4333-8333-333333333333"),
variant_id=UUID("22222222-2222-4222-8222-222222222222"),
source_id="manual",
external_id="demo-offer-a",
seller="DemoShop",
url=None,
market="DE",
currency="EUR",
condition=OfferCondition.NEW,
created_at=utc(2026, 10, 1, 8),
)
def observation(day, total, *, offer_id=None, obs_id=None, availability="in_stock", confidence="1.0", hour=10):
return PriceObservation(
id=obs_id or uuid4(),
offer_id=offer_id or UUID("33333333-3333-4333-8333-333333333333"),
source_id="manual",
external_observation_id=str(obs_id or uuid4()),
observed_at=utc(2026, day[0], day[1], hour) if isinstance(day, tuple) else day,
ingested_at=utc(2026, 10, 3, 11),
item_price=Decimal(str(total)),
shipping_price=Decimal("0.00"),
currency="EUR",
availability=Availability(availability),
raw_payload={"total": str(total)},
confidence=Decimal(str(confidence)),
)
def case_a_attributes():
return (
statement("ram_gb", 32),
statement("storage_gb", 1024),
statement("weight_kg", Decimal("1.2")),
statement("battery_score", 90),
statement("display_score", 90),
statement("cpu_score", 90),
)
def case_a_prices():
return (
observation((9, 1), "1599.00"),
observation((9, 15), "1599.00"),
observation((10, 1), "1599.00"),
observation((10, 3), "1298.00"),
)