#!/usr/bin/env python3
"""
Test Snapshot Downloader Functionality
This script tests the snapshot downloader to ensure it properly fetches
snapshots with pagination and generates HTML reports correctly.
"""
import asyncio
import json
import logging
import sys
import tempfile
from datetime import datetime, timedelta
from pathlib import Path
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 snapshot_downloader import SnapshotDownloader
from config_snapshot_downloader import ConfigSnapshotDownloader
class SnapshotDownloaderTester:
"""Test class for snapshot downloader functionality."""
def __init__(self):
"""Initialize the tester."""
self.logger = logging.getLogger(__name__)
# Test credentials
self.email = "tudor.sitaru@gmail.com"
self.password = "mTVq8uNUvY7R39EPGVAm@"
self.api_key = "95c74983-5d8f-4cf2-a216-3aa4416344ea"
def create_test_config(self, output_dir: str, **kwargs) -> dict:
"""Create a test configuration."""
config = {
"api_url": "https://api.parentzone.me",
"output_dir": output_dir,
"type_ids": [15],
"date_from": "2024-01-01",
"date_to": "2024-01-31", # Small range for testing
"max_pages": 2, # Limit for testing
"email": self.email,
"password": self.password,
}
config.update(kwargs)
return config
def test_initialization(self):
"""Test that SnapshotDownloader initializes correctly."""
print("=" * 60)
print("TEST 1: Initialization")
print("=" * 60)
with tempfile.TemporaryDirectory() as temp_dir:
print("1. Testing basic initialization...")
downloader = SnapshotDownloader(
output_dir=temp_dir, email=self.email, password=self.password
)
# Check initialization
if downloader.output_dir == Path(temp_dir):
print(" ✅ Output directory set correctly")
else:
print(" ❌ Output directory not set correctly")
return False
if downloader.email == self.email:
print(" ✅ Email set correctly")
else:
print(" ❌ Email not set correctly")
return False
if downloader.stats["total_snapshots"] == 0:
print(" ✅ Statistics initialized correctly")
else:
print(" ❌ Statistics not initialized correctly")
return False
print("\n2. Testing with API key...")
downloader_api = SnapshotDownloader(
output_dir=temp_dir, api_key=self.api_key
)
if downloader_api.api_key == self.api_key:
print(" ✅ API key set correctly")
else:
print(" ❌ API key not set correctly")
return False
print("\n✅ Initialization test passed!")
return True
def test_authentication_headers(self):
"""Test that authentication headers are set properly."""
print("\n" + "=" * 60)
print("TEST 2: Authentication Headers")
print("=" * 60)
with tempfile.TemporaryDirectory() as temp_dir:
print("1. Testing API key headers...")
downloader = SnapshotDownloader(output_dir=temp_dir, api_key=self.api_key)
headers = downloader.get_auth_headers()
if "x-api-key" in headers and headers["x-api-key"] == self.api_key:
print(" ✅ API key header set correctly")
else:
print(" ❌ API key header not set correctly")
return False
print("\n2. Testing standard headers...")
expected_headers = [
"accept",
"accept-language",
"origin",
"user-agent",
"sec-fetch-dest",
"sec-fetch-mode",
"sec-fetch-site",
]
for header in expected_headers:
if header in headers:
print(f" ✅ {header} header present")
else:
print(f" ❌ {header} header missing")
return False
print("\n✅ Authentication headers test passed!")
return True
async def test_authentication_flow(self):
"""Test the authentication flow."""
print("\n" + "=" * 60)
print("TEST 3: Authentication Flow")
print("=" * 60)
with tempfile.TemporaryDirectory() as temp_dir:
print("1. Testing login authentication...")
downloader = SnapshotDownloader(
output_dir=temp_dir, email=self.email, password=self.password
)
try:
await downloader.authenticate()
if (
downloader.auth_manager
and downloader.auth_manager.is_authenticated()
):
print(" ✅ Login authentication successful")
# Check if API key was obtained
headers = downloader.get_auth_headers()
if "x-api-key" in headers:
print(" ✅ API key obtained from authentication")
obtained_key = headers["x-api-key"]
if obtained_key:
print(f" ✅ API key: {obtained_key[:20]}...")
else:
print(" ❌ API key not obtained from authentication")
return False
else:
print(" ❌ Login authentication failed")
return False
except Exception as e:
print(f" ❌ Authentication error: {e}")
return False
print("\n✅ Authentication flow test passed!")
return True
async def test_url_building(self):
"""Test URL building for API requests."""
print("\n" + "=" * 60)
print("TEST 4: URL Building")
print("=" * 60)
with tempfile.TemporaryDirectory() as temp_dir:
downloader = SnapshotDownloader(output_dir=temp_dir)
print("1. Testing basic URL construction...")
# Mock session for URL building test
class MockSession:
def __init__(self):
self.last_url = None
self.last_headers = None
async def get(self, url, headers=None, timeout=None):
self.last_url = url
self.last_headers = headers
# Return mock async context manager
return MockAsyncContext()
async def __aenter__(self):
return self
async def __aexit__(self, *args):
pass
class MockAsyncContext:
async def __aenter__(self):
raise Exception("Mock response - URL captured")
async def __aexit__(self, *args):
pass
mock_session = MockSession()
try:
await downloader.fetch_snapshots_page(
mock_session,
type_ids=[15],
date_from="2024-01-01",
date_to="2024-01-31",
page=1,
per_page=100,
)
except Exception as e:
# Expected - we just want to capture the URL
if "Mock response" in str(e):
url = mock_session.last_url
print(f" Generated URL: {url}")
# Check URL components
if "https://api.parentzone.me/v1/posts" in url:
print(" ✅ Base URL correct")
else:
print(" ❌ Base URL incorrect")
return False
if "typeIDs%5B%5D=15" in url or "typeIDs[]=15" in url:
print(" ✅ Type ID parameter correct")
else:
print(" ❌ Type ID parameter incorrect")
return False
if "dateFrom=2024-01-01" in url:
print(" ✅ Date from parameter correct")
else:
print(" ❌ Date from parameter incorrect")
return False
if "dateTo=2024-01-31" in url:
print(" ✅ Date to parameter correct")
else:
print(" ❌ Date to parameter incorrect")
return False
if "page=1" in url:
print(" ✅ Page parameter correct")
else:
print(" ❌ Page parameter incorrect")
return False
else:
print(f" ❌ Unexpected error: {e}")
return False
print("\n✅ URL building test passed!")
return True
def test_html_formatting(self):
"""Test HTML formatting functions."""
print("\n" + "=" * 60)
print("TEST 5: HTML Formatting")
print("=" * 60)
with tempfile.TemporaryDirectory() as temp_dir:
downloader = SnapshotDownloader(output_dir=temp_dir)
print("1. Testing snapshot HTML formatting...")
# Create mock snapshot data
mock_snapshot = {
"id": "test_snapshot_123",
"title": "Test Snapshot ",
"content": "This is a test snapshot with some content & special characters",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z",
"author": {"name": "Test Author"},
"child": {"name": "Test Child"},
"activity": {"name": "Test Activity"},
"images": [
{"url": "https://example.com/image1.jpg", "name": "Test Image"}
],
}
html = downloader.format_snapshot_html(mock_snapshot)
# Check basic structure
if '
" in content:
print(" ✅ Valid HTML document")
else:
print(" ❌ Invalid HTML document")
return False
if "ParentZone Snapshots" in content:
print(" ✅ Title included")
else:
print(" ❌ Title missing")
return False
if "Test Snapshot" in content:
print(" ✅ Snapshot content included")
else:
print(" ❌ Snapshot content missing")
return False
else:
print(" ❌ HTML file not created")
return False
print("\n✅ HTML formatting test passed!")
return True
def test_config_downloader(self):
"""Test the configuration-based downloader."""
print("\n" + "=" * 60)
print("TEST 6: Config Downloader")
print("=" * 60)
with tempfile.TemporaryDirectory() as temp_dir:
print("1. Testing configuration loading...")
# Create test config file
config_data = self.create_test_config(temp_dir)
config_file = Path(temp_dir) / "test_config.json"
with open(config_file, "w") as f:
json.dump(config_data, f, indent=2)
# Test config loading
try:
config_downloader = ConfigSnapshotDownloader(str(config_file))
print(" ✅ Configuration loaded successfully")
# Check if underlying downloader was created
if hasattr(config_downloader, "downloader"):
print(" ✅ Underlying downloader created")
else:
print(" ❌ Underlying downloader not created")
return False
except Exception as e:
print(f" ❌ Configuration loading failed: {e}")
return False
print("\n2. Testing invalid configuration...")
# Test invalid config (missing auth)
invalid_config = config_data.copy()
del invalid_config["email"]
del invalid_config["password"]
# Don't set api_key either
invalid_config_file = Path(temp_dir) / "invalid_config.json"
with open(invalid_config_file, "w") as f:
json.dump(invalid_config, f, indent=2)
try:
ConfigSnapshotDownloader(str(invalid_config_file))
print(" ❌ Should have failed with invalid config")
return False
except ValueError:
print(" ✅ Correctly rejected invalid configuration")
except Exception as e:
print(f" ❌ Unexpected error: {e}")
return False
print("\n✅ Config downloader test passed!")
return True
def test_date_formatting(self):
"""Test date formatting functionality."""
print("\n" + "=" * 60)
print("TEST 7: Date Formatting")
print("=" * 60)
with tempfile.TemporaryDirectory() as temp_dir:
downloader = SnapshotDownloader(output_dir=temp_dir)
print("1. Testing various date formats...")
test_dates = [
("2024-01-15T10:30:00Z", "2024-01-15 10:30:00"),
("2024-01-15T10:30:00.123Z", "2024-01-15 10:30:00"),
("2024-01-15T10:30:00+00:00", "2024-01-15 10:30:00"),
("invalid-date", "invalid-date"), # Should pass through unchanged
("", ""), # Should handle empty string
]
for input_date, expected_prefix in test_dates:
formatted = downloader.format_date(input_date)
print(f" Input: '{input_date}' → Output: '{formatted}'")
if expected_prefix in formatted or input_date == formatted:
print(f" ✅ Date formatted correctly")
else:
print(f" ❌ Date formatting failed")
return False
print("\n✅ Date formatting test passed!")
return True
async def test_pagination_logic(self):
"""Test pagination handling logic."""
print("\n" + "=" * 60)
print("TEST 8: Pagination Logic")
print("=" * 60)
print("1. Testing pagination parameters...")
with tempfile.TemporaryDirectory() as temp_dir:
downloader = SnapshotDownloader(output_dir=temp_dir)
# Mock session to test pagination
class PaginationMockSession:
def __init__(self):
self.call_count = 0
self.pages = [
# Page 1
{
"data": [{"id": "snap1"}, {"id": "snap2"}],
"pagination": {"current_page": 1, "last_page": 3},
},
# Page 2
{
"data": [{"id": "snap3"}, {"id": "snap4"}],
"pagination": {"current_page": 2, "last_page": 3},
},
# Page 3
{
"data": [{"id": "snap5"}],
"pagination": {"current_page": 3, "last_page": 3},
},
]
async def get(self, url, headers=None, timeout=None):
return MockResponse(self.pages[self.call_count])
async def __aenter__(self):
return self
async def __aexit__(self, *args):
pass
class MockResponse:
def __init__(self, data):
self.data = data
self.status = 200
def raise_for_status(self):
pass
async def json(self):
return self.data
mock_session = PaginationMockSession()
# Override the fetch_snapshots_page method to use our mock
original_method = downloader.fetch_snapshots_page
async def mock_fetch_page(
session, type_ids, date_from, date_to, page, per_page
):
response_data = mock_session.pages[page - 1]
mock_session.call_count += 1
downloader.stats["pages_fetched"] += 1
return response_data
downloader.fetch_snapshots_page = mock_fetch_page
try:
# Test fetching all pages
snapshots = await downloader.fetch_all_snapshots(
mock_session, [15], "2024-01-01", "2024-01-31"
)
if len(snapshots) == 5: # Total snapshots across all pages
print(" ✅ All pages fetched correctly")
else:
print(f" ❌ Expected 5 snapshots, got {len(snapshots)}")
return False
if downloader.stats["pages_fetched"] == 3:
print(" ✅ Page count tracked correctly")
else:
print(
f" ❌ Expected 3 pages, tracked {downloader.stats['pages_fetched']}"
)
return False
# Test max_pages limit
downloader.stats["pages_fetched"] = 0 # Reset
mock_session.call_count = 0 # Reset
snapshots_limited = await downloader.fetch_all_snapshots(
mock_session, [15], "2024-01-01", "2024-01-31", max_pages=2
)
if len(snapshots_limited) == 4: # First 2 pages only
print(" ✅ Max pages limit respected")
else:
print(
f" ❌ Expected 4 snapshots with limit, got {len(snapshots_limited)}"
)
return False
except Exception as e:
print(f" ❌ Pagination test error: {e}")
return False
print("\n✅ Pagination logic test passed!")
return True
async def run_all_tests(self):
"""Run all tests."""
print("🚀 Starting Snapshot Downloader Tests")
print("=" * 80)
try:
success = True
success &= self.test_initialization()
success &= self.test_authentication_headers()
success &= await self.test_authentication_flow()
success &= await self.test_url_building()
success &= self.test_html_formatting()
success &= self.test_config_downloader()
success &= self.test_date_formatting()
success &= await self.test_pagination_logic()
if success:
print("\n" + "=" * 80)
print("🎉 ALL SNAPSHOT DOWNLOADER TESTS PASSED!")
print("=" * 80)
print("✅ Snapshot downloader is working correctly")
print("✅ Pagination handling is implemented properly")
print("✅ HTML generation creates proper markup files")
print("✅ Authentication works with both API key and login")
print("✅ Configuration-based downloader is functional")
else:
print("\n❌ SOME TESTS FAILED")
return success
except Exception as e:
print(f"\n❌ TEST SUITE FAILED: {e}")
import traceback
traceback.print_exc()
return False
def show_usage_examples():
"""Show usage examples for the snapshot downloader."""
print("\n" + "=" * 80)
print("📋 SNAPSHOT DOWNLOADER USAGE EXAMPLES")
print("=" * 80)
print("\n💻 Command Line Usage:")
print("# Download snapshots with API key")
print("python3 snapshot_downloader.py --api-key YOUR_API_KEY")
print()
print("# Download with login credentials")
print("python3 snapshot_downloader.py --email user@example.com --password password")
print()
print("# Specify date range")
print(
"python3 snapshot_downloader.py --api-key KEY --date-from 2024-01-01 --date-to 2024-12-31"
)
print()
print("# Limit pages for testing")
print("python3 snapshot_downloader.py --api-key KEY --max-pages 5")
print("\n🔧 Configuration File Usage:")
print("# Create example config")
print("python3 config_snapshot_downloader.py --create-example")
print()
print("# Use config file")
print("python3 config_snapshot_downloader.py --config snapshot_config.json")
print()
print("# Show config summary")
print(
"python3 config_snapshot_downloader.py --config snapshot_config.json --show-config"
)
print("\n📄 Features:")
print("• Downloads all snapshots with pagination support")
print("• Generates interactive HTML reports")
print("• Includes search and filtering capabilities")
print("• Supports both API key and login authentication")
print("• Configurable date ranges and type filters")
print("• Mobile-responsive design")
print("• Collapsible sections for detailed metadata")
print("\n🎯 Output:")
print("• HTML file with all snapshots in chronological order")
print("• Embedded images and attachments (if available)")
print("• Raw JSON data for each snapshot (expandable)")
print("• Search functionality to find specific snapshots")
print("• Statistics and summary information")
def main():
"""Main test function."""
# Setup logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
tester = SnapshotDownloaderTester()
# Run tests
success = asyncio.run(tester.run_all_tests())
# Show usage examples
if success:
show_usage_examples()
return 0 if success else 1
if __name__ == "__main__":
exit(main())