"""Tests that orch budget gating is wired into the suggest endpoint.""" from pathlib import Path from unittest.mock import MagicMock, patch import pytest from fastapi.testclient import TestClient from app.cloud_session import CloudUser, get_session from app.main import app def _make_session( tier: str = "paid", has_byok: bool = False, license_key: str | None = "CFG-KIWI-TEST-TEST-TEST", ) -> CloudUser: return CloudUser( user_id="test-user", tier=tier, db=Path("/tmp/kiwi_test.db"), has_byok=has_byok, license_key=license_key, ) @patch("app.api.endpoints.recipes._suggest_in_thread") @patch("app.services.heimdall_orch.requests.post") def test_orch_budget_exhausted_downgrades_to_l2(mock_post, mock_suggest): """When orch budget is denied, L3 request is downgraded to L2 and orch_fallback=True.""" deny_resp = MagicMock() deny_resp.ok = True deny_resp.json.return_value = { "allowed": False, "calls_used": 60, "calls_total": 60, "topup_calls": 0, "period_start": "2026-04-14", "resets_on": "2026-05-14", } mock_post.return_value = deny_resp fallback_result = MagicMock() fallback_result.suggestions = [] fallback_result.element_gaps = [] fallback_result.grocery_list = [] fallback_result.grocery_links = [] fallback_result.rate_limited = False fallback_result.rate_limit_count = 0 fallback_result.orch_fallback = False fallback_result.model_copy.return_value = fallback_result mock_suggest.return_value = fallback_result app.dependency_overrides[get_session] = lambda: _make_session(tier="paid") client = TestClient(app) resp = client.post("/api/v1/recipes/suggest", json={ "pantry_items": ["chicken", "rice"], "level": 3, "tier": "paid", }) app.dependency_overrides.clear() assert resp.status_code == 200 # The engine should have been called with level=2 (downgraded from 3) called_req = mock_suggest.call_args[0][1] assert called_req.level == 2 @patch("app.api.endpoints.recipes._suggest_in_thread") @patch("app.services.heimdall_orch.requests.post") def test_orch_budget_allowed_passes_l3_through(mock_post, mock_suggest): allow_resp = MagicMock() allow_resp.ok = True allow_resp.json.return_value = { "allowed": True, "calls_used": 5, "calls_total": 60, "topup_calls": 0, "period_start": "2026-04-14", "resets_on": "2026-05-14", } mock_post.return_value = allow_resp ok_result = MagicMock() ok_result.suggestions = [] ok_result.element_gaps = [] ok_result.grocery_list = [] ok_result.grocery_links = [] ok_result.rate_limited = False ok_result.rate_limit_count = 0 ok_result.orch_fallback = False mock_suggest.return_value = ok_result app.dependency_overrides[get_session] = lambda: _make_session(tier="paid") client = TestClient(app) resp = client.post("/api/v1/recipes/suggest", json={ "pantry_items": ["chicken", "rice"], "level": 3, "tier": "paid", }) app.dependency_overrides.clear() assert resp.status_code == 200 called_req = mock_suggest.call_args[0][1] assert called_req.level == 3 @patch("app.api.endpoints.recipes._suggest_in_thread") def test_no_orch_check_for_local_tier(mock_suggest): """Local sessions never hit the orch check.""" ok_result = MagicMock() ok_result.suggestions = [] ok_result.element_gaps = [] ok_result.grocery_list = [] ok_result.grocery_links = [] ok_result.rate_limited = False ok_result.rate_limit_count = 0 ok_result.orch_fallback = False mock_suggest.return_value = ok_result app.dependency_overrides[get_session] = lambda: _make_session(tier="local", license_key=None) client = TestClient(app) with patch("app.services.heimdall_orch.requests.post") as mock_check: client.post("/api/v1/recipes/suggest", json={ "pantry_items": ["chicken"], "level": 3, "tier": "local", }) mock_check.assert_not_called() app.dependency_overrides.clear() @patch("app.api.endpoints.recipes._suggest_in_thread") def test_no_orch_check_when_license_key_is_none(mock_suggest): """Subscription users (no license_key) skip the orch check.""" ok_result = MagicMock() ok_result.suggestions = [] ok_result.element_gaps = [] ok_result.grocery_list = [] ok_result.grocery_links = [] ok_result.rate_limited = False ok_result.rate_limit_count = 0 ok_result.orch_fallback = False mock_suggest.return_value = ok_result app.dependency_overrides[get_session] = lambda: _make_session(tier="paid", license_key=None) client = TestClient(app) with patch("app.services.heimdall_orch.requests.post") as mock_check: client.post("/api/v1/recipes/suggest", json={ "pantry_items": ["chicken"], "level": 3, "tier": "paid", }) mock_check.assert_not_called() app.dependency_overrides.clear()