#!/usr/bin/env python3 """ Test Asset Tracking Functionality This script tests the asset tracking system to ensure new assets are detected and only new/modified assets are downloaded. """ import asyncio import json import logging import sys import tempfile from pathlib import Path from datetime import datetime import os # Add the current directory to the path so we can import modules sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from asset_tracker import AssetTracker from auth_manager import AuthManager from image_downloader import ImageDownloader class AssetTrackingTester: """Test class for asset tracking functionality.""" def __init__(self): """Initialize the tester.""" self.logger = logging.getLogger(__name__) # Mock API data for testing self.mock_assets_v1 = [ { "id": "asset_001", "name": "family_photo_1.jpg", "updated": "2024-01-01T10:00:00Z", "size": 1024000, "mimeType": "image/jpeg" }, { "id": "asset_002", "name": "birthday_party.jpg", "updated": "2024-01-02T15:30:00Z", "size": 2048000, "mimeType": "image/jpeg" }, { "id": "asset_003", "name": "school_event.png", "updated": "2024-01-03T09:15:00Z", "size": 1536000, "mimeType": "image/png" } ] self.mock_assets_v2 = [ # Existing asset - unchanged { "id": "asset_001", "name": "family_photo_1.jpg", "updated": "2024-01-01T10:00:00Z", "size": 1024000, "mimeType": "image/jpeg" }, # Existing asset - modified { "id": "asset_002", "name": "birthday_party.jpg", "updated": "2024-01-05T16:45:00Z", # Updated timestamp "size": 2100000, # Different size "mimeType": "image/jpeg" }, # Existing asset - unchanged { "id": "asset_003", "name": "school_event.png", "updated": "2024-01-03T09:15:00Z", "size": 1536000, "mimeType": "image/png" }, # New asset { "id": "asset_004", "name": "new_vacation_photo.jpg", "updated": "2024-01-06T14:20:00Z", "size": 3072000, "mimeType": "image/jpeg" } ] def test_basic_tracking(self): """Test basic asset tracking functionality.""" print("=" * 60) print("TEST 1: Basic Asset Tracking") print("=" * 60) with tempfile.TemporaryDirectory() as temp_dir: tracker = AssetTracker(storage_dir=temp_dir) print(f"Testing with temporary directory: {temp_dir}") # Test with empty tracker print("\n1. Testing new asset detection (empty tracker)...") new_assets = tracker.get_new_assets(self.mock_assets_v1) print(f" Found {len(new_assets)} new assets (expected: 3)") assert len(new_assets) == 3, f"Expected 3 new assets, got {len(new_assets)}" print(" ✅ All assets correctly identified as new") # Simulate downloading first batch print("\n2. Simulating download of first batch...") for asset in self.mock_assets_v1: filename = asset['name'] filepath = Path(temp_dir) / filename # Create dummy file filepath.write_text(f"Mock content for {asset['id']}") # Mark as downloaded tracker.mark_asset_downloaded(asset, filepath, True) print(f" Marked as downloaded: {filename}") # Test tracker stats stats = tracker.get_stats() print(f"\n3. Tracker statistics after first batch:") print(f" Total tracked assets: {stats['total_tracked_assets']}") print(f" Successful downloads: {stats['successful_downloads']}") print(f" Existing files: {stats['existing_files']}") # Test with same assets (should find no new ones) print("\n4. Testing with same assets (should find none)...") new_assets = tracker.get_new_assets(self.mock_assets_v1) print(f" Found {len(new_assets)} new assets (expected: 0)") assert len(new_assets) == 0, f"Expected 0 new assets, got {len(new_assets)}" print(" ✅ Correctly identified all assets as already downloaded") print("\n✅ Basic tracking test passed!") def test_modified_asset_detection(self): """Test detection of modified assets.""" print("\n" + "=" * 60) print("TEST 2: Modified Asset Detection") print("=" * 60) with tempfile.TemporaryDirectory() as temp_dir: tracker = AssetTracker(storage_dir=temp_dir) # Simulate first batch download print("1. Simulating initial download...") for asset in self.mock_assets_v1: filename = asset['name'] filepath = Path(temp_dir) / filename filepath.write_text(f"Mock content for {asset['id']}") tracker.mark_asset_downloaded(asset, filepath, True) print(f" Downloaded {len(self.mock_assets_v1)} assets") # Test with modified assets print("\n2. Testing with modified asset list...") new_assets = tracker.get_new_assets(self.mock_assets_v2) print(f" Found {len(new_assets)} new/modified assets") # Should detect 1 modified + 1 new = 2 assets expected = 2 # asset_002 (modified) + asset_004 (new) assert len(new_assets) == expected, f"Expected {expected} assets, got {len(new_assets)}" # Check which assets were detected detected_ids = [asset['id'] for asset in new_assets] print(f" Detected asset IDs: {detected_ids}") assert 'asset_002' in detected_ids, "Modified asset_002 should be detected" assert 'asset_004' in detected_ids, "New asset_004 should be detected" assert 'asset_001' not in detected_ids, "Unchanged asset_001 should not be detected" assert 'asset_003' not in detected_ids, "Unchanged asset_003 should not be detected" print(" ✅ Correctly identified 1 modified + 1 new asset") print("✅ Modified asset detection test passed!") def test_cleanup_functionality(self): """Test cleanup of missing files.""" print("\n" + "=" * 60) print("TEST 3: Cleanup Functionality") print("=" * 60) with tempfile.TemporaryDirectory() as temp_dir: tracker = AssetTracker(storage_dir=temp_dir) # Create some files and track them print("1. Creating and tracking assets...") filepaths = [] for asset in self.mock_assets_v1: filename = asset['name'] filepath = Path(temp_dir) / filename filepath.write_text(f"Mock content for {asset['id']}") tracker.mark_asset_downloaded(asset, filepath, True) filepaths.append(filepath) print(f" Created and tracked: {filename}") # Remove one file manually print("\n2. Removing one file manually...") removed_file = filepaths[1] removed_file.unlink() print(f" Removed: {removed_file.name}") # Check stats before cleanup stats_before = tracker.get_stats() print(f"\n3. Stats before cleanup:") print(f" Total tracked: {stats_before['total_tracked_assets']}") print(f" Existing files: {stats_before['existing_files']}") print(f" Missing files: {stats_before['missing_files']}") # Run cleanup print("\n4. Running cleanup...") tracker.cleanup_missing_files() # Check stats after cleanup stats_after = tracker.get_stats() print(f"\n5. Stats after cleanup:") print(f" Total tracked: {stats_after['total_tracked_assets']}") print(f" Existing files: {stats_after['existing_files']}") print(f" Missing files: {stats_after['missing_files']}") # Verify cleanup worked assert stats_after['missing_files'] == 0, "Should have no missing files after cleanup" assert stats_after['total_tracked_assets'] == len(self.mock_assets_v1) - 1, "Should have one less tracked asset" print(" ✅ Cleanup successfully removed missing file metadata") print("✅ Cleanup functionality test passed!") async def test_integration_with_downloader(self): """Test integration with ImageDownloader.""" print("\n" + "=" * 60) print("TEST 4: Integration with ImageDownloader") print("=" * 60) # Note: This test requires actual API credentials to work fully # For now, we'll test the initialization and basic functionality with tempfile.TemporaryDirectory() as temp_dir: print(f"1. Testing ImageDownloader with asset tracking...") try: downloader = ImageDownloader( api_url="https://api.parentzone.me", list_endpoint="/v1/media/list", download_endpoint="/v1/media", output_dir=temp_dir, track_assets=True ) # Check if asset tracker was initialized if downloader.asset_tracker: print(" ✅ Asset tracker successfully initialized in downloader") # Test tracker stats stats = downloader.asset_tracker.get_stats() print(f" Initial stats: {stats['total_tracked_assets']} tracked assets") else: print(" ❌ Asset tracker was not initialized") except Exception as e: print(f" Error during downloader initialization: {e}") print("✅ Integration test completed!") def run_all_tests(self): """Run all tests.""" print("🚀 Starting Asset Tracking Tests") print("=" * 80) try: self.test_basic_tracking() self.test_modified_asset_detection() self.test_cleanup_functionality() asyncio.run(self.test_integration_with_downloader()) print("\n" + "=" * 80) print("🎉 ALL TESTS PASSED!") print("=" * 80) return True except Exception as e: print(f"\n❌ TEST FAILED: {e}") import traceback traceback.print_exc() return False async def test_with_real_api(): """Test with real API (requires authentication).""" print("\n" + "=" * 60) print("REAL API TEST: Asset Tracking with ParentZone API") print("=" * 60) # Test credentials email = "tudor.sitaru@gmail.com" password = "mTVq8uNUvY7R39EPGVAm@" with tempfile.TemporaryDirectory() as temp_dir: print(f"Using temporary directory: {temp_dir}") try: # Create downloader with asset tracking downloader = ImageDownloader( api_url="https://api.parentzone.me", list_endpoint="/v1/media/list", download_endpoint="/v1/media", output_dir=temp_dir, email=email, password=password, track_assets=True, max_concurrent=2 # Limit for testing ) print("\n1. First run - downloading all assets...") await downloader.download_all_assets() if downloader.asset_tracker: stats1 = downloader.asset_tracker.get_stats() print(f"\nFirst run statistics:") print(f" Downloaded assets: {stats1['successful_downloads']}") print(f" Failed downloads: {stats1['failed_downloads']}") print(f" Total size: {stats1['total_size_mb']} MB") print("\n2. Second run - should find no new assets...") downloader.stats = {'total': 0, 'successful': 0, 'failed': 0, 'skipped': 0} await downloader.download_all_assets() if downloader.asset_tracker: stats2 = downloader.asset_tracker.get_stats() print(f"\nSecond run statistics:") print(f" New downloads: {downloader.stats['successful']}") print(f" Skipped (unchanged): {len(stats2.get('total_tracked_assets', 0))}") print("\n✅ Real API test completed!") except Exception as e: print(f"❌ Real API test failed: {e}") import traceback traceback.print_exc() def main(): """Main test function.""" # Setup logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) tester = AssetTrackingTester() # Run unit tests success = tester.run_all_tests() # Ask user if they want to run real API test if success and len(sys.argv) > 1 and sys.argv[1] == '--real-api': print("\n" + "🌐 Running real API test...") asyncio.run(test_with_real_api()) return 0 if success else 1 if __name__ == "__main__": exit(main())