from datetime import date from fastapi.testclient import TestClient from sqlalchemy import create_engine from sqlalchemy.orm import Session, sessionmaker from app.core.config import settings from app.db.session import Base from app.main import app from app.models.assumption import FreightCostRule, PackagingCostRule, ProcessCostRule from app.models.mix import Mix, MixIngredient from app.models.product import Product from app.models.raw_material import RawMaterial, RawMaterialPriceVersion from app.services.costing_engine import calculate_mix_cost, calculate_product_cost, calculate_raw_material_cost def build_session() -> Session: engine = create_engine("sqlite:///:memory:") Base.metadata.create_all(bind=engine) session = sessionmaker(bind=engine, expire_on_commit=False)() return session def test_calculate_raw_material_cost(): raw_material = RawMaterial(name="Maize", unit_of_measure="tonne", kg_per_unit=1000, status="active") price = RawMaterialPriceVersion(market_value=500, waste_percentage=0.02, effective_date=date(2026, 4, 1)) result = calculate_raw_material_cost(raw_material, price) assert result.loss_cost == 10.0 assert result.cost_per_unit == 510.0 assert result.cost_per_kg == 0.51 def test_mix_and_product_cost_breakdown(): db = build_session() maize = RawMaterial(name="Maize", unit_of_measure="tonne", kg_per_unit=1000, status="active") maize.price_versions.append(RawMaterialPriceVersion(market_value=520, waste_percentage=0.02, effective_date=date(2026, 4, 1))) barley = RawMaterial(name="Barley", unit_of_measure="tonne", kg_per_unit=1000, status="active") barley.price_versions.append(RawMaterialPriceVersion(market_value=470, waste_percentage=0.015, effective_date=date(2026, 4, 1))) db.add_all([maize, barley]) db.flush() mix = Mix(client_name="Specialty Feeds", name="Pigeon Mix", status="active", version=1) db.add(mix) db.flush() db.add_all( [ MixIngredient(mix_id=mix.id, raw_material_id=maize.id, quantity_kg=180), MixIngredient(mix_id=mix.id, raw_material_id=barley.id, quantity_kg=100), ] ) db.add(ProcessCostRule(process_name="standard_bagging", grading_cost=0.055, bagging_cost=0.04, cracking_cost=0.0)) db.add(PackagingCostRule(sale_type="standard", unit_of_measure="20kg bag", own_bag=False, bag_cost=0.63)) db.add(FreightCostRule(sale_type="standard", unit_of_measure="20kg bag", cost_per_unit=1.45)) db.flush() product = Product( client_name="Specialty Feeds", name="Specialty Pigeon Breeder 20kg", mix_id=mix.id, sale_type="standard", own_bag=False, unit_of_measure="20kg bag", items_per_pallet=50, bagging_process="standard_bagging", distributor_margin=0.225, wholesale_margin=0.18, ) db.add(product) db.commit() mix_result = calculate_mix_cost(db, mix.id) product_result = calculate_product_cost(db, product.id) assert mix_result["total_mix_kg"] == 280 assert mix_result["mix_cost_per_kg"] == 0.5114 assert product_result["finished_product_delivered"] == 14.208 assert product_result["distributor_price"] == 18.3329 assert product_result["wholesale_price"] == 17.3268 def test_root_and_login_endpoints(): client = TestClient(app) root_response = client.get("/") assert root_response.status_code == 200 assert root_response.json()["endpoints"]["login"] == "/api/auth/login" login_response = client.post( "/api/auth/login", json={"email": settings.operator_email, "password": settings.operator_password}, ) assert login_response.status_code == 200 assert login_response.json()["email"] == settings.operator_email