diff --git a/tools/data/test_downloader.py b/tools/data/test_downloader.py deleted file mode 100644 index cc781ed..0000000 --- a/tools/data/test_downloader.py +++ /dev/null @@ -1,295 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script for the Pokemon data downloader. - -This script tests the downloader with small data segments to ensure -everything works correctly before downloading larger datasets. -""" - -import sys -import tempfile -import shutil -from pathlib import Path -import json - -# Add the tools directory to Python path for imports -sys.path.insert(0, str(Path(__file__).parent.parent)) - -from data.pokemon_downloader import PokemonDownloader -from data.schemas import DataValidator -from rich.console import Console -from rich.panel import Panel - -console = Console() - - -def test_small_pokemon_download(): - """Test downloading a small set of Pokemon (first 3).""" - console.print(Panel.fit("๐Ÿงช Testing Pokemon Download (IDs 1-3)", style="bold yellow")) - - # Create temporary directory for test output - with tempfile.TemporaryDirectory() as temp_dir: - downloader = PokemonDownloader(output_dir=Path(temp_dir)) - - # Download first 3 Pokemon - pokemon_data = downloader.download_pokemon_batch(1, 3, max_workers=2) - - if not pokemon_data: - console.print("โŒ Failed to download any Pokemon") - return False - - console.print(f"โœ… Downloaded {len(pokemon_data)} Pokemon:") - for pokemon_id, pokemon in pokemon_data.items(): - console.print(f" - #{pokemon_id}: {pokemon.name.title()} ({', '.join(pokemon.types)})") - - # Save and validate - downloader.save_pokemon_data(pokemon_data, "test_pokemon.json") - - # Check the saved file - saved_file = Path(temp_dir) / "test_pokemon.json" - if saved_file.exists(): - with open(saved_file) as f: - saved_data = json.load(f) - console.print(f"โœ… Successfully saved {len(saved_data)} Pokemon to file") - - # Show first Pokemon details - first_pokemon = list(saved_data.values())[0] - console.print(f"๐Ÿ“Š Sample data for {first_pokemon['name']}:") - console.print(f" - Types: {first_pokemon['types']}") - console.print(f" - Base HP: {first_pokemon['base_stats']['hp']}") - console.print(f" - Abilities: {first_pokemon['abilities'][:2]}...") # Show first 2 - console.print(f" - Move count: {len(first_pokemon['moves'])}") - - return True - - -def test_moves_download(): - """Test downloading a small set of moves.""" - console.print(Panel.fit("๐Ÿงช Testing Moves Download (IDs 1-5)", style="bold yellow")) - - with tempfile.TemporaryDirectory() as temp_dir: - downloader = PokemonDownloader(output_dir=Path(temp_dir)) - - # Download first 5 moves - move_ids = [1, 2, 3, 4, 5] # Pound, Karate Chop, Double Slap, Comet Punch, Mega Punch - moves_data = downloader.download_moves_batch(move_ids, max_workers=2) - - if not moves_data: - console.print("โŒ Failed to download any moves") - return False - - console.print(f"โœ… Downloaded {len(moves_data)} moves:") - for move_id, move in moves_data.items(): - power_str = f"{move.power} power" if move.power else "no power" - console.print(f" - #{move_id}: {move.name.title()} ({move.type}, {power_str})") - - # Save and validate - downloader.save_moves_data(moves_data, "test_moves.json") - - # Check the saved file - saved_file = Path(temp_dir) / "test_moves.json" - if saved_file.exists(): - with open(saved_file) as f: - saved_data = json.load(f) - console.print(f"โœ… Successfully saved {len(saved_data)} moves to file") - - # Show move details - for move_data in list(saved_data.values())[:2]: # Show first 2 moves - console.print(f"๐Ÿ“Š {move_data['name'].title()}:") - console.print(f" - Type: {move_data['type']}") - console.print(f" - Power: {move_data['power']}") - console.print(f" - Accuracy: {move_data['accuracy']}") - console.print(f" - PP: {move_data['pp']}") - - return True - - -def test_type_effectiveness(): - """Test downloading type effectiveness data.""" - console.print(Panel.fit("๐Ÿงช Testing Type Effectiveness Download", style="bold yellow")) - - with tempfile.TemporaryDirectory() as temp_dir: - downloader = PokemonDownloader(output_dir=Path(temp_dir)) - - # Download type effectiveness - effectiveness_data = downloader.download_type_effectiveness() - - if not effectiveness_data: - console.print("โŒ Failed to download type effectiveness data") - return False - - console.print(f"โœ… Downloaded {len(effectiveness_data)} type effectiveness entries") - - # Show some examples - console.print("๐Ÿ“Š Sample type effectiveness entries:") - for entry in effectiveness_data[:5]: - factor_str = {0.0: "no effect", 0.5: "not very effective", 2.0: "super effective"} - console.print(f" - {entry.attacking_type} vs {entry.defending_type}: {factor_str.get(entry.damage_factor, str(entry.damage_factor))}") - - # Save and validate - downloader.save_type_effectiveness(effectiveness_data, "test_types.json") - - # Check the saved file - saved_file = Path(temp_dir) / "test_types.json" - if saved_file.exists(): - with open(saved_file) as f: - saved_data = json.load(f) - console.print(f"โœ… Successfully saved {len(saved_data)} type effectiveness entries to file") - - return True - - -def test_validation(): - """Test the data validation system.""" - console.print(Panel.fit("๐Ÿงช Testing Data Validation", style="bold yellow")) - - validator = DataValidator() - - # Test valid Pokemon data - valid_pokemon = { - "1": { - "id": 1, - "name": "bulbasaur", - "types": ["grass", "poison"], - "base_stats": { - "hp": 45, - "attack": 49, - "defense": 49, - "special_attack": 65, - "special_defense": 65, - "speed": 45 - }, - "abilities": ["overgrow", "chlorophyll"], - "moves": [1, 2, 3, 4], - "weight": 69, - "height": 7, - "base_experience": 64 - } - } - - errors = validator.validate_pokemon_collection(valid_pokemon) - if errors: - console.print(f"โŒ Validation failed for valid data: {errors}") - return False - else: - console.print("โœ… Valid Pokemon data passed validation") - - # Test invalid Pokemon data - invalid_pokemon = { - "1": { - "id": 1, - "name": "bulbasaur", - "types": ["grass", "invalid_type"], # Invalid type - "base_stats": { - "hp": 45, - "attack": 49, - "defense": 49, - "special_attack": 65, - "special_defense": 65, - "speed": 45 - }, - "abilities": ["overgrow"], - "moves": [1, 2, 3, 4], - "weight": 69, - "height": 7, - "base_experience": 64 - } - } - - errors = validator.validate_pokemon_collection(invalid_pokemon) - if errors: - console.print(f"โœ… Invalid Pokemon data correctly failed validation: {len(errors)} errors") - else: - console.print("โŒ Invalid data should have failed validation") - return False - - return True - - -def test_integrated_download(): - """Test downloading Pokemon with their moves in an integrated fashion.""" - console.print(Panel.fit("๐Ÿงช Testing Integrated Pokemon + Moves Download", style="bold yellow")) - - with tempfile.TemporaryDirectory() as temp_dir: - downloader = PokemonDownloader(output_dir=Path(temp_dir)) - - # Download a single Pokemon (Pikachu) - pokemon_data = downloader.download_pokemon_batch(25, 25, max_workers=1) - - if not pokemon_data: - console.print("โŒ Failed to download Pikachu") - return False - - pikachu = pokemon_data[25] - console.print(f"โœ… Downloaded {pikachu.name.title()}") - console.print(f" - Types: {pikachu.types}") - console.print(f" - Base stats total: {sum(pikachu.base_stats.__dict__.values())}") - console.print(f" - Can learn {len(pikachu.moves)} moves") - - # Download first 10 moves that Pikachu can learn - pikachu_moves = pikachu.moves[:10] - moves_data = downloader.download_moves_batch(pikachu_moves, max_workers=3) - - if moves_data: - console.print(f"โœ… Downloaded {len(moves_data)} of Pikachu's moves:") - for move_id, move in list(moves_data.items())[:5]: - console.print(f" - {move.name.title()} ({move.type} type)") - - # Save both datasets - downloader.save_pokemon_data(pokemon_data, "pikachu.json") - downloader.save_moves_data(moves_data, "pikachu_moves.json") - - return True - - -def run_all_tests(): - """Run all tests.""" - console.print(Panel.fit( - "๐Ÿš€ Pokemon Data Downloader Test Suite", - style="bold green" - )) - - tests = [ - ("Pokemon Download", test_small_pokemon_download), - ("Moves Download", test_moves_download), - ("Type Effectiveness", test_type_effectiveness), - ("Data Validation", test_validation), - ("Integrated Download", test_integrated_download), - ] - - results = [] - - for test_name, test_func in tests: - console.print(f"\n{'='*50}") - try: - result = test_func() - results.append((test_name, result)) - status = "โœ… PASSED" if result else "โŒ FAILED" - console.print(f"{test_name}: {status}") - except Exception as e: - results.append((test_name, False)) - console.print(f"{test_name}: โŒ ERROR - {e}") - - # Summary - console.print(f"\n{'='*50}") - console.print("TEST SUMMARY:") - passed = sum(1 for _, result in results if result) - total = len(results) - - for test_name, result in results: - status = "โœ…" if result else "โŒ" - console.print(f" {status} {test_name}") - - console.print(f"\nOverall: {passed}/{total} tests passed") - - if passed == total: - console.print(Panel.fit("๐ŸŽ‰ All tests passed! The downloader is ready to use.", style="bold green")) - else: - console.print(Panel.fit("โš ๏ธ Some tests failed. Please check the errors above.", style="bold red")) - - return passed == total - - -if __name__ == "__main__": - success = run_all_tests() - sys.exit(0 if success else 1) diff --git a/tools/testing/test_downloader.py b/tools/testing/test_downloader.py new file mode 100644 index 0000000..6af438d --- /dev/null +++ b/tools/testing/test_downloader.py @@ -0,0 +1,326 @@ +#!/usr/bin/env python3 +""" +Pytest test suite for the Pokemon data downloader. + +This module contains pytest-compatible tests for the downloader with small data segments +to ensure everything works correctly before downloading larger datasets. +""" + +import sys +import tempfile +import json +from pathlib import Path +import pytest + +# Add the tools directory to Python path for imports +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from data.pokemon_downloader import PokemonDownloader +from data.schemas import DataValidator +from rich.console import Console +from rich.panel import Panel + +console = Console() + + +@pytest.fixture +def temp_output_dir(): + """Fixture providing a temporary directory for test outputs.""" + with tempfile.TemporaryDirectory() as temp_dir: + yield Path(temp_dir) + + +@pytest.fixture +def downloader(temp_output_dir): + """Fixture providing a PokemonDownloader instance with temporary output directory.""" + return PokemonDownloader(output_dir=temp_output_dir) + + +@pytest.fixture +def data_validator(): + """Fixture providing a DataValidator instance.""" + return DataValidator() + + +def test_small_pokemon_download(downloader, temp_output_dir): + """Test downloading a small set of Pokemon (first 3).""" + console.print(Panel.fit("๐Ÿงช Testing Pokemon Download (IDs 1-3)", style="bold yellow")) + + # Download first 3 Pokemon + pokemon_data = downloader.download_pokemon_batch(1, 3, max_workers=2) + + # Assertions using pytest + assert pokemon_data is not None, "Failed to download any Pokemon" + assert len(pokemon_data) > 0, "Pokemon data should not be empty" + assert len(pokemon_data) <= 3, "Should not download more than 3 Pokemon" + + console.print(f"โœ… Downloaded {len(pokemon_data)} Pokemon:") + for pokemon_id, pokemon in pokemon_data.items(): + console.print(f" - #{pokemon_id}: {pokemon.name.title()} ({', '.join(pokemon.types)})") + # Assert each pokemon has required attributes + assert hasattr(pokemon, 'name'), f"Pokemon {pokemon_id} missing name" + assert hasattr(pokemon, 'types'), f"Pokemon {pokemon_id} missing types" + assert len(pokemon.types) > 0, f"Pokemon {pokemon_id} should have at least one type" + + # Save and validate + downloader.save_pokemon_data(pokemon_data, "test_pokemon.json") + + # Check the saved file + saved_file = temp_output_dir / "test_pokemon.json" + assert saved_file.exists(), "Saved Pokemon file should exist" + + with open(saved_file) as f: + saved_data = json.load(f) + + assert len(saved_data) == len(pokemon_data), "Saved data should match downloaded data length" + console.print(f"โœ… Successfully saved {len(saved_data)} Pokemon to file") + + # Show first Pokemon details + first_pokemon = list(saved_data.values())[0] + console.print(f"๐Ÿ“Š Sample data for {first_pokemon['name']}:") + console.print(f" - Types: {first_pokemon['types']}") + console.print(f" - Base HP: {first_pokemon['base_stats']['hp']}") + console.print(f" - Abilities: {first_pokemon['abilities'][:2]}...") # Show first 2 + console.print(f" - Move count: {len(first_pokemon['moves'])}") + + # Assert the structure of saved data + assert 'name' in first_pokemon, "Pokemon should have name" + assert 'types' in first_pokemon, "Pokemon should have types" + assert 'base_stats' in first_pokemon, "Pokemon should have base_stats" + assert 'hp' in first_pokemon['base_stats'], "Pokemon should have HP stat" + + +def test_moves_download(downloader, temp_output_dir): + """Test downloading a small set of moves.""" + console.print(Panel.fit("๐Ÿงช Testing Moves Download (IDs 1-5)", style="bold yellow")) + + # Download first 5 moves + move_ids = [1, 2, 3, 4, 5] # Pound, Karate Chop, Double Slap, Comet Punch, Mega Punch + moves_data = downloader.download_moves_batch(move_ids, max_workers=2) + + # Assertions using pytest + assert moves_data is not None, "Failed to download any moves" + assert len(moves_data) > 0, "Moves data should not be empty" + assert len(moves_data) <= 5, "Should not download more than 5 moves" + + console.print(f"โœ… Downloaded {len(moves_data)} moves:") + for move_id, move in moves_data.items(): + power_str = f"{move.power} power" if move.power else "no power" + console.print(f" - #{move_id}: {move.name.title()} ({move.type}, {power_str})") + + # Assert each move has required attributes + assert hasattr(move, 'name'), f"Move {move_id} missing name" + assert hasattr(move, 'type'), f"Move {move_id} missing type" + assert move.name, f"Move {move_id} should have a non-empty name" + + # Save and validate + downloader.save_moves_data(moves_data, "test_moves.json") + + # Check the saved file + saved_file = temp_output_dir / "test_moves.json" + assert saved_file.exists(), "Saved moves file should exist" + + with open(saved_file) as f: + saved_data = json.load(f) + + assert len(saved_data) == len(moves_data), "Saved data should match downloaded data length" + console.print(f"โœ… Successfully saved {len(saved_data)} moves to file") + + # Show move details and assert structure + for move_data in list(saved_data.values())[:2]: # Show first 2 moves + console.print(f"๐Ÿ“Š {move_data['name'].title()}:") + console.print(f" - Type: {move_data['type']}") + console.print(f" - Power: {move_data['power']}") + console.print(f" - Accuracy: {move_data['accuracy']}") + console.print(f" - PP: {move_data['pp']}") + + # Assert the structure of saved move data + assert 'name' in move_data, "Move should have name" + assert 'type' in move_data, "Move should have type" + assert 'power' in move_data, "Move should have power (can be None)" + assert 'accuracy' in move_data, "Move should have accuracy" + assert 'pp' in move_data, "Move should have PP" + + +def test_type_effectiveness(downloader, temp_output_dir): + """Test downloading type effectiveness data.""" + console.print(Panel.fit("๐Ÿงช Testing Type Effectiveness Download", style="bold yellow")) + + # Download type effectiveness + effectiveness_data = downloader.download_type_effectiveness() + + # Assertions using pytest + assert effectiveness_data is not None, "Failed to download type effectiveness data" + assert len(effectiveness_data) > 0, "Type effectiveness data should not be empty" + + console.print(f"โœ… Downloaded {len(effectiveness_data)} type effectiveness entries") + + # Show some examples and validate structure + console.print("๐Ÿ“Š Sample type effectiveness entries:") + for entry in effectiveness_data[:5]: + factor_str = {0.0: "no effect", 0.5: "not very effective", 2.0: "super effective"} + console.print(f" - {entry.attacking_type} vs {entry.defending_type}: {factor_str.get(entry.damage_factor, str(entry.damage_factor))}") + + # Assert each entry has required attributes + assert hasattr(entry, 'attacking_type'), "Entry missing attacking_type" + assert hasattr(entry, 'defending_type'), "Entry missing defending_type" + assert hasattr(entry, 'damage_factor'), "Entry missing damage_factor" + assert isinstance(entry.damage_factor, (int, float)), "Damage factor should be numeric" + + # Save and validate + downloader.save_type_effectiveness(effectiveness_data, "test_types.json") + + # Check the saved file + saved_file = temp_output_dir / "test_types.json" + assert saved_file.exists(), "Saved type effectiveness file should exist" + + with open(saved_file) as f: + saved_data = json.load(f) + + assert len(saved_data) == len(effectiveness_data), "Saved data should match downloaded data length" + console.print(f"โœ… Successfully saved {len(saved_data)} type effectiveness entries to file") + + +def test_validation(data_validator): + """Test the data validation system.""" + console.print(Panel.fit("๐Ÿงช Testing Data Validation", style="bold yellow")) + + # Test valid Pokemon data + valid_pokemon = { + "1": { + "id": 1, + "name": "bulbasaur", + "types": ["grass", "poison"], + "base_stats": { + "hp": 45, + "attack": 49, + "defense": 49, + "special_attack": 65, + "special_defense": 65, + "speed": 45 + }, + "abilities": ["overgrow", "chlorophyll"], + "moves": [1, 2, 3, 4], + "weight": 69, + "height": 7, + "base_experience": 64 + } + } + + errors = data_validator.validate_pokemon_collection(valid_pokemon) + assert not errors, f"Validation should pass for valid data, but got errors: {errors}" + console.print("โœ… Valid Pokemon data passed validation") + + # Test invalid Pokemon data + invalid_pokemon = { + "1": { + "id": 1, + "name": "bulbasaur", + "types": ["grass", "invalid_type"], # Invalid type + "base_stats": { + "hp": 45, + "attack": 49, + "defense": 49, + "special_attack": 65, + "special_defense": 65, + "speed": 45 + }, + "abilities": ["overgrow"], + "moves": [1, 2, 3, 4], + "weight": 69, + "height": 7, + "base_experience": 64 + } + } + + errors = data_validator.validate_pokemon_collection(invalid_pokemon) + assert errors, "Invalid Pokemon data should fail validation" + assert len(errors) > 0, "Should have validation errors for invalid data" + console.print(f"โœ… Invalid Pokemon data correctly failed validation: {len(errors)} errors") + + +def test_integrated_download(downloader, temp_output_dir): + """Test downloading Pokemon with their moves in an integrated fashion.""" + console.print(Panel.fit("๐Ÿงช Testing Integrated Pokemon + Moves Download", style="bold yellow")) + + # Download a single Pokemon (Pikachu) + pokemon_data = downloader.download_pokemon_batch(25, 25, max_workers=1) + + assert pokemon_data is not None, "Failed to download Pikachu" + assert 25 in pokemon_data, "Pikachu (ID 25) should be in the downloaded data" + + pikachu = pokemon_data[25] + assert pikachu.name.lower() == "pikachu", "Downloaded Pokemon should be Pikachu" + assert len(pikachu.types) > 0, "Pikachu should have at least one type" + assert len(pikachu.moves) > 0, "Pikachu should have moves" + + console.print(f"โœ… Downloaded {pikachu.name.title()}") + console.print(f" - Types: {pikachu.types}") + console.print(f" - Base stats total: {sum(pikachu.base_stats.__dict__.values())}") + console.print(f" - Can learn {len(pikachu.moves)} moves") + + # Download first 10 moves that Pikachu can learn + pikachu_moves = pikachu.moves[:10] + moves_data = downloader.download_moves_batch(pikachu_moves, max_workers=3) + + assert moves_data is not None, "Should successfully download Pikachu's moves" + assert len(moves_data) > 0, "Should download at least some of Pikachu's moves" + + console.print(f"โœ… Downloaded {len(moves_data)} of Pikachu's moves:") + for move_id, move in list(moves_data.items())[:5]: + console.print(f" - {move.name.title()} ({move.type} type)") + assert hasattr(move, 'name'), f"Move {move_id} should have a name" + assert hasattr(move, 'type'), f"Move {move_id} should have a type" + + # Save both datasets + downloader.save_pokemon_data(pokemon_data, "pikachu.json") + downloader.save_moves_data(moves_data, "pikachu_moves.json") + + # Verify files were saved + assert (temp_output_dir / "pikachu.json").exists(), "Pikachu data file should be saved" + assert (temp_output_dir / "pikachu_moves.json").exists(), "Pikachu moves data file should be saved" + + +# Parametrized tests for better pytest organization +@pytest.mark.parametrize("pokemon_range", [(1, 3), (4, 6)]) +def test_pokemon_download_ranges(downloader, temp_output_dir, pokemon_range): + """Test downloading different ranges of Pokemon.""" + start_id, end_id = pokemon_range + pokemon_data = downloader.download_pokemon_batch(start_id, end_id, max_workers=2) + + assert pokemon_data is not None, f"Failed to download Pokemon {start_id}-{end_id}" + assert len(pokemon_data) > 0, "Pokemon data should not be empty" + + for pokemon_id, pokemon in pokemon_data.items(): + assert start_id <= pokemon_id <= end_id, f"Pokemon ID {pokemon_id} should be in range {start_id}-{end_id}" + assert hasattr(pokemon, 'name'), f"Pokemon {pokemon_id} missing name" + assert hasattr(pokemon, 'types'), f"Pokemon {pokemon_id} missing types" + + +@pytest.mark.parametrize("move_ids", [[1, 2, 3], [10, 11, 12, 13]]) +def test_moves_download_batches(downloader, temp_output_dir, move_ids): + """Test downloading different batches of moves.""" + moves_data = downloader.download_moves_batch(move_ids, max_workers=2) + + assert moves_data is not None, f"Failed to download moves {move_ids}" + assert len(moves_data) > 0, "Moves data should not be empty" + + for move_id, move in moves_data.items(): + assert move_id in move_ids, f"Move ID {move_id} should be in requested list" + assert hasattr(move, 'name'), f"Move {move_id} missing name" + assert hasattr(move, 'type'), f"Move {move_id} missing type" + + +# Test that can be run directly with python for debugging +if __name__ == "__main__": + # This allows the file to be run directly for debugging, but pytest is preferred + console.print(Panel.fit( + "๐Ÿš€ Pokemon Data Downloader Tests\n\nFor full test suite, please use: pytest tools/data/test_downloader.py", + style="bold yellow" + )) + console.print("\nRunning basic validation test...") + + # Just run a quick validation test when run directly + validator = DataValidator() + test_validation(validator) + console.print("\nโœ… Basic test passed! Use pytest for full test suite.")