""" Comprehensive Unit Tests for TeamService Tests all team operations. """ import pytest from unittest.mock import MagicMock, patch import sys import os sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from app.services.team_service import TeamService from app.services.base import ServiceConfig from app.services.mocks import MockTeamRepository, MockCacheService # ============================================================================ # FIXTURES # ============================================================================ @pytest.fixture def cache(): """Create fresh cache for each test.""" return MockCacheService() @pytest.fixture def repo(cache): """Create fresh repo with test data.""" repo = MockTeamRepository() # Add test teams teams = [ {'id': 1, 'abbrev': 'BAL', 'sname': 'Orioles', 'lname': 'Baltimore Orioles', 'gmid': 123, 'season': 10, 'manager1_id': 1}, {'id': 2, 'abbrev': 'NYY', 'sname': 'Yankees', 'lname': 'New York Yankees', 'gmid': 456, 'season': 10, 'manager1_id': 2}, {'id': 3, 'abbrev': 'BOS', 'sname': 'Red Sox', 'lname': 'Boston Red Sox', 'gmid': 789, 'season': 10, 'manager1_id': 3}, {'id': 4, 'abbrev': 'BALIL', 'sname': 'Orioles IL', 'lname': 'Baltimore Orioles IL', 'gmid': 123, 'season': 10}, {'id': 5, 'abbrev': 'OLD', 'sname': 'Old Team', 'lname': 'Old Team Full', 'gmid': 999, 'season': 5}, ] for team in teams: repo.add_team(team) return repo @pytest.fixture def service(repo, cache): """Create service with mocks.""" config = ServiceConfig(team_repo=repo, cache=cache) return TeamService(config=config) # ============================================================================ # TEST CLASSES # ============================================================================ class TestTeamServiceGetTeams: """Tests for get_teams method.""" def test_get_all_season_teams(self, service, repo): """Get all teams for a season.""" result = service.get_teams(season=10) assert result['count'] >= 4 # 4 season 10 teams assert len(result['teams']) >= 4 def test_filter_by_abbrev(self, service): """Filter by team abbreviation.""" result = service.get_teams(season=10, team_abbrev=['BAL']) assert result['count'] >= 1 assert any(t.get('abbrev') == 'BAL' for t in result['teams']) def test_filter_by_multiple_abbrevs(self, service): """Filter by multiple abbreviations.""" result = service.get_teams(season=10, team_abbrev=['BAL', 'NYY']) assert result['count'] >= 2 for team in result['teams']: assert team.get('abbrev') in ['BAL', 'NYY'] def test_filter_active_only(self, service): """Filter out IL teams.""" result = service.get_teams(season=10, active_only=True) assert result['count'] >= 3 # Excludes BALIL assert all(not t.get('abbrev', '').endswith('IL') for t in result['teams']) def test_filter_by_manager(self, service): """Filter by manager ID.""" result = service.get_teams(season=10, manager_id=[1]) assert result['count'] >= 1 assert any(t.get('manager1_id') == 1 for t in result['teams']) def test_sort_by_name(self, service): """Sort teams by abbreviation.""" result = service.get_teams(season=10) # Teams should be ordered by ID (default) ids = [t.get('id') for t in result['teams']] assert ids == sorted(ids) class TestTeamServiceGetTeam: """Tests for get_team method.""" def test_get_existing_team(self, service): """Get existing team by ID.""" result = service.get_team(1) assert result is not None assert result.get('id') == 1 assert result.get('abbrev') == 'BAL' def test_get_nonexistent_team(self, service): """Get team that doesn't exist.""" result = service.get_team(99999) assert result is None class TestTeamServiceGetRoster: """Tests for get_team_roster method.""" def test_get_current_roster(self, service): """Get current week roster.""" # Note: This requires more complex mock setup for full testing # Simplified test for now pass def test_get_next_roster(self, service): """Get next week roster.""" # Note: This requires more complex mock setup for full testing pass class TestTeamServiceUpdate: """Tests for update_team method.""" def test_patch_team_name(self, repo, cache): """Patch team's abbreviation.""" config = ServiceConfig(team_repo=repo, cache=cache) service = TeamService(config=config) with patch.object(service, 'require_auth', return_value=True): result = service.update_team(1, {'abbrev': 'BAL2'}, 'valid_token') assert result.get('abbrev') == 'BAL2' def test_patch_team_manager(self, repo, cache): """Patch team's manager.""" config = ServiceConfig(team_repo=repo, cache=cache) service = TeamService(config=config) with patch.object(service, 'require_auth', return_value=True): result = service.update_team(1, {'manager1_id': 10}, 'valid_token') assert result.get('manager1_id') == 10 def test_patch_multiple_fields(self, repo, cache): """Patch multiple fields at once.""" config = ServiceConfig(team_repo=repo, cache=cache) service = TeamService(config=config) updates = { 'abbrev': 'BAL3', 'sname': 'Birds', 'color': '#FF0000' } with patch.object(service, 'require_auth', return_value=True): result = service.update_team(1, updates, 'valid_token') assert result.get('abbrev') == 'BAL3' assert result.get('sname') == 'Birds' assert result.get('color') == '#FF0000' def test_patch_nonexistent_team(self, repo, cache): """Patch fails for non-existent team.""" config = ServiceConfig(team_repo=repo, cache=cache) service = TeamService(config=config) with patch.object(service, 'require_auth', return_value=True): with pytest.raises(Exception) as exc_info: service.update_team(99999, {'abbrev': 'TEST'}, 'valid_token') assert 'not found' in str(exc_info.value) def test_patch_requires_auth(self, repo, cache): """Patching requires authentication.""" config = ServiceConfig(team_repo=repo, cache=cache) service = TeamService(config=config) with pytest.raises(Exception) as exc_info: service.update_team(1, {'abbrev': 'TEST'}, 'bad_token') assert exc_info.value.status_code == 401 class TestTeamServiceCreate: """Tests for create_teams method.""" def test_create_single_team(self, repo, cache): """Create a single new team.""" config = ServiceConfig(team_repo=repo, cache=cache) service = TeamService(config=config) new_team = [{ 'abbrev': 'CLE2', 'sname': 'Guardians2', 'lname': 'Cleveland Guardians 2', 'gmid': 999, 'season': 10 }] with patch.object(service, 'require_auth', return_value=True): result = service.create_teams(new_team, 'valid_token') assert 'Inserted' in str(result) # Verify team was added team = repo.get_by_id(6) # Next ID assert team is not None assert team['abbrev'] == 'CLE2' def test_create_multiple_teams(self, repo, cache): """Create multiple new teams.""" config = ServiceConfig(team_repo=repo, cache=cache) service = TeamService(config=config) new_teams = [ {'abbrev': 'TST1', 'sname': 'Test1', 'lname': 'Test Team 1', 'gmid': 100, 'season': 10}, {'abbrev': 'TST2', 'sname': 'Test2', 'lname': 'Test Team 2', 'gmid': 101, 'season': 10}, ] with patch.object(service, 'require_auth', return_value=True): result = service.create_teams(new_teams, 'valid_token') assert 'Inserted 2 teams' in str(result) def test_create_duplicate_fails(self, repo, cache): """Creating duplicate team should fail.""" config = ServiceConfig(team_repo=repo, cache=cache) service = TeamService(config=config) duplicate = [{'abbrev': 'BAL', 'sname': 'Dup', 'lname': 'Duplicate', 'gmid': 999, 'season': 10}] with patch.object(service, 'require_auth', return_value=True): with pytest.raises(Exception) as exc_info: service.create_teams(duplicate, 'valid_token') assert 'already exists' in str(exc_info.value) def test_create_requires_auth(self, repo, cache): """Creating teams requires authentication.""" config = ServiceConfig(team_repo=repo, cache=cache) service = TeamService(config=config) new_team = [{'abbrev': 'TST', 'sname': 'Test', 'lname': 'Test', 'gmid': 999, 'season': 10}] with pytest.raises(Exception) as exc_info: service.create_teams(new_team, 'bad_token') assert exc_info.value.status_code == 401 class TestTeamServiceDelete: """Tests for delete_team method.""" def test_delete_team(self, repo, cache): """Delete existing team.""" config = ServiceConfig(team_repo=repo, cache=cache) service = TeamService(config=config) # Verify team exists assert repo.get_by_id(1) is not None with patch.object(service, 'require_auth', return_value=True): result = service.delete_team(1, 'valid_token') assert 'deleted' in str(result) # Verify team is gone assert repo.get_by_id(1) is None def test_delete_nonexistent_team(self, repo, cache): """Delete fails for non-existent team.""" config = ServiceConfig(team_repo=repo, cache=cache) service = TeamService(config=config) with patch.object(service, 'require_auth', return_value=True): with pytest.raises(Exception) as exc_info: service.delete_team(99999, 'valid_token') assert 'not found' in str(exc_info.value) def test_delete_requires_auth(self, repo, cache): """Deleting requires authentication.""" config = ServiceConfig(team_repo=repo, cache=cache) service = TeamService(config=config) with pytest.raises(Exception) as exc_info: service.delete_team(1, 'bad_token') assert exc_info.value.status_code == 401 class TestTeamServiceCache: """Tests for cache functionality.""" @pytest.mark.skip(reason="Caching not yet implemented in service methods") def test_cache_set_on_read(self, service, cache): """Cache is set on team read.""" service.get_teams(season=10) assert cache.was_called('set') @pytest.mark.skip(reason="Caching not yet implemented in service methods") def test_cache_invalidation_on_update(self, repo, cache): """Cache is invalidated on team update.""" config = ServiceConfig(team_repo=repo, cache=cache) service = TeamService(config=config) # Read to set cache service.get_teams(season=10) # Update should invalidate cache with patch.object(service, 'require_auth', return_value=True): service.update_team(1, {'abbrev': 'TEST'}, 'valid_token') # Should have invalidate/delete calls delete_calls = [c for c in cache.get_calls() if c.get('method') == 'delete'] assert len(delete_calls) > 0 @pytest.mark.skip(reason="Caching not yet implemented in service methods") def test_cache_invalidation_on_create(self, repo, cache): """Cache is invalidated on team create.""" config = ServiceConfig(team_repo=repo, cache=cache) service = TeamService(config=config) # Set up some cache data cache.set('test:key', 'value', 300) with patch.object(service, 'require_auth', return_value=True): service.create_teams([{ 'abbrev': 'NEW', 'sname': 'New', 'lname': 'New Team', 'gmid': 888, 'season': 10 }], 'valid_token') # Should have invalidate calls assert len(cache.get_calls()) > 0 @pytest.mark.skip(reason="Caching not yet implemented in service methods") def test_cache_invalidation_on_delete(self, repo, cache): """Cache is invalidated on team delete.""" config = ServiceConfig(team_repo=repo, cache=cache) service = TeamService(config=config) cache.set('test:key', 'value', 300) with patch.object(service, 'require_auth', return_value=True): service.delete_team(1, 'valid_token') assert len(cache.get_calls()) > 0 class TestTeamServiceValidation: """Tests for input validation and edge cases.""" def test_invalid_season_returns_empty(self, service): """Invalid season returns empty result.""" result = service.get_teams(season=999) assert result['count'] == 0 or result['teams'] == [] def test_sort_with_no_results(self, service): """Sorting with no results doesn't error.""" result = service.get_teams(season=999, active_only=True) assert result['count'] == 0 or result['teams'] == [] def test_filter_nonexistent_abbrev(self, service): """Filter by non-existent abbreviation.""" result = service.get_teams(season=10, team_abbrev=['XYZ']) assert result['count'] == 0 class TestTeamServiceIntegration: """Integration tests combining multiple operations.""" def test_full_crud_cycle(self, repo, cache): """Test complete CRUD cycle.""" config = ServiceConfig(team_repo=repo, cache=cache) service = TeamService(config=config) # CREATE with patch.object(service, 'require_auth', return_value=True): create_result = service.create_teams([{ 'abbrev': 'CRUD', 'sname': 'Test', 'lname': 'CRUD Test Team', 'gmid': 777, 'season': 10 }], 'valid_token') # READ search_result = service.get_teams(season=10, team_abbrev=['CRUD']) assert search_result['count'] >= 1 team_id = search_result['teams'][0].get('id') # UPDATE with patch.object(service, 'require_auth', return_value=True): update_result = service.update_team(team_id, {'sname': 'Updated'}, 'valid_token') assert update_result.get('sname') == 'Updated' # DELETE with patch.object(service, 'require_auth', return_value=True): delete_result = service.delete_team(team_id, 'valid_token') assert 'deleted' in str(delete_result) # VERIFY DELETED get_result = service.get_team(team_id) assert get_result is None def test_filter_then_get(self, service): """Filter teams then get individual team.""" # First filter filtered = service.get_teams(season=10, team_abbrev=['BAL']) assert filtered['count'] >= 1 # Then get by ID team_id = filtered['teams'][0].get('id') single = service.get_team(team_id) assert single is not None assert single.get('abbrev') == 'BAL' # ============================================================================ # RUN TESTS # ============================================================================ if __name__ == "__main__": pytest.main([__file__, "-v", "--tb=short"])