from unittest.mock import Mock, patch import pytest from src.server.services.job_board_service import JobBoardService, JobData @pytest.fixture def mock_supabase(): with patch("src.server.services.job_board_service.get_supabase_client") as mock_get_client: mock_client = Mock() mock_get_client.return_value = mock_client yield mock_client @pytest.mark.asyncio async def test_search_jobs_fallback_to_mock(): """ Test that search_jobs falls back to mock data when API fails or returns empty, and correctly infers needs. """ service = JobBoardService() # Mock _fetch_from_104_sync to raise an exception with patch.object(JobBoardService, "_fetch_from_104_sync", side_effect=Exception("Network Error")): jobs = await service.search_jobs("Data Analyst") # In Sync-Thru Real Data Mode, errors return empty list (No mock fallback) assert len(jobs) == 0 @pytest.mark.asyncio async def test_identify_leads_and_save(mock_supabase): """ Test that identify_leads_and_save attempts to insert new leads into Supabase. """ service = JobBoardService() # Setup mock jobs jobs = [JobData(title="Data Scientist", company="Test Corp", url="http://test/1", identified_need="Need AI")] # Setup Supabase Mock chain: table().select().eq().eq().execute() -> data mock_select_builder = Mock() mock_select_builder.eq.return_value.eq.return_value.execute.return_value = Mock(data=[]) # No existing lead mock_insert_builder = Mock() mock_insert_builder.execute.return_value = Mock(data=[{"id": "new-id"}]) # Configure table() to return different builders based on table name (optional but good practice) # Simplified: Just make table() return a builder that can handle both flows mock_table = Mock() mock_supabase.table.return_value = mock_table # Chain for SELECT mock_table.select.return_value = mock_select_builder # Chain for INSERT mock_table.insert.return_value = mock_insert_builder # Execute count = await service.identify_leads_and_save(jobs) # Assert assert count == 1 mock_supabase.table.assert_called_with("leads") mock_table.insert.assert_called_once() # Verify insert payload inserted_data = mock_table.insert.call_args[0][0] assert inserted_data[0]["company_name"] == "Test Corp" assert inserted_data[0]["identified_need"] == "Need AI" @pytest.mark.asyncio async def test_identify_leads_skips_existing(mock_supabase): """ Test that duplicates are skipped. """ service = JobBoardService() jobs = [JobData(title="Data Scientist", company="Test Corp", url="http://test/1")] # Mock finding an existing lead mock_select_builder = Mock() mock_select_builder.eq.return_value.eq.return_value.execute.return_value = Mock(data=[{"id": "existing-id"}]) mock_table = Mock() mock_supabase.table.return_value = mock_table mock_table.select.return_value = mock_select_builder # Execute count = await service.identify_leads_and_save(jobs) # Assert assert count == 0 mock_table.insert.assert_not_called()