| import pytest |
| from unittest.mock import patch |
|
|
| from app.recs.rules import generate_recommendations |
| from app.recs.generate import generate_explanation |
| from app.db.repo import init_db, SessionLocal |
| from app.db.models import Campaign, Recommendation |
|
|
|
|
| |
| |
| |
| @pytest.fixture |
| def session(): |
| init_db() |
| db = SessionLocal() |
| yield db |
| db.close() |
|
|
|
|
| |
| |
| |
| def seed_campaign_metrics(session): |
| campaigns = [ |
| Campaign( |
| google_campaign_id="c1", |
| name="High CPL Campaign", |
| budget=100, |
| spend=500, |
| clicks=100, |
| impressions=2000, |
| ctr=5.0, |
| leads=5, |
| cpl=100.0, |
| ), |
| Campaign( |
| google_campaign_id="c2", |
| name="Low CPL Campaign", |
| budget=100, |
| spend=200, |
| clicks=150, |
| impressions=3000, |
| ctr=5.0, |
| leads=20, |
| cpl=10.0, |
| ), |
| Campaign( |
| google_campaign_id="c3", |
| name="Low CTR Campaign", |
| budget=100, |
| spend=300, |
| clicks=20, |
| impressions=3000, |
| ctr=1.0, |
| leads=5, |
| cpl=60.0, |
| ), |
| ] |
|
|
| session.add_all(campaigns) |
| session.commit() |
|
|
| return campaigns |
|
|
|
|
| |
| |
| |
| def extract_metrics(session): |
| campaigns = session.query(Campaign).all() |
|
|
| return [ |
| { |
| "campaign_id": c.google_campaign_id, |
| "cpl": c.cpl, |
| "ctr": c.ctr, |
| } |
| for c in campaigns |
| ] |
|
|
|
|
| |
| |
| |
| def test_e2e_pipeline(session): |
|
|
| |
| seed_campaign_metrics(session) |
|
|
| metrics = extract_metrics(session) |
|
|
| |
| recs = generate_recommendations(metrics) |
|
|
| assert len(recs) > 0, "Rule engine returned no recommendations" |
|
|
| |
| assert any(r["type"] == "high_cpl" for r in recs) |
| assert any(r["type"] == "low_ctr" for r in recs) |
|
|
| |
| def fake_llm_response(rec): |
| return f"Mock explanation for {rec['campaign_id']}" |
|
|
| with patch("app.recs.generate.load_model", return_value=None), \ |
| patch("app.recs.generate.generate_explanation") as mocked: |
|
|
| mocked.side_effect = fake_llm_response |
|
|
| enriched = [ |
| { |
| **r, |
| "explanation": generate_explanation(r) |
| } |
| for r in recs |
| ] |
|
|
| |
| assert len(enriched) == len(recs) |
|
|
| for e in enriched: |
| assert "explanation" in e |
| assert e["explanation"] is not None |
| assert isinstance(e["explanation"], str) |