""" Tests for DraftSheetService Tests the Google Sheets integration for draft pick tracking. Uses mocked pygsheets to avoid actual API calls. """ import pytest from unittest.mock import MagicMock, patch from services.draft_sheet_service import DraftSheetService, get_draft_sheet_service class TestDraftSheetService: """ Test suite for DraftSheetService. Tests write_pick(), write_picks_batch(), clear_picks_range(), and get_sheet_url(). All tests mock pygsheets to avoid actual Google Sheets API calls. """ @pytest.fixture def mock_config(self): """ Create a mock config with draft sheet settings. Provides: - draft_sheet_enabled: True - sba_season: 12 - draft_sheet_worksheet: "Ordered List" - draft_sheet_start_column: "D" - draft_total_picks: 512 """ config = MagicMock() config.draft_sheet_enabled = True config.sba_season = 12 config.draft_sheet_worksheet = "Ordered List" config.draft_sheet_start_column = "D" config.draft_total_picks = 512 config.sheets_credentials_path = "/app/data/test-creds.json" config.get_draft_sheet_key = MagicMock(return_value="test-sheet-key-123") config.get_draft_sheet_url = MagicMock( return_value="https://docs.google.com/spreadsheets/d/test-sheet-key-123" ) return config @pytest.fixture def mock_pygsheets(self): """ Create mock pygsheets client, spreadsheet, and worksheet. Provides: - sheets_client: Mock pygsheets client - spreadsheet: Mock spreadsheet - worksheet: Mock worksheet with update_values method """ worksheet = MagicMock() worksheet.update_values = MagicMock() spreadsheet = MagicMock() spreadsheet.worksheet_by_title = MagicMock(return_value=worksheet) sheets_client = MagicMock() sheets_client.open_by_key = MagicMock(return_value=spreadsheet) return { 'client': sheets_client, 'spreadsheet': spreadsheet, 'worksheet': worksheet } @pytest.fixture def service(self, mock_config, mock_pygsheets): """ Create DraftSheetService instance with mocked dependencies. The service is set up with: - Mocked config - Mocked pygsheets client (via _get_client override) """ with patch('services.draft_sheet_service.get_config', return_value=mock_config): service = DraftSheetService() service._config = mock_config service._sheets_client = mock_pygsheets['client'] return service # ==================== write_pick() Tests ==================== @pytest.mark.asyncio async def test_write_pick_success(self, service, mock_pygsheets): """ Test successful write of a single draft pick to the sheet. Verifies: - Correct cell range is calculated (D + overall + 1) - Correct data is written (4 columns) - Returns True on success """ result = await service.write_pick( season=12, overall=1, orig_owner_abbrev="HAM", owner_abbrev="HAM", player_name="Mike Trout", swar=8.5 ) assert result is True # Verify worksheet was accessed mock_pygsheets['spreadsheet'].worksheet_by_title.assert_called_with("Ordered List") @pytest.mark.asyncio async def test_write_pick_disabled(self, service, mock_config): """ Test that write_pick returns False when feature is disabled. Verifies: - Returns False when draft_sheet_enabled is False - No API calls are made """ mock_config.draft_sheet_enabled = False result = await service.write_pick( season=12, overall=1, orig_owner_abbrev="HAM", owner_abbrev="HAM", player_name="Mike Trout", swar=8.5 ) assert result is False @pytest.mark.asyncio async def test_write_pick_no_sheet_configured(self, service, mock_config): """ Test that write_pick returns False when no sheet is configured for season. Verifies: - Returns False when get_draft_sheet_key returns None - No API calls are made """ mock_config.get_draft_sheet_key = MagicMock(return_value=None) result = await service.write_pick( season=13, # Season 13 has no configured sheet overall=1, orig_owner_abbrev="HAM", owner_abbrev="HAM", player_name="Mike Trout", swar=8.5 ) assert result is False @pytest.mark.asyncio async def test_write_pick_api_error(self, service, mock_pygsheets): """ Test that write_pick returns False and logs error on API failure. Verifies: - Returns False on exception - Exception is caught and logged (not raised) """ mock_pygsheets['spreadsheet'].worksheet_by_title.side_effect = Exception("API Error") result = await service.write_pick( season=12, overall=1, orig_owner_abbrev="HAM", owner_abbrev="HAM", player_name="Mike Trout", swar=8.5 ) assert result is False # ==================== write_picks_batch() Tests ==================== @pytest.mark.asyncio async def test_write_picks_batch_success(self, service, mock_pygsheets): """ Test successful batch write of multiple picks. Verifies: - All picks are written - Returns correct success/failure counts """ picks = [ (1, "HAM", "HAM", "Player 1", 2.5), (2, "NYY", "NYY", "Player 2", 3.0), (3, "BOS", "BOS", "Player 3", 1.5), ] success_count, failure_count = await service.write_picks_batch( season=12, picks=picks ) assert success_count == 3 assert failure_count == 0 @pytest.mark.asyncio async def test_write_picks_batch_empty_list(self, service): """ Test batch write with empty picks list. Verifies: - Returns (0, 0) for empty list - No API calls are made """ success_count, failure_count = await service.write_picks_batch( season=12, picks=[] ) assert success_count == 0 assert failure_count == 0 @pytest.mark.asyncio async def test_write_picks_batch_disabled(self, service, mock_config): """ Test batch write when feature is disabled. Verifies: - Returns (0, total_picks) when disabled """ mock_config.draft_sheet_enabled = False picks = [ (1, "HAM", "HAM", "Player 1", 2.5), (2, "NYY", "NYY", "Player 2", 3.0), ] success_count, failure_count = await service.write_picks_batch( season=12, picks=picks ) assert success_count == 0 assert failure_count == 2 # ==================== clear_picks_range() Tests ==================== @pytest.mark.asyncio async def test_clear_picks_range_success(self, service, mock_pygsheets): """ Test successful clearing of picks range. Verifies: - Returns True on success - Correct range is cleared """ result = await service.clear_picks_range( season=12, start_overall=1, end_overall=512 ) assert result is True @pytest.mark.asyncio async def test_clear_picks_range_disabled(self, service, mock_config): """ Test clearing when feature is disabled. Verifies: - Returns False when disabled """ mock_config.draft_sheet_enabled = False result = await service.clear_picks_range( season=12, start_overall=1, end_overall=512 ) assert result is False # ==================== get_sheet_url() Tests ==================== def test_get_sheet_url_configured(self, service, mock_config): """ Test get_sheet_url returns URL when configured. Verifies: - Returns correct URL format """ url = service.get_sheet_url(season=12) assert url == "https://docs.google.com/spreadsheets/d/test-sheet-key-123" def test_get_sheet_url_not_configured(self, service, mock_config): """ Test get_sheet_url returns None when not configured. Verifies: - Returns None for unconfigured season """ mock_config.get_draft_sheet_url = MagicMock(return_value=None) url = service.get_sheet_url(season=99) assert url is None class TestGlobalServiceInstance: """ Test suite for the global service instance pattern. Tests get_draft_sheet_service() lazy initialization. """ def test_get_draft_sheet_service_returns_instance(self): """ Test that get_draft_sheet_service returns a DraftSheetService instance. Note: This creates a real service instance but won't make API calls without being used. """ with patch('services.draft_sheet_service.get_config') as mock_config: mock_config.return_value.sheets_credentials_path = "/test/path.json" mock_config.return_value.draft_sheet_enabled = True # Reset global instance import services.draft_sheet_service as service_module service_module._draft_sheet_service = None service = get_draft_sheet_service() assert isinstance(service, DraftSheetService) def test_get_draft_sheet_service_returns_same_instance(self): """ Test that get_draft_sheet_service returns the same instance on subsequent calls. Verifies singleton pattern for global service. """ with patch('services.draft_sheet_service.get_config') as mock_config: mock_config.return_value.sheets_credentials_path = "/test/path.json" mock_config.return_value.draft_sheet_enabled = True # Reset global instance import services.draft_sheet_service as service_module service_module._draft_sheet_service = None service1 = get_draft_sheet_service() service2 = get_draft_sheet_service() assert service1 is service2