from pathlib import Path from backend import modal_api from voiceledger.parser.rules import parse_transaction as local_parse_transaction class FakeResponse: def __init__(self, payload: dict) -> None: self.payload = payload def raise_for_status(self) -> None: return None def json(self) -> dict: return self.payload def test_parse_transaction_uses_local_fallback_when_url_missing(monkeypatch) -> None: monkeypatch.delenv(modal_api.MODAL_PARSE_URL_ENV, raising=False) transaction = modal_api.parse_transaction("Amit owes 100", fallback=local_parse_transaction) assert transaction.transaction_type == "customer_credit" assert transaction.customer == "Amit" def test_parse_transaction_uses_modal_response(monkeypatch) -> None: monkeypatch.setenv(modal_api.MODAL_PARSE_URL_ENV, "https://modal.example/parse") def fake_post(*args, **kwargs) -> FakeResponse: return FakeResponse( { "transaction": { "transaction_type": "expense", "item": "rent", "quantity": None, "unit_price": None, "amount": 300, "customer": None, "payment_status": "paid", "notes": "rent 300", "confidence": 0.99, } } ) monkeypatch.setattr(modal_api.requests, "post", fake_post) transaction = modal_api.parse_transaction("rent 300", fallback=local_parse_transaction) assert transaction.transaction_type == "expense" assert transaction.item == "rent" assert transaction.amount == 300 def test_parse_transaction_result_reports_modal_source(monkeypatch) -> None: monkeypatch.setenv(modal_api.MODAL_PARSE_URL_ENV, "https://modal.example/parse") def fake_post(*args, **kwargs) -> FakeResponse: return FakeResponse( { "transaction": { "transaction_type": "sale", "item": "mangoes", "quantity": 12, "unit_price": 20, "amount": 240, "customer": None, "payment_status": "paid", "notes": "Sold 12 mangoes, 20 each", "confidence": 0.97, } } ) monkeypatch.setattr(modal_api.requests, "post", fake_post) result = modal_api.parse_transaction_result("Sold 12 mangoes, 20 each", fallback=local_parse_transaction) assert result.source == "modal" assert "NVIDIA Nemotron" in result.message assert result.fallback_reason is None assert result.transaction.amount == 240 def test_parse_transaction_falls_back_when_modal_errors(monkeypatch) -> None: monkeypatch.setenv(modal_api.MODAL_PARSE_URL_ENV, "https://modal.example/parse") def fake_post(*args, **kwargs) -> None: raise RuntimeError("network down") monkeypatch.setattr(modal_api.requests, "post", fake_post) transaction = modal_api.parse_transaction("mango 12 x 20", fallback=local_parse_transaction) assert transaction.transaction_type == "sale" assert transaction.amount == 240 def test_parse_transaction_result_reports_fallback_reason(monkeypatch) -> None: monkeypatch.setenv(modal_api.MODAL_PARSE_URL_ENV, "https://modal.example/parse") def fake_post(*args, **kwargs) -> None: raise RuntimeError("network down") monkeypatch.setattr(modal_api.requests, "post", fake_post) result = modal_api.parse_transaction_result("mango 12 x 20", fallback=local_parse_transaction) assert result.source == "local" assert "after Modal failed" in result.message assert "network down" in result.fallback_reason assert result.transaction.amount == 240 def test_parse_transaction_result_force_local_skips_modal(monkeypatch) -> None: monkeypatch.setenv(modal_api.MODAL_PARSE_URL_ENV, "https://modal.example/parse") def fake_post(*args, **kwargs) -> None: raise AssertionError("Modal should not be called in local-first mode") monkeypatch.setattr(modal_api.requests, "post", fake_post) result = modal_api.parse_transaction_result( "Amit owes 100", fallback=local_parse_transaction, force_local=True, ) assert result.source == "local" assert result.transaction.transaction_type == "customer_credit" assert "local-first mode" in result.fallback_reason def test_transcribe_audio_uses_modal_response(tmp_path: Path, monkeypatch) -> None: audio_path = tmp_path / "audio.wav" audio_path.write_bytes(b"audio") monkeypatch.setenv(modal_api.MODAL_TRANSCRIBE_URL_ENV, "https://modal.example/transcribe") def fake_post(*args, **kwargs) -> FakeResponse: return FakeResponse({"transcript": "Sold 12 mangoes"}) monkeypatch.setattr(modal_api.requests, "post", fake_post) transcript = modal_api.transcribe_audio(audio_path, fallback=lambda _: "fallback") assert transcript == "Sold 12 mangoes" def test_transcribe_audio_result_reports_modal_source(tmp_path: Path, monkeypatch) -> None: audio_path = tmp_path / "audio.wav" audio_path.write_bytes(b"audio") monkeypatch.setenv(modal_api.MODAL_TRANSCRIBE_URL_ENV, "https://modal.example/transcribe") def fake_post(*args, **kwargs) -> FakeResponse: return FakeResponse({"transcript": "Sold 12 mangoes"}) monkeypatch.setattr(modal_api.requests, "post", fake_post) result = modal_api.transcribe_audio_result(audio_path, fallback=lambda _: "fallback") assert result.source == "modal" assert result.transcript == "Sold 12 mangoes" assert result.fallback_reason is None def test_transcribe_audio_result_force_local_skips_modal(tmp_path: Path, monkeypatch) -> None: audio_path = tmp_path / "audio.wav" audio_path.write_bytes(b"audio") monkeypatch.setenv(modal_api.MODAL_TRANSCRIBE_URL_ENV, "https://modal.example/transcribe") def fake_post(*args, **kwargs) -> None: raise AssertionError("Modal should not be called in local-first mode") monkeypatch.setattr(modal_api.requests, "post", fake_post) result = modal_api.transcribe_audio_result( audio_path, fallback=lambda _: "local transcript", force_local=True, ) assert result.source == "local" assert result.transcript == "local transcript" assert "local-first mode" in result.fallback_reason def test_transcribe_audio_accepts_gradio_dict_payload(tmp_path: Path, monkeypatch) -> None: audio_path = tmp_path / "audio.wav" audio_path.write_bytes(b"audio") monkeypatch.setenv(modal_api.MODAL_TRANSCRIBE_URL_ENV, "https://modal.example/transcribe") def fake_post(*args, **kwargs) -> FakeResponse: return FakeResponse({"transcript": "Paid 500 for supplies"}) monkeypatch.setattr(modal_api.requests, "post", fake_post) transcript = modal_api.transcribe_audio({"path": str(audio_path)}, fallback=lambda _: "fallback") assert transcript == "Paid 500 for supplies" def test_transcribe_audio_falls_back_when_url_missing(tmp_path: Path, monkeypatch) -> None: audio_path = tmp_path / "audio.wav" audio_path.write_bytes(b"audio") monkeypatch.delenv(modal_api.MODAL_TRANSCRIBE_URL_ENV, raising=False) transcript = modal_api.transcribe_audio(audio_path, fallback=lambda _: "fallback transcript") assert transcript == "fallback transcript" def test_get_modal_health_reports_version(monkeypatch) -> None: monkeypatch.setenv(modal_api.MODAL_PARSE_URL_ENV, "https://modal.example/parse") def fake_get(url: str, *args, **kwargs) -> FakeResponse: if url.endswith("/health"): return FakeResponse({"status": "ok"}) if url.endswith("/version"): return FakeResponse({"version": "parse-starlette-route-v1"}) raise AssertionError(f"unexpected url: {url}") monkeypatch.setattr(modal_api.requests, "get", fake_get) health = modal_api.get_modal_health() assert health["status"] == "ok" assert health["version"] == "parse-starlette-route-v1"