BuyingAgent/backend/tests/e2e/test_http_walkthrough.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

205 lines
7.8 KiB
Python

import json
from pathlib import Path
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import text
from app.main import app
from app.persistence.db import SessionLocal, engine
from scripts.bootstrap import run_bootstrap
pytestmark = [pytest.mark.e2e, pytest.mark.integration]
ROOT = Path(__file__).resolve().parents[2]
PROFILE = json.loads((ROOT / "profiles" / "mobile-development-notebook.json").read_text(encoding="utf-8"))
FIXTURE = json.loads((ROOT / "fixtures" / "manual-demo.json").read_text(encoding="utf-8"))
AS_OF = "2026-10-03T12:00:00Z"
DEFINITIONS = [
{"key": "ram_gb", "value_type": "number", "canonical_unit": "GB", "minimum": 0},
{"key": "storage_gb", "value_type": "number", "canonical_unit": "GB", "minimum": 0},
{"key": "weight_kg", "value_type": "number", "canonical_unit": "kg", "minimum": 0},
{"key": "battery_score", "value_type": "number", "minimum": 0, "maximum": 100},
{"key": "display_score", "value_type": "number", "minimum": 0, "maximum": 100},
{"key": "cpu_score", "value_type": "number", "minimum": 0, "maximum": 100},
]
@pytest.fixture(scope="module", autouse=True)
def require_postgres() -> None:
try:
with engine.connect() as connection:
connection.execute(text("SELECT 1"))
except Exception as exc:
pytest.skip(f"PostgreSQL not available: {exc}")
session = SessionLocal()
try:
run_bootstrap(session)
finally:
session.close()
@pytest.fixture
def client() -> TestClient:
return TestClient(app)
def _ok(response, allowed=(200, 201, 409)) -> None:
assert response.status_code in allowed, response.text
def _seed(client: TestClient) -> None:
for definition in DEFINITIONS:
_ok(client.post("/api/v1/attribute-definitions", json=definition))
_ok(client.post("/api/v1/profiles", json=PROFILE))
product = FIXTURE["products"][0]
_ok(
client.post(
"/api/v1/products",
json={
"id": product["id"],
"category": product["category"],
"brand": product["brand"],
"name": product["name"],
"model": product.get("model"),
},
)
)
for variant in product["variants"]:
_ok(
client.post(
f"/api/v1/products/{product['id']}/variants",
json={
"id": variant["id"],
"name": variant["name"],
"manufacturer_sku": variant.get("manufacturer_sku"),
"identifiers": variant.get("identifiers") or {},
},
)
)
for attribute in variant["attributes"]:
_ok(client.post(f"/api/v1/variants/{variant['id']}/attributes", json={**attribute, "source_id": "manual"}))
for offer in FIXTURE["offers"]:
_ok(
client.post(
f"/api/v1/variants/{offer['variant_id']}/offers",
json={
"id": offer["id"],
"source_id": "manual",
"external_id": offer["external_id"],
"seller": offer["seller"],
"url": offer.get("url"),
"market": offer["market"],
"currency": offer["currency"],
"condition": offer["condition"],
},
)
)
for price in FIXTURE["prices"]:
offer = next(item for item in FIXTURE["offers"] if item["external_id"] == price["external_offer_id"])
_ok(
client.post(
f"/api/v1/offers/{offer['id']}/prices",
json={
"id": price["id"],
"source_id": "manual",
"external_observation_id": price["external_observation_id"],
"observed_at": price["observed_at"],
"item_price": price["item_price"],
"shipping_price": price["shipping_price"],
"currency": price["currency"],
"availability": price["availability"],
"raw_payload": price["raw_payload"],
"confidence": price["confidence"],
},
),
allowed=(200, 201),
)
def test_http_cases_and_replay(client: TestClient) -> None:
_seed(client)
cases = {
"A": ("22222222-2222-4222-8222-222222222222", "33333333-3333-4333-8333-333333333333"),
"B": ("22222222-2222-4222-8222-22222222222b", "33333333-3333-4333-8333-33333333333b"),
"C": ("22222222-2222-4222-8222-22222222222c", "33333333-3333-4333-8333-33333333333c"),
"D": ("22222222-2222-4222-8222-222222222222", "33333333-3333-4333-8333-33333333333d"),
}
created = {}
for label, (variant_id, offer_id) in cases.items():
response = client.post(
f"/api/v1/evaluate/{variant_id}",
json={
"profile_id": "mobile-development-notebook",
"profile_version": 1,
"offer_id": offer_id,
"as_of": AS_OF,
},
)
assert response.status_code == 201, response.text
created[label] = response.json()
assert created["A"]["product_score"] == 90.21
assert created["A"]["price_score"] == 98.24
assert created["A"]["deal_score"] == 93.42
assert created["A"]["classification"] == "EXCEPTIONAL"
assert created["A"]["eligible"] is True
assert created["A"]["price_reference_scope"] == "offer"
assert created["B"]["product_eligibility"] == "INELIGIBLE"
assert created["B"]["offer_eligibility"] == "ELIGIBLE"
assert created["B"]["deal_score"] is None
assert created["B"]["product_score"] is not None
assert created["C"]["product_eligibility"] == "UNKNOWN"
assert created["C"]["classification"] == "INSUFFICIENT_DATA"
assert created["D"]["product_eligibility"] == "ELIGIBLE"
assert created["D"]["offer_eligibility"] == "INELIGIBLE"
assert created["D"]["product_score"] == 90.21
assert created["D"]["price_score"] is not None
assert created["D"]["deal_score"] is None
stored = client.get(f"/api/v1/evaluations/{created['A']['id']}")
assert stored.status_code == 200
assert stored.json()["deal_score"] == created["A"]["deal_score"]
assert stored.json()["input_snapshot"] == created["A"]["input_snapshot"]
extra = client.post(
"/api/v1/offers/33333333-3333-4333-8333-333333333333/prices",
json={
"source_id": "manual",
"external_observation_id": "a-later",
"observed_at": "2026-10-03T11:00:00Z",
"item_price": "999.00",
"shipping_price": "0.00",
"currency": "EUR",
"availability": "in_stock",
"raw_payload": {"total": "999.00"},
"confidence": 1.0,
},
)
assert extra.status_code == 201
replay = client.get(f"/api/v1/evaluations/{created['A']['id']}")
assert replay.json()["deal_score"] == created["A"]["deal_score"]
assert replay.json()["price_statistics"]["current_total"] == created["A"]["price_statistics"]["current_total"]
def test_error_contracts(client: TestClient) -> None:
missing = client.get("/api/v1/products/00000000-0000-4000-8000-000000000000")
assert missing.status_code == 404
assert "error" in missing.json()
assert missing.json()["error"]["code"] == "NOT_FOUND"
bad = client.post("/api/v1/products", json={"category": "notebook"})
assert bad.status_code == 422
assert "error" in bad.json()
client.post("/api/v1/attribute-definitions", json={"key": "ports", "value_type": "string_list"})
again = client.post("/api/v1/attribute-definitions", json={"key": "ports", "value_type": "string_list"})
assert again.status_code == 409
no_version = client.get("/api/v1/profiles/mobile-development-notebook")
assert no_version.status_code == 422