"""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) == []