feat(backend): move product pricing to async persisted jobs

This commit is contained in:
Piotr Oleszczyk 2026-03-04 22:46:16 +01:00
parent c869f88db2
commit 0e439b4ca7
18 changed files with 468 additions and 67 deletions

View file

@ -1,8 +1,10 @@
import uuid
from innercontext.api import products as products_api
from innercontext.models import Product
from innercontext.models import PricingRecalcJob, Product
from innercontext.models.enums import DayTime, ProductCategory
from innercontext.services.pricing_jobs import process_one_pending_pricing_job
from sqlmodel import select
def _product(
@ -45,7 +47,7 @@ def test_compute_pricing_outputs_groups_by_category(monkeypatch):
assert cleanser_tiers[-1] == "luxury"
def test_price_tier_is_null_when_not_enough_products(client, monkeypatch):
def test_price_tier_is_null_when_not_enough_products(client, session, monkeypatch):
monkeypatch.setattr(products_api, "convert_to_pln", lambda amount, currency: amount)
base = {
@ -67,13 +69,15 @@ def test_price_tier_is_null_when_not_enough_products(client, monkeypatch):
)
assert response.status_code == 201
assert process_one_pending_pricing_job(session)
products = client.get("/products").json()
assert len(products) == 7
assert all(p["price_tier"] is None for p in products)
assert all(p["price_per_use_pln"] is not None for p in products)
def test_price_tier_is_computed_on_list(client, monkeypatch):
def test_price_tier_is_computed_by_worker(client, session, monkeypatch):
monkeypatch.setattr(products_api, "convert_to_pln", lambda amount, currency: amount)
base = {
@ -91,13 +95,15 @@ def test_price_tier_is_computed_on_list(client, monkeypatch):
)
assert response.status_code == 201
assert process_one_pending_pricing_job(session)
products = client.get("/products").json()
assert len(products) == 8
assert any(p["price_tier"] == "budget" for p in products)
assert any(p["price_tier"] == "luxury" for p in products)
def test_price_tier_uses_fallback_for_medium_categories(client, monkeypatch):
def test_price_tier_uses_fallback_for_medium_categories(client, session, monkeypatch):
monkeypatch.setattr(products_api, "convert_to_pln", lambda amount, currency: amount)
serum_base = {
@ -130,6 +136,8 @@ def test_price_tier_uses_fallback_for_medium_categories(client, monkeypatch):
)
assert response.status_code == 201
assert process_one_pending_pricing_job(session)
products = client.get("/products?category=toner").json()
assert len(products) == 5
assert all(p["price_tier"] is not None for p in products)
@ -137,7 +145,7 @@ def test_price_tier_uses_fallback_for_medium_categories(client, monkeypatch):
def test_price_tier_stays_null_for_tiny_categories_even_with_fallback_pool(
client, monkeypatch
client, session, monkeypatch
):
monkeypatch.setattr(products_api, "convert_to_pln", lambda amount, currency: amount)
@ -171,7 +179,27 @@ def test_price_tier_stays_null_for_tiny_categories_even_with_fallback_pool(
)
assert response.status_code == 201
assert process_one_pending_pricing_job(session)
oils = client.get("/products?category=oil").json()
assert len(oils) == 3
assert all(p["price_tier"] is None for p in oils)
assert all(p["price_tier_source"] == "insufficient_data" for p in oils)
def test_product_write_enqueues_pricing_job(client, session):
response = client.post(
"/products",
json={
"name": "Serum X",
"brand": "B",
"category": "serum",
"recommended_time": "both",
"leave_on": True,
},
)
assert response.status_code == 201
jobs = session.exec(select(PricingRecalcJob)).all()
assert len(jobs) == 1
assert jobs[0].status in {"pending", "running", "succeeded"}