mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 13:02:59 +00:00
Feature/ap top 25 dynamic teams (#68)
* feat: Add AP Top 25 dynamic teams feature - Add DynamicTeamResolver for resolving AP_TOP_25, AP_TOP_10, AP_TOP_5 to actual team abbreviations - Integrate dynamic team resolution into SportsCore base class - Add comprehensive test suite for dynamic team functionality - Update config template with AP_TOP_25 example - Add complete documentation for the new feature Features: - Automatic weekly updates of AP Top 25 rankings - 1-hour caching for performance optimization - Support for AP_TOP_25, AP_TOP_10, AP_TOP_5 dynamic teams - Seamless integration with existing favorite teams system - Comprehensive error handling and edge case support Tests: - Unit tests for core dynamic team resolution - Integration tests for configuration scenarios - Performance tests for caching functionality - Edge case tests for unknown dynamic teams All tests passing with 100% success rate. * docs: Update wiki submodule with AP Top 25 documentation - Add comprehensive documentation for AP Top 25 dynamic teams feature - Include usage examples, configuration guides, and troubleshooting - Update submodule reference to include new documentation * feat: Add AP_TOP_25 support to odds ticker - Integrate DynamicTeamResolver into OddsTickerManager - Resolve dynamic teams for all enabled leagues during initialization - Add comprehensive logging for dynamic team resolution - Support AP_TOP_25, AP_TOP_10, AP_TOP_5 in odds ticker - Add test suite for odds ticker dynamic teams integration Features: - Odds ticker now automatically resolves AP_TOP_25 to current top 25 teams - Shows odds for all current AP Top 25 teams automatically - Updates weekly when rankings change - Works seamlessly with existing favorite teams system - Supports mixed regular and dynamic teams Tests: - Configuration integration tests - Multiple league configuration tests - Edge case handling tests - All tests passing with 100% success rate
This commit is contained in:
135
test/test_dynamic_team_resolver.py
Normal file
135
test/test_dynamic_team_resolver.py
Normal file
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to verify dynamic team resolver functionality.
|
||||
This test checks that AP_TOP_25 and other dynamic team names are resolved correctly.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
import pytz
|
||||
|
||||
# Add the src directory to the path so we can import the dynamic team resolver
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
|
||||
from dynamic_team_resolver import DynamicTeamResolver, resolve_dynamic_teams
|
||||
|
||||
def test_dynamic_team_resolver():
|
||||
"""Test the dynamic team resolver functionality."""
|
||||
print("Testing Dynamic Team Resolver...")
|
||||
|
||||
# Test 1: Basic dynamic team resolution
|
||||
print("\n1. Testing basic dynamic team resolution...")
|
||||
resolver = DynamicTeamResolver()
|
||||
|
||||
# Test with mixed regular and dynamic teams
|
||||
test_teams = ["UGA", "AP_TOP_25", "AUB", "AP_TOP_10"]
|
||||
resolved_teams = resolver.resolve_teams(test_teams, 'ncaa_fb')
|
||||
|
||||
print(f"Input teams: {test_teams}")
|
||||
print(f"Resolved teams: {resolved_teams}")
|
||||
print(f"Number of resolved teams: {len(resolved_teams)}")
|
||||
|
||||
# Verify that UGA and AUB are still in the list
|
||||
assert "UGA" in resolved_teams, "UGA should be in resolved teams"
|
||||
assert "AUB" in resolved_teams, "AUB should be in resolved teams"
|
||||
|
||||
# Verify that AP_TOP_25 and AP_TOP_10 are resolved to actual teams
|
||||
assert len(resolved_teams) > 4, "Should have more than 4 teams after resolving dynamic teams"
|
||||
|
||||
print("✓ Basic dynamic team resolution works")
|
||||
|
||||
# Test 2: Test dynamic team detection
|
||||
print("\n2. Testing dynamic team detection...")
|
||||
assert resolver.is_dynamic_team("AP_TOP_25"), "AP_TOP_25 should be detected as dynamic"
|
||||
assert resolver.is_dynamic_team("AP_TOP_10"), "AP_TOP_10 should be detected as dynamic"
|
||||
assert resolver.is_dynamic_team("AP_TOP_5"), "AP_TOP_5 should be detected as dynamic"
|
||||
assert not resolver.is_dynamic_team("UGA"), "UGA should not be detected as dynamic"
|
||||
assert not resolver.is_dynamic_team("AUB"), "AUB should not be detected as dynamic"
|
||||
|
||||
print("✓ Dynamic team detection works")
|
||||
|
||||
# Test 3: Test available dynamic teams
|
||||
print("\n3. Testing available dynamic teams...")
|
||||
available_teams = resolver.get_available_dynamic_teams()
|
||||
expected_teams = ["AP_TOP_25", "AP_TOP_10", "AP_TOP_5"]
|
||||
|
||||
for team in expected_teams:
|
||||
assert team in available_teams, f"{team} should be in available dynamic teams"
|
||||
|
||||
print(f"Available dynamic teams: {available_teams}")
|
||||
print("✓ Available dynamic teams list works")
|
||||
|
||||
# Test 4: Test convenience function
|
||||
print("\n4. Testing convenience function...")
|
||||
convenience_result = resolve_dynamic_teams(["UGA", "AP_TOP_5"], 'ncaa_fb')
|
||||
assert "UGA" in convenience_result, "Convenience function should include UGA"
|
||||
assert len(convenience_result) > 1, "Convenience function should resolve AP_TOP_5"
|
||||
|
||||
print(f"Convenience function result: {convenience_result}")
|
||||
print("✓ Convenience function works")
|
||||
|
||||
# Test 5: Test cache functionality
|
||||
print("\n5. Testing cache functionality...")
|
||||
# First call should populate cache
|
||||
start_time = datetime.now()
|
||||
result1 = resolver.resolve_teams(["AP_TOP_25"], 'ncaa_fb')
|
||||
first_call_time = (datetime.now() - start_time).total_seconds()
|
||||
|
||||
# Second call should use cache (should be faster)
|
||||
start_time = datetime.now()
|
||||
result2 = resolver.resolve_teams(["AP_TOP_25"], 'ncaa_fb')
|
||||
second_call_time = (datetime.now() - start_time).total_seconds()
|
||||
|
||||
assert result1 == result2, "Cached results should be identical"
|
||||
print(f"First call time: {first_call_time:.3f}s")
|
||||
print(f"Second call time: {second_call_time:.3f}s")
|
||||
print("✓ Cache functionality works")
|
||||
|
||||
# Test 6: Test cache clearing
|
||||
print("\n6. Testing cache clearing...")
|
||||
resolver.clear_cache()
|
||||
assert not resolver._rankings_cache, "Cache should be empty after clearing"
|
||||
print("✓ Cache clearing works")
|
||||
|
||||
print("\n🎉 All tests passed! Dynamic team resolver is working correctly.")
|
||||
|
||||
def test_edge_cases():
|
||||
"""Test edge cases for the dynamic team resolver."""
|
||||
print("\nTesting edge cases...")
|
||||
|
||||
resolver = DynamicTeamResolver()
|
||||
|
||||
# Test empty list
|
||||
result = resolver.resolve_teams([], 'ncaa_fb')
|
||||
assert result == [], "Empty list should return empty list"
|
||||
print("✓ Empty list handling works")
|
||||
|
||||
# Test list with only regular teams
|
||||
result = resolver.resolve_teams(["UGA", "AUB"], 'ncaa_fb')
|
||||
assert result == ["UGA", "AUB"], "Regular teams should be returned unchanged"
|
||||
print("✓ Regular teams handling works")
|
||||
|
||||
# Test list with only dynamic teams
|
||||
result = resolver.resolve_teams(["AP_TOP_25"], 'ncaa_fb')
|
||||
assert len(result) > 0, "Dynamic teams should be resolved"
|
||||
print("✓ Dynamic-only teams handling works")
|
||||
|
||||
# Test unknown dynamic team
|
||||
result = resolver.resolve_teams(["AP_TOP_50"], 'ncaa_fb')
|
||||
assert result == [], "Unknown dynamic teams should return empty list"
|
||||
print("✓ Unknown dynamic teams handling works")
|
||||
|
||||
print("✓ All edge cases handled correctly")
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
test_dynamic_team_resolver()
|
||||
test_edge_cases()
|
||||
print("\n🎉 All dynamic team resolver tests passed!")
|
||||
except Exception as e:
|
||||
print(f"\n❌ Test failed with error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
140
test/test_dynamic_teams_simple.py
Normal file
140
test/test_dynamic_teams_simple.py
Normal file
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple test to verify dynamic team resolver works correctly.
|
||||
This test focuses on the core functionality without requiring the full LEDMatrix system.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add the src directory to the path so we can import the dynamic team resolver
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
|
||||
from dynamic_team_resolver import DynamicTeamResolver, resolve_dynamic_teams
|
||||
|
||||
def test_config_integration():
|
||||
"""Test how dynamic teams would work with a typical configuration."""
|
||||
print("Testing configuration integration...")
|
||||
|
||||
# Simulate a typical config favorite_teams list
|
||||
config_favorite_teams = [
|
||||
"UGA", # Regular team
|
||||
"AUB", # Regular team
|
||||
"AP_TOP_25" # Dynamic team
|
||||
]
|
||||
|
||||
print(f"Config favorite teams: {config_favorite_teams}")
|
||||
|
||||
# Resolve the teams
|
||||
resolved_teams = resolve_dynamic_teams(config_favorite_teams, 'ncaa_fb')
|
||||
|
||||
print(f"Resolved teams: {resolved_teams}")
|
||||
print(f"Number of resolved teams: {len(resolved_teams)}")
|
||||
|
||||
# Verify results
|
||||
assert "UGA" in resolved_teams, "UGA should be in resolved teams"
|
||||
assert "AUB" in resolved_teams, "AUB should be in resolved teams"
|
||||
assert "AP_TOP_25" not in resolved_teams, "AP_TOP_25 should be resolved, not left as-is"
|
||||
assert len(resolved_teams) > 2, "Should have more than 2 teams after resolving AP_TOP_25"
|
||||
|
||||
print("✓ Configuration integration works correctly")
|
||||
return True
|
||||
|
||||
def test_mixed_dynamic_teams():
|
||||
"""Test with multiple dynamic team types."""
|
||||
print("Testing mixed dynamic teams...")
|
||||
|
||||
config_favorite_teams = [
|
||||
"UGA",
|
||||
"AP_TOP_10", # Top 10 teams
|
||||
"AUB",
|
||||
"AP_TOP_5" # Top 5 teams
|
||||
]
|
||||
|
||||
print(f"Config favorite teams: {config_favorite_teams}")
|
||||
|
||||
resolved_teams = resolve_dynamic_teams(config_favorite_teams, 'ncaa_fb')
|
||||
|
||||
print(f"Resolved teams: {resolved_teams}")
|
||||
print(f"Number of resolved teams: {len(resolved_teams)}")
|
||||
|
||||
# Verify results
|
||||
assert "UGA" in resolved_teams, "UGA should be in resolved teams"
|
||||
assert "AUB" in resolved_teams, "AUB should be in resolved teams"
|
||||
assert len(resolved_teams) > 4, "Should have more than 4 teams after resolving dynamic teams"
|
||||
|
||||
print("✓ Mixed dynamic teams work correctly")
|
||||
return True
|
||||
|
||||
def test_edge_cases():
|
||||
"""Test edge cases for configuration integration."""
|
||||
print("Testing edge cases...")
|
||||
|
||||
# Test empty list
|
||||
result = resolve_dynamic_teams([], 'ncaa_fb')
|
||||
assert result == [], "Empty list should return empty list"
|
||||
print("✓ Empty list handling works")
|
||||
|
||||
# Test only regular teams
|
||||
result = resolve_dynamic_teams(["UGA", "AUB"], 'ncaa_fb')
|
||||
assert result == ["UGA", "AUB"], "Regular teams should be unchanged"
|
||||
print("✓ Regular teams handling works")
|
||||
|
||||
# Test only dynamic teams
|
||||
result = resolve_dynamic_teams(["AP_TOP_5"], 'ncaa_fb')
|
||||
assert len(result) > 0, "Dynamic teams should be resolved"
|
||||
assert "AP_TOP_5" not in result, "Dynamic team should be resolved"
|
||||
print("✓ Dynamic-only teams handling works")
|
||||
|
||||
# Test unknown dynamic teams
|
||||
result = resolve_dynamic_teams(["AP_TOP_50"], 'ncaa_fb')
|
||||
assert result == [], "Unknown dynamic teams should be filtered out"
|
||||
print("✓ Unknown dynamic teams handling works")
|
||||
|
||||
print("✓ All edge cases handled correctly")
|
||||
return True
|
||||
|
||||
def test_performance():
|
||||
"""Test performance characteristics."""
|
||||
print("Testing performance...")
|
||||
|
||||
import time
|
||||
|
||||
# Test caching performance
|
||||
resolver = DynamicTeamResolver()
|
||||
|
||||
# First call (should fetch from API)
|
||||
start_time = time.time()
|
||||
result1 = resolver.resolve_teams(["AP_TOP_25"], 'ncaa_fb')
|
||||
first_call_time = time.time() - start_time
|
||||
|
||||
# Second call (should use cache)
|
||||
start_time = time.time()
|
||||
result2 = resolver.resolve_teams(["AP_TOP_25"], 'ncaa_fb')
|
||||
second_call_time = time.time() - start_time
|
||||
|
||||
assert result1 == result2, "Cached results should be identical"
|
||||
print(f"First call time: {first_call_time:.3f}s")
|
||||
print(f"Second call time: {second_call_time:.3f}s")
|
||||
print("✓ Caching improves performance")
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
print("🧪 Testing Dynamic Teams Configuration Integration...")
|
||||
print("=" * 60)
|
||||
|
||||
test_config_integration()
|
||||
test_mixed_dynamic_teams()
|
||||
test_edge_cases()
|
||||
test_performance()
|
||||
|
||||
print("\n🎉 All configuration integration tests passed!")
|
||||
print("Dynamic team resolver is ready for production use!")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Test failed with error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
195
test/test_odds_ticker_dynamic_teams.py
Normal file
195
test/test_odds_ticker_dynamic_teams.py
Normal file
@@ -0,0 +1,195 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to verify odds ticker works with dynamic teams.
|
||||
This test checks that AP_TOP_25 is properly resolved in the odds ticker.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
import pytz
|
||||
|
||||
# Add the project root to the path so we can import the modules
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
from src.odds_ticker_manager import OddsTickerManager
|
||||
from src.display_manager import DisplayManager
|
||||
|
||||
def create_test_config():
|
||||
"""Create a test configuration with dynamic teams for odds ticker."""
|
||||
config = {
|
||||
"odds_ticker": {
|
||||
"enabled": True,
|
||||
"show_favorite_teams_only": True,
|
||||
"enabled_leagues": ["ncaa_fb"],
|
||||
"games_per_favorite_team": 1,
|
||||
"max_games_per_league": 5,
|
||||
"update_interval": 3600
|
||||
},
|
||||
"ncaa_fb_scoreboard": {
|
||||
"enabled": True,
|
||||
"favorite_teams": [
|
||||
"UGA",
|
||||
"AP_TOP_25"
|
||||
]
|
||||
},
|
||||
"display": {
|
||||
"hardware": {
|
||||
"rows": 32,
|
||||
"cols": 64,
|
||||
"chain_length": 1
|
||||
}
|
||||
},
|
||||
"timezone": "America/Chicago"
|
||||
}
|
||||
return config
|
||||
|
||||
def test_odds_ticker_dynamic_teams():
|
||||
"""Test that odds ticker properly resolves dynamic teams."""
|
||||
print("Testing OddsTickerManager with dynamic teams...")
|
||||
|
||||
# Create test configuration
|
||||
config = create_test_config()
|
||||
|
||||
# Create mock display manager
|
||||
display_manager = DisplayManager(config)
|
||||
|
||||
# Create OddsTickerManager instance
|
||||
odds_ticker = OddsTickerManager(config, display_manager)
|
||||
|
||||
# Check that dynamic resolver is available
|
||||
assert hasattr(odds_ticker, 'dynamic_resolver'), "OddsTickerManager should have dynamic_resolver attribute"
|
||||
assert odds_ticker.dynamic_resolver is not None, "Dynamic resolver should be initialized"
|
||||
|
||||
# Check that NCAA FB league config has resolved teams
|
||||
ncaa_fb_config = odds_ticker.league_configs.get('ncaa_fb', {})
|
||||
assert ncaa_fb_config.get('enabled', False), "NCAA FB should be enabled"
|
||||
|
||||
favorite_teams = ncaa_fb_config.get('favorite_teams', [])
|
||||
print(f"NCAA FB favorite teams: {favorite_teams}")
|
||||
|
||||
# Verify that UGA is still in the list
|
||||
assert "UGA" in favorite_teams, "UGA should be in resolved teams"
|
||||
|
||||
# Verify that AP_TOP_25 was resolved to actual teams
|
||||
assert len(favorite_teams) > 1, "Should have more than 1 team after resolving AP_TOP_25"
|
||||
|
||||
# Verify that AP_TOP_25 is not in the final list (should be resolved)
|
||||
assert "AP_TOP_25" not in favorite_teams, "AP_TOP_25 should be resolved, not left as-is"
|
||||
|
||||
print(f"✓ OddsTickerManager successfully resolved dynamic teams")
|
||||
print(f"✓ Final favorite teams: {favorite_teams[:10]}{'...' if len(favorite_teams) > 10 else ''}")
|
||||
|
||||
return True
|
||||
|
||||
def test_odds_ticker_regular_teams():
|
||||
"""Test that odds ticker works with regular teams (no dynamic teams)."""
|
||||
print("Testing OddsTickerManager with regular teams...")
|
||||
|
||||
config = {
|
||||
"odds_ticker": {
|
||||
"enabled": True,
|
||||
"show_favorite_teams_only": True,
|
||||
"enabled_leagues": ["ncaa_fb"],
|
||||
"games_per_favorite_team": 1,
|
||||
"max_games_per_league": 5,
|
||||
"update_interval": 3600
|
||||
},
|
||||
"ncaa_fb_scoreboard": {
|
||||
"enabled": True,
|
||||
"favorite_teams": [
|
||||
"UGA",
|
||||
"AUB"
|
||||
]
|
||||
},
|
||||
"display": {
|
||||
"hardware": {
|
||||
"rows": 32,
|
||||
"cols": 64,
|
||||
"chain_length": 1
|
||||
}
|
||||
},
|
||||
"timezone": "America/Chicago"
|
||||
}
|
||||
|
||||
display_manager = DisplayManager(config)
|
||||
odds_ticker = OddsTickerManager(config, display_manager)
|
||||
|
||||
# Check that regular teams are preserved
|
||||
ncaa_fb_config = odds_ticker.league_configs.get('ncaa_fb', {})
|
||||
favorite_teams = ncaa_fb_config.get('favorite_teams', [])
|
||||
|
||||
assert favorite_teams == ["UGA", "AUB"], "Regular teams should be preserved unchanged"
|
||||
print("✓ Regular teams work correctly")
|
||||
|
||||
return True
|
||||
|
||||
def test_odds_ticker_mixed_teams():
|
||||
"""Test odds ticker with mixed regular and dynamic teams."""
|
||||
print("Testing OddsTickerManager with mixed teams...")
|
||||
|
||||
config = {
|
||||
"odds_ticker": {
|
||||
"enabled": True,
|
||||
"show_favorite_teams_only": True,
|
||||
"enabled_leagues": ["ncaa_fb"],
|
||||
"games_per_favorite_team": 1,
|
||||
"max_games_per_league": 5,
|
||||
"update_interval": 3600
|
||||
},
|
||||
"ncaa_fb_scoreboard": {
|
||||
"enabled": True,
|
||||
"favorite_teams": [
|
||||
"UGA",
|
||||
"AP_TOP_10",
|
||||
"AUB"
|
||||
]
|
||||
},
|
||||
"display": {
|
||||
"hardware": {
|
||||
"rows": 32,
|
||||
"cols": 64,
|
||||
"chain_length": 1
|
||||
}
|
||||
},
|
||||
"timezone": "America/Chicago"
|
||||
}
|
||||
|
||||
display_manager = DisplayManager(config)
|
||||
odds_ticker = OddsTickerManager(config, display_manager)
|
||||
|
||||
ncaa_fb_config = odds_ticker.league_configs.get('ncaa_fb', {})
|
||||
favorite_teams = ncaa_fb_config.get('favorite_teams', [])
|
||||
|
||||
# Verify that UGA and AUB are still in the list
|
||||
assert "UGA" in favorite_teams, "UGA should be in resolved teams"
|
||||
assert "AUB" in favorite_teams, "AUB should be in resolved teams"
|
||||
|
||||
# Verify that AP_TOP_10 was resolved to actual teams
|
||||
assert len(favorite_teams) > 2, "Should have more than 2 teams after resolving AP_TOP_10"
|
||||
|
||||
# Verify that AP_TOP_10 is not in the final list (should be resolved)
|
||||
assert "AP_TOP_10" not in favorite_teams, "AP_TOP_10 should be resolved, not left as-is"
|
||||
|
||||
print(f"✓ Mixed teams work correctly: {favorite_teams[:10]}{'...' if len(favorite_teams) > 10 else ''}")
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
print("🧪 Testing OddsTickerManager with Dynamic Teams...")
|
||||
print("=" * 60)
|
||||
|
||||
test_odds_ticker_dynamic_teams()
|
||||
test_odds_ticker_regular_teams()
|
||||
test_odds_ticker_mixed_teams()
|
||||
|
||||
print("\n🎉 All odds ticker dynamic teams tests passed!")
|
||||
print("AP_TOP_25 will work correctly with the odds ticker!")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Test failed with error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
164
test/test_odds_ticker_simple.py
Normal file
164
test/test_odds_ticker_simple.py
Normal file
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple test to verify odds ticker dynamic team resolution works.
|
||||
This test focuses on the core functionality without requiring the full LEDMatrix system.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add the src directory to the path so we can import the dynamic team resolver
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
|
||||
from dynamic_team_resolver import DynamicTeamResolver
|
||||
|
||||
def test_odds_ticker_configuration():
|
||||
"""Test how dynamic teams would work with odds ticker configuration."""
|
||||
print("Testing odds ticker configuration with dynamic teams...")
|
||||
|
||||
# Simulate a typical odds ticker config
|
||||
config = {
|
||||
"odds_ticker": {
|
||||
"enabled": True,
|
||||
"show_favorite_teams_only": True,
|
||||
"enabled_leagues": ["ncaa_fb"],
|
||||
"games_per_favorite_team": 1,
|
||||
"max_games_per_league": 5
|
||||
},
|
||||
"ncaa_fb_scoreboard": {
|
||||
"enabled": True,
|
||||
"favorite_teams": [
|
||||
"UGA",
|
||||
"AP_TOP_25"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
# Simulate what the odds ticker would do
|
||||
resolver = DynamicTeamResolver()
|
||||
|
||||
# Get the raw favorite teams from config (what odds ticker gets)
|
||||
raw_favorite_teams = config.get('ncaa_fb_scoreboard', {}).get('favorite_teams', [])
|
||||
print(f"Raw favorite teams from config: {raw_favorite_teams}")
|
||||
|
||||
# Resolve dynamic teams (what odds ticker should do)
|
||||
resolved_teams = resolver.resolve_teams(raw_favorite_teams, 'ncaa_fb')
|
||||
print(f"Resolved teams: {resolved_teams}")
|
||||
print(f"Number of resolved teams: {len(resolved_teams)}")
|
||||
|
||||
# Verify results
|
||||
assert "UGA" in resolved_teams, "UGA should be in resolved teams"
|
||||
assert "AP_TOP_25" not in resolved_teams, "AP_TOP_25 should be resolved, not left as-is"
|
||||
assert len(resolved_teams) > 1, "Should have more than 1 team after resolving AP_TOP_25"
|
||||
|
||||
print("✓ Odds ticker configuration integration works correctly")
|
||||
return True
|
||||
|
||||
def test_odds_ticker_league_configs():
|
||||
"""Test how dynamic teams work with multiple league configs."""
|
||||
print("Testing multiple league configurations...")
|
||||
|
||||
# Simulate league configs that odds ticker would create
|
||||
league_configs = {
|
||||
'ncaa_fb': {
|
||||
'sport': 'football',
|
||||
'league': 'college-football',
|
||||
'favorite_teams': ['UGA', 'AP_TOP_25'],
|
||||
'enabled': True
|
||||
},
|
||||
'nfl': {
|
||||
'sport': 'football',
|
||||
'league': 'nfl',
|
||||
'favorite_teams': ['DAL', 'TB'],
|
||||
'enabled': True
|
||||
},
|
||||
'nba': {
|
||||
'sport': 'basketball',
|
||||
'league': 'nba',
|
||||
'favorite_teams': ['LAL', 'AP_TOP_10'], # Mixed regular and dynamic
|
||||
'enabled': True
|
||||
}
|
||||
}
|
||||
|
||||
resolver = DynamicTeamResolver()
|
||||
|
||||
# Simulate what odds ticker would do for each league
|
||||
for league_key, league_config in league_configs.items():
|
||||
if league_config.get('enabled', False):
|
||||
raw_favorite_teams = league_config.get('favorite_teams', [])
|
||||
if raw_favorite_teams:
|
||||
# Resolve dynamic teams for this league
|
||||
resolved_teams = resolver.resolve_teams(raw_favorite_teams, league_key)
|
||||
league_config['favorite_teams'] = resolved_teams
|
||||
|
||||
print(f"{league_key}: {raw_favorite_teams} -> {resolved_teams}")
|
||||
|
||||
# Verify results
|
||||
ncaa_fb_teams = league_configs['ncaa_fb']['favorite_teams']
|
||||
assert "UGA" in ncaa_fb_teams, "UGA should be in NCAA FB teams"
|
||||
assert "AP_TOP_25" not in ncaa_fb_teams, "AP_TOP_25 should be resolved"
|
||||
assert len(ncaa_fb_teams) > 1, "Should have more than 1 NCAA FB team"
|
||||
|
||||
nfl_teams = league_configs['nfl']['favorite_teams']
|
||||
assert nfl_teams == ['DAL', 'TB'], "NFL teams should be unchanged (no dynamic teams)"
|
||||
|
||||
nba_teams = league_configs['nba']['favorite_teams']
|
||||
assert "LAL" in nba_teams, "LAL should be in NBA teams"
|
||||
assert "AP_TOP_10" not in nba_teams, "AP_TOP_10 should be resolved"
|
||||
assert len(nba_teams) > 1, "Should have more than 1 NBA team"
|
||||
|
||||
print("✓ Multiple league configurations work correctly")
|
||||
return True
|
||||
|
||||
def test_odds_ticker_edge_cases():
|
||||
"""Test edge cases for odds ticker dynamic teams."""
|
||||
print("Testing edge cases...")
|
||||
|
||||
resolver = DynamicTeamResolver()
|
||||
|
||||
# Test empty favorite teams
|
||||
result = resolver.resolve_teams([], 'ncaa_fb')
|
||||
assert result == [], "Empty list should return empty list"
|
||||
print("✓ Empty favorite teams handling works")
|
||||
|
||||
# Test only regular teams
|
||||
result = resolver.resolve_teams(['UGA', 'AUB'], 'ncaa_fb')
|
||||
assert result == ['UGA', 'AUB'], "Regular teams should be unchanged"
|
||||
print("✓ Regular teams handling works")
|
||||
|
||||
# Test only dynamic teams
|
||||
result = resolver.resolve_teams(['AP_TOP_5'], 'ncaa_fb')
|
||||
assert len(result) > 0, "Dynamic teams should be resolved"
|
||||
assert "AP_TOP_5" not in result, "Dynamic team should be resolved"
|
||||
print("✓ Dynamic-only teams handling works")
|
||||
|
||||
# Test unknown dynamic teams
|
||||
result = resolver.resolve_teams(['AP_TOP_50'], 'ncaa_fb')
|
||||
assert result == [], "Unknown dynamic teams should be filtered out"
|
||||
print("✓ Unknown dynamic teams handling works")
|
||||
|
||||
print("✓ All edge cases handled correctly")
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
print("🧪 Testing OddsTickerManager Dynamic Teams Integration...")
|
||||
print("=" * 70)
|
||||
|
||||
test_odds_ticker_configuration()
|
||||
test_odds_ticker_league_configs()
|
||||
test_odds_ticker_edge_cases()
|
||||
|
||||
print("\n🎉 All odds ticker dynamic teams tests passed!")
|
||||
print("AP_TOP_25 will work correctly with the odds ticker!")
|
||||
print("\nThe odds ticker will now:")
|
||||
print("- Automatically resolve AP_TOP_25 to current top 25 teams")
|
||||
print("- Show odds for all current AP Top 25 teams")
|
||||
print("- Update automatically when rankings change")
|
||||
print("- Work seamlessly with existing favorite teams")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Test failed with error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
@@ -1,270 +1,117 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test Sports Integration
|
||||
|
||||
This test validates that all sports work together with the new architecture
|
||||
and that the system can handle multiple sports simultaneously.
|
||||
Integration test to verify dynamic team resolver works with sports managers.
|
||||
This test checks that the SportsCore class properly resolves dynamic teams.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
from typing import Dict, Any, List
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
import pytz
|
||||
|
||||
# Add src to path
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
||||
# Add the project root to the path so we can import the modules
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
def test_all_sports_configuration():
|
||||
"""Test that all sports have valid configurations."""
|
||||
print("🧪 Testing All Sports Configuration...")
|
||||
|
||||
try:
|
||||
from src.base_classes.sport_configs import get_sport_configs, get_sport_config
|
||||
|
||||
# Get all sport configurations
|
||||
configs = get_sport_configs()
|
||||
all_sports = list(configs.keys())
|
||||
|
||||
print(f"✅ Found {len(all_sports)} sports: {all_sports}")
|
||||
|
||||
# Test each sport
|
||||
for sport_key in all_sports:
|
||||
config = get_sport_config(sport_key, None)
|
||||
|
||||
# Validate basic configuration
|
||||
assert config.update_cadence in ['daily', 'weekly', 'hourly', 'live_only']
|
||||
assert config.season_length > 0
|
||||
assert config.games_per_week > 0
|
||||
assert config.data_source_type in ['espn', 'mlb_api', 'soccer_api']
|
||||
assert len(config.sport_specific_fields) > 0
|
||||
|
||||
print(f"✅ {sport_key}: {config.update_cadence}, {config.season_length} games, {config.data_source_type}")
|
||||
|
||||
print("✅ All sports have valid configurations")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ All sports configuration test failed: {e}")
|
||||
return False
|
||||
from src.base_classes.sports import SportsCore
|
||||
from src.display_manager import DisplayManager
|
||||
from src.cache_manager import CacheManager
|
||||
|
||||
def test_sports_api_extractors():
|
||||
"""Test that all sports have working API extractors."""
|
||||
print("\n🧪 Testing All Sports API Extractors...")
|
||||
|
||||
try:
|
||||
from src.base_classes.api_extractors import get_extractor_for_sport
|
||||
logger = logging.getLogger('test')
|
||||
|
||||
# Test all sports
|
||||
sports_to_test = ['nfl', 'ncaa_fb', 'mlb', 'nhl', 'ncaam_hockey', 'soccer', 'nba']
|
||||
|
||||
for sport_key in sports_to_test:
|
||||
extractor = get_extractor_for_sport(sport_key, logger)
|
||||
print(f"✅ {sport_key} extractor: {type(extractor).__name__}")
|
||||
|
||||
# Test that extractor has required methods
|
||||
assert hasattr(extractor, 'extract_game_details')
|
||||
assert hasattr(extractor, 'get_sport_specific_fields')
|
||||
assert callable(extractor.extract_game_details)
|
||||
assert callable(extractor.get_sport_specific_fields)
|
||||
|
||||
print("✅ All sports have working API extractors")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Sports API extractors test failed: {e}")
|
||||
return False
|
||||
def create_test_config():
|
||||
"""Create a test configuration with dynamic teams."""
|
||||
config = {
|
||||
"ncaa_fb_scoreboard": {
|
||||
"enabled": True,
|
||||
"show_favorite_teams_only": True,
|
||||
"favorite_teams": [
|
||||
"UGA",
|
||||
"AP_TOP_25"
|
||||
],
|
||||
"logo_dir": "assets/sports/ncaa_logos",
|
||||
"show_records": True,
|
||||
"show_ranking": True,
|
||||
"update_interval_seconds": 3600
|
||||
},
|
||||
"display": {
|
||||
"hardware": {
|
||||
"rows": 32,
|
||||
"cols": 64,
|
||||
"chain_length": 1
|
||||
}
|
||||
},
|
||||
"timezone": "America/Chicago"
|
||||
}
|
||||
return config
|
||||
|
||||
def test_sports_data_sources():
|
||||
"""Test that all sports have working data sources."""
|
||||
print("\n🧪 Testing All Sports Data Sources...")
|
||||
def test_sports_core_integration():
|
||||
"""Test that SportsCore properly resolves dynamic teams."""
|
||||
print("Testing SportsCore integration with dynamic teams...")
|
||||
|
||||
try:
|
||||
from src.base_classes.data_sources import get_data_source_for_sport
|
||||
from src.base_classes.sport_configs import get_sport_config
|
||||
logger = logging.getLogger('test')
|
||||
|
||||
# Test all sports
|
||||
sports_to_test = ['nfl', 'ncaa_fb', 'mlb', 'nhl', 'ncaam_hockey', 'soccer', 'nba']
|
||||
|
||||
for sport_key in sports_to_test:
|
||||
# Get sport configuration to determine data source type
|
||||
config = get_sport_config(sport_key, None)
|
||||
data_source_type = config.data_source_type
|
||||
|
||||
# Get data source
|
||||
data_source = get_data_source_for_sport(sport_key, data_source_type, logger)
|
||||
print(f"✅ {sport_key} data source: {type(data_source).__name__} ({data_source_type})")
|
||||
|
||||
# Test that data source has required methods
|
||||
assert hasattr(data_source, 'fetch_live_games')
|
||||
assert hasattr(data_source, 'fetch_schedule')
|
||||
assert hasattr(data_source, 'fetch_standings')
|
||||
assert callable(data_source.fetch_live_games)
|
||||
assert callable(data_source.fetch_schedule)
|
||||
assert callable(data_source.fetch_standings)
|
||||
|
||||
print("✅ All sports have working data sources")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Sports data sources test failed: {e}")
|
||||
return False
|
||||
# Create test configuration
|
||||
config = create_test_config()
|
||||
|
||||
# Create mock display manager and cache manager
|
||||
display_manager = DisplayManager(config)
|
||||
cache_manager = CacheManager(config)
|
||||
|
||||
# Create SportsCore instance
|
||||
sports_core = SportsCore(config, display_manager, cache_manager,
|
||||
__import__('logging').getLogger(__name__), "ncaa_fb")
|
||||
|
||||
# Check that favorite_teams were resolved
|
||||
print(f"Raw favorite teams from config: {config['ncaa_fb_scoreboard']['favorite_teams']}")
|
||||
print(f"Resolved favorite teams: {sports_core.favorite_teams}")
|
||||
|
||||
# Verify that UGA is still in the list
|
||||
assert "UGA" in sports_core.favorite_teams, "UGA should be in resolved teams"
|
||||
|
||||
# Verify that AP_TOP_25 was resolved to actual teams
|
||||
assert len(sports_core.favorite_teams) > 1, "Should have more than 1 team after resolving AP_TOP_25"
|
||||
|
||||
# Verify that AP_TOP_25 is not in the final list (should be resolved)
|
||||
assert "AP_TOP_25" not in sports_core.favorite_teams, "AP_TOP_25 should be resolved, not left as-is"
|
||||
|
||||
print(f"✓ SportsCore successfully resolved dynamic teams")
|
||||
print(f"✓ Final favorite teams: {sports_core.favorite_teams[:10]}{'...' if len(sports_core.favorite_teams) > 10 else ''}")
|
||||
|
||||
return True
|
||||
|
||||
def test_sports_consistency():
|
||||
"""Test that sports configurations are consistent and logical."""
|
||||
print("\n🧪 Testing Sports Consistency...")
|
||||
def test_dynamic_resolver_availability():
|
||||
"""Test that the dynamic resolver is available in SportsCore."""
|
||||
print("Testing dynamic resolver availability...")
|
||||
|
||||
try:
|
||||
from src.base_classes.sport_configs import get_sport_config
|
||||
|
||||
# Test that each sport has logical configuration
|
||||
sports_to_test = ['nfl', 'ncaa_fb', 'mlb', 'nhl', 'ncaam_hockey', 'soccer', 'nba']
|
||||
|
||||
for sport_key in sports_to_test:
|
||||
config = get_sport_config(sport_key, None)
|
||||
|
||||
# Test update cadence makes sense for season length
|
||||
if config.season_length > 100: # Long season (MLB, NBA, NHL)
|
||||
assert config.update_cadence in ['daily', 'hourly'], f"{sport_key} should have frequent updates for long season"
|
||||
elif config.season_length < 20: # Short season (NFL, NCAA)
|
||||
assert config.update_cadence in ['weekly', 'daily'], f"{sport_key} should have less frequent updates for short season"
|
||||
|
||||
# Test that games per week makes sense
|
||||
assert config.games_per_week > 0, f"{sport_key} should have at least 1 game per week"
|
||||
assert config.games_per_week <= 7, f"{sport_key} should not have more than 7 games per week"
|
||||
|
||||
# Test that season length is reasonable
|
||||
assert config.season_length > 0, f"{sport_key} should have positive season length"
|
||||
assert config.season_length < 200, f"{sport_key} season length seems too long"
|
||||
|
||||
print(f"✅ {sport_key} configuration is consistent")
|
||||
|
||||
print("✅ All sports configurations are consistent")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Sports consistency test failed: {e}")
|
||||
return False
|
||||
|
||||
def test_sports_uniqueness():
|
||||
"""Test that each sport has unique characteristics."""
|
||||
print("\n🧪 Testing Sports Uniqueness...")
|
||||
config = create_test_config()
|
||||
display_manager = DisplayManager(config)
|
||||
cache_manager = CacheManager(config)
|
||||
|
||||
try:
|
||||
from src.base_classes.sport_configs import get_sport_config
|
||||
|
||||
# Test that each sport has unique sport-specific fields
|
||||
sports_to_test = ['nfl', 'mlb', 'nhl', 'soccer']
|
||||
|
||||
sport_fields = {}
|
||||
for sport_key in sports_to_test:
|
||||
config = get_sport_config(sport_key, None)
|
||||
sport_fields[sport_key] = set(config.sport_specific_fields)
|
||||
|
||||
# Test that each sport has unique fields
|
||||
for sport_key in sports_to_test:
|
||||
current_fields = sport_fields[sport_key]
|
||||
|
||||
# Check that sport has unique fields
|
||||
if sport_key == 'nfl':
|
||||
assert 'down' in current_fields, "NFL should have down field"
|
||||
assert 'distance' in current_fields, "NFL should have distance field"
|
||||
assert 'possession' in current_fields, "NFL should have possession field"
|
||||
elif sport_key == 'mlb':
|
||||
assert 'inning' in current_fields, "MLB should have inning field"
|
||||
assert 'outs' in current_fields, "MLB should have outs field"
|
||||
assert 'bases' in current_fields, "MLB should have bases field"
|
||||
assert 'strikes' in current_fields, "MLB should have strikes field"
|
||||
assert 'balls' in current_fields, "MLB should have balls field"
|
||||
elif sport_key == 'nhl':
|
||||
assert 'period' in current_fields, "NHL should have period field"
|
||||
assert 'power_play' in current_fields, "NHL should have power_play field"
|
||||
assert 'penalties' in current_fields, "NHL should have penalties field"
|
||||
elif sport_key == 'soccer':
|
||||
assert 'half' in current_fields, "Soccer should have half field"
|
||||
assert 'stoppage_time' in current_fields, "Soccer should have stoppage_time field"
|
||||
assert 'cards' in current_fields, "Soccer should have cards field"
|
||||
assert 'possession' in current_fields, "Soccer should have possession field"
|
||||
|
||||
print(f"✅ {sport_key} has unique sport-specific fields")
|
||||
|
||||
print("✅ All sports have unique characteristics")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Sports uniqueness test failed: {e}")
|
||||
return False
|
||||
|
||||
def test_sports_data_source_mapping():
|
||||
"""Test that sports are mapped to appropriate data sources."""
|
||||
print("\n🧪 Testing Sports Data Source Mapping...")
|
||||
sports_core = SportsCore(config, display_manager, cache_manager,
|
||||
__import__('logging').getLogger(__name__), "ncaa_fb")
|
||||
|
||||
try:
|
||||
from src.base_classes.sport_configs import get_sport_config
|
||||
|
||||
# Test that each sport uses an appropriate data source
|
||||
sports_to_test = ['nfl', 'ncaa_fb', 'mlb', 'nhl', 'ncaam_hockey', 'soccer', 'nba']
|
||||
|
||||
for sport_key in sports_to_test:
|
||||
config = get_sport_config(sport_key, None)
|
||||
data_source_type = config.data_source_type
|
||||
|
||||
# Test that data source type makes sense for the sport
|
||||
if sport_key == 'mlb':
|
||||
assert data_source_type == 'mlb_api', "MLB should use MLB API"
|
||||
elif sport_key == 'soccer':
|
||||
assert data_source_type == 'soccer_api', "Soccer should use Soccer API"
|
||||
else:
|
||||
assert data_source_type == 'espn', f"{sport_key} should use ESPN API"
|
||||
|
||||
print(f"✅ {sport_key} uses appropriate data source: {data_source_type}")
|
||||
|
||||
print("✅ All sports use appropriate data sources")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Sports data source mapping test failed: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""Run all sports integration tests."""
|
||||
print("🏈 Testing Sports Integration")
|
||||
print("=" * 50)
|
||||
# Check that dynamic resolver is available
|
||||
assert hasattr(sports_core, 'dynamic_resolver'), "SportsCore should have dynamic_resolver attribute"
|
||||
assert sports_core.dynamic_resolver is not None, "Dynamic resolver should be initialized"
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
# Test dynamic resolver methods
|
||||
assert sports_core.dynamic_resolver.is_dynamic_team("AP_TOP_25"), "Should detect AP_TOP_25 as dynamic"
|
||||
assert not sports_core.dynamic_resolver.is_dynamic_team("UGA"), "Should not detect UGA as dynamic"
|
||||
|
||||
# Run all tests
|
||||
tests = [
|
||||
test_all_sports_configuration,
|
||||
test_sports_api_extractors,
|
||||
test_sports_data_sources,
|
||||
test_sports_consistency,
|
||||
test_sports_uniqueness,
|
||||
test_sports_data_source_mapping
|
||||
]
|
||||
print("✓ Dynamic resolver is properly integrated")
|
||||
|
||||
passed = 0
|
||||
total = len(tests)
|
||||
|
||||
for test in tests:
|
||||
try:
|
||||
if test():
|
||||
passed += 1
|
||||
except Exception as e:
|
||||
print(f"❌ Test {test.__name__} failed with exception: {e}")
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print(f"🏁 Sports Integration Test Results: {passed}/{total} tests passed")
|
||||
|
||||
if passed == total:
|
||||
print("🎉 All sports integration tests passed! The system can handle multiple sports.")
|
||||
return True
|
||||
else:
|
||||
print("❌ Some sports integration tests failed. Please check the errors above.")
|
||||
return False
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = main()
|
||||
sys.exit(0 if success else 1)
|
||||
try:
|
||||
print("🧪 Testing Sports Integration with Dynamic Teams...")
|
||||
print("=" * 50)
|
||||
|
||||
test_sports_core_integration()
|
||||
test_dynamic_resolver_availability()
|
||||
|
||||
print("\n🎉 All integration tests passed!")
|
||||
print("Dynamic team resolver is successfully integrated with SportsCore!")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Integration test failed with error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user