Fix: Throughput API v1 available - Details posted to Irving. POWERBI_KEY was missing from the .ENV file, so was not live. Add: Editor now supports editing a mix's resolved formula directly, with % and kg dual entry on ingredient rows Fix: Mix Editor should bring through correct ingredients. New resolved formula (same logic we use in Mix Calculator). Fix: Security headers on all API responses (hardening) Add: New mix button available on the Mix Editor. Add: New ingredient button available on the Ingredient Editor
89 lines
2.7 KiB
Python
89 lines
2.7 KiB
Python
"""The change log records who edited a mix/ingredient and what changed.
|
|
|
|
Covers `record_change` / `diff_fields` / `list_changes`: edits are stored with a
|
|
field-level before/after diff and read back newest-first per entity.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import Session, sessionmaker
|
|
|
|
from app.api.deps import AuthSession
|
|
from app.db.session import Base
|
|
from app.services.change_log import (
|
|
ENTITY_INGREDIENT,
|
|
ENTITY_MIX,
|
|
diff_fields,
|
|
list_changes,
|
|
record_change,
|
|
)
|
|
|
|
TENANT = "hunter-premium-produce"
|
|
|
|
LABELS = {"name": "Name", "kg_per_unit": "Kg per unit", "status": "Status"}
|
|
|
|
|
|
def _session() -> Session:
|
|
engine = create_engine("sqlite:///:memory:")
|
|
Base.metadata.create_all(bind=engine)
|
|
return sessionmaker(bind=engine, expire_on_commit=False)()
|
|
|
|
|
|
def _actor() -> AuthSession:
|
|
return AuthSession(role="internal", email="lara@hunter.test", name="Lara", tenant_id=TENANT, client_role="admin")
|
|
|
|
|
|
def test_diff_fields_only_emits_changed_keys():
|
|
deltas = diff_fields({"name": "Maize", "kg_per_unit": 25.0}, {"name": "Maize", "kg_per_unit": 30.0}, LABELS)
|
|
assert len(deltas) == 1
|
|
assert deltas[0]["field"] == "kg_per_unit"
|
|
assert deltas[0]["before"] == "25"
|
|
assert deltas[0]["after"] == "30"
|
|
|
|
|
|
def test_records_and_lists_changes_newest_first():
|
|
db = _session()
|
|
session = _actor()
|
|
|
|
record_change(
|
|
db,
|
|
session=session,
|
|
entity_type=ENTITY_INGREDIENT,
|
|
entity_id=7,
|
|
action="created",
|
|
summary="Created ingredient “Maize”",
|
|
)
|
|
record_change(
|
|
db,
|
|
session=session,
|
|
entity_type=ENTITY_INGREDIENT,
|
|
entity_id=7,
|
|
action="updated",
|
|
summary="Updated Kg per unit",
|
|
changes=diff_fields({"kg_per_unit": 25.0}, {"kg_per_unit": 30.0}, LABELS),
|
|
)
|
|
# A different entity must not leak into entity 7's history.
|
|
record_change(db, session=session, entity_type=ENTITY_MIX, entity_id=7, action="created", summary="Created mix")
|
|
db.commit()
|
|
|
|
events = list_changes(db, tenant_id=TENANT, entity_type=ENTITY_INGREDIENT, entity_id=7)
|
|
assert [event.action for event in events] == ["updated", "created"]
|
|
assert events[0].actor_name == "Lara"
|
|
assert events[0].actor_role == "admin"
|
|
assert events[0].changes[0]["label"] == "Kg per unit"
|
|
|
|
|
|
def test_changes_are_tenant_scoped():
|
|
db = _session()
|
|
record_change(
|
|
db,
|
|
session=AuthSession(role="internal", email="x@y.test", name="X", tenant_id="other-tenant"),
|
|
entity_type=ENTITY_MIX,
|
|
entity_id=1,
|
|
action="created",
|
|
summary="Created mix",
|
|
)
|
|
db.commit()
|
|
|
|
assert list_changes(db, tenant_id=TENANT, entity_type=ENTITY_MIX, entity_id=1) == []
|