from __future__ import annotations from types import SimpleNamespace from unittest.mock import Mock, patch from django.test import SimpleTestCase from rag.failure_contract import RAGServiceError from rag.services.pest_disease import get_pest_disease_detection from rag.services.soil_anomaly import get_soil_anomaly_insight from rag.services.water_need_prediction import get_water_need_prediction_insight from rag.services.yield_harvest import YieldHarvestRAGService class RAGFailureContractTests(SimpleTestCase): @patch("rag.services.soil_anomaly._create_audit_log", return_value=object()) @patch("rag.services.soil_anomaly._fail_audit_log") @patch("rag.services.soil_anomaly._build_service_client") @patch("rag.services.soil_anomaly.build_rag_context", return_value="") @patch("rag.services.soil_anomaly._load_farm_or_error", return_value={"farm_uuid": "farm-1"}) def test_soil_anomaly_invalid_json_raises_structured_error( self, _mock_load_farm, _mock_context, mock_build_client, _mock_fail, _mock_audit, ): client = Mock() client.chat.completions.create.return_value = SimpleNamespace( choices=[SimpleNamespace(message=SimpleNamespace(content="not-json"))] ) mock_build_client.return_value = (SimpleNamespace(system_prompt=""), client, "gpt-test") with self.assertRaises(RAGServiceError) as exc_info: get_soil_anomaly_insight(farm_uuid="farm-1", anomaly_payload={}) self.assertEqual(exc_info.exception.contract.error_code, "invalid_json") @patch("rag.services.water_need_prediction._create_audit_log", return_value=object()) @patch("rag.services.water_need_prediction._fail_audit_log") @patch("rag.services.water_need_prediction._build_service_client") @patch("rag.services.water_need_prediction.build_rag_context", return_value="") @patch("rag.services.water_need_prediction._load_farm_or_error", return_value={"farm_uuid": "farm-1"}) def test_water_need_invalid_json_raises_structured_error( self, _mock_load_farm, _mock_context, mock_build_client, _mock_fail, _mock_audit, ): client = Mock() client.chat.completions.create.return_value = SimpleNamespace( choices=[SimpleNamespace(message=SimpleNamespace(content="not-json"))] ) mock_build_client.return_value = (SimpleNamespace(system_prompt=""), client, "gpt-test") with self.assertRaises(RAGServiceError) as exc_info: get_water_need_prediction_insight(farm_uuid="farm-1", prediction_payload={}) self.assertEqual(exc_info.exception.contract.error_code, "invalid_json") def test_pest_detection_requires_image_with_structured_error(self): with self.assertRaises(RAGServiceError) as exc_info: get_pest_disease_detection(farm_uuid="farm-1", images=[]) self.assertEqual(exc_info.exception.contract.error_code, "missing_images") @patch("rag.services.yield_harvest._create_audit_log", return_value=object()) @patch("rag.services.yield_harvest._fail_audit_log") @patch("rag.services.yield_harvest.YieldHarvestRAGService._build_service_client") def test_yield_harvest_invalid_json_raises_structured_error( self, mock_build_client, _mock_fail, _mock_audit, ): client = Mock() client.chat.completions.create.return_value = SimpleNamespace( choices=[SimpleNamespace(message=SimpleNamespace(content="not-json"))] ) mock_build_client.return_value = (SimpleNamespace(system_prompt=""), client, "gpt-test") with self.assertRaises(RAGServiceError) as exc_info: YieldHarvestRAGService().generate_narrative({"farm_uuid": "farm-1"}) self.assertEqual(exc_info.exception.contract.error_code, "invalid_json")