# #region Test.BatchProcessingService.Extra [C:3] [TYPE Module] [SEMANTICS test, batch, process, coverage] # @BRIEF Additional coverage for BatchProcessingService edge cases. # @RELATION BINDS_TO -> [BatchProcessingService] # @TEST_EDGE: empty_source_text_cache -> continue on empty source_text in _check_cache # @TEST_EDGE: provider_token_config_error -> Graceful fallback in _process_llm # @TEST_EDGE: insert_batch_to_target_delegation -> Delegates to _batch_insert import pytest from unittest.mock import AsyncMock, MagicMock, patch from src.models.translate import TranslationJob from src.plugins.translate._batch_proc import BatchProcessingService from .conftest import JOB_ID, RUN_ID def _make_job(session, target_langs=None): job = session.query(TranslationJob).filter(TranslationJob.id == JOB_ID).first() if job is None: job = TranslationJob( id=JOB_ID, name="Batch Extra Test", source_dialect="en", target_dialect=target_langs[0] if target_langs else "fr", target_languages=target_langs or ["fr"], status="ACTIVE", provider_id="test-provider", ) session.add(job) else: if target_langs: job.target_languages = target_langs job.target_dialect = target_langs[0] session.commit() return job def _batch_rows(count=2, detected_lang="en"): return [ { "row_index": str(i), "source_text": f"Hello world {i}", "source_data": {"table": "test", "row_id": i}, "_detected_lang": detected_lang, } for i in range(count) ] class TestCheckCacheEdgeCases: """Cover edge cases in _check_cache.""" def test_empty_source_text_skips_cache(self, db_session): """When source_text is empty, continue without setting cache values.""" config = MagicMock() svc = BatchProcessingService(db_session, config) job = _make_job(db_session) rows = [{"row_index": "0", "source_text": "", "_detected_lang": "en"}] svc._check_cache(job, rows, "hash1", "hash2") assert "_cached_lang_values" not in rows[0] # _source_hash is NOT set because empty text hits `if not st: continue` # (the hash is only computed when we proceed past the empty-text guard) # So we don't assert _source_hash — it's skipped for empty text class TestProcessLlmEdgeCases: """Cover edge cases in _process_llm.""" @pytest.mark.asyncio async def test_provider_token_config_error_graceful(self, db_with_run): """When get_provider_token_config raises, fall through gracefully.""" session, run_id = db_with_run config = MagicMock() svc = BatchProcessingService(session, config) job = _make_job(session) rows = _batch_rows(1) with patch('src.plugins.translate._batch_proc.LLMProviderService') as MockProv: mock_prov_instance = MockProv.return_value mock_prov_instance.get_provider_token_config.side_effect = ValueError("DB error") with patch('src.plugins.translate._batch_proc.estimate_token_budget', return_value={ "max_output_needed": 1000, "max_rows_by_output": 10, "estimated_input_tokens": 500, "warning": None, }): with patch.object(svc._llm_service, 'call_llm_for_batch', new=AsyncMock(return_value={"successful": 1, "failed": 0, "skipped": 0, "retries": 0})): result = await svc._process_llm( job, run_id, rows, [], "batch-1", ["fr"], ) assert result["successful"] == 1 class TestInsertBatchToTarget: """Cover insert_batch_to_target delegation.""" @pytest.mark.asyncio async def test_delegates_to_batch_insert(self, db_with_run): """Ensure insert_batch_to_target delegates correctly.""" session, run_id = db_with_run config = MagicMock() svc = BatchProcessingService(session, config) job = _make_job(session) with patch('src.plugins.translate._batch_proc.insert_batch_to_target', new=AsyncMock(return_value=None)) as mock_insert: await svc.insert_batch_to_target(job, "batch-1", run_id) mock_insert.assert_called_once_with(session, config, job, "batch-1", run_id) # #endregion Test.BatchProcessingService.Extra