Documentation Added: - ARCHITECTURE_DECISIONS.md: Explains why systemd over k8s (TikTok display requirements) - DEPLOYMENT_CHECKLIST.md: Step-by-step deployment procedures - ROLLBACK_PROCEDURES.md: Emergency rollback and recovery procedures - test_production_deployment.py: Automated deployment verification script Key Documentation Highlights: - Detailed explanation of containerization limitations with browser automation - Complete deployment checklist with pre/post verification steps - Rollback scenarios with recovery time objectives - Emergency contact templates and backup procedures - Automated test script for production readiness 17 of 25 tasks completed (68% done) Remaining work focuses on spec compliance and testing 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
278 lines
No EOL
7.9 KiB
Python
Executable file
278 lines
No EOL
7.9 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""
|
|
Production Deployment Test Script
|
|
Tests all components before going live
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import time
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
|
|
# Add project to path
|
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
|
|
def test_environment():
|
|
"""Test environment variables are set"""
|
|
print("\n=== Testing Environment Variables ===")
|
|
|
|
required_vars = [
|
|
'WORDPRESS_USERNAME',
|
|
'WORDPRESS_API_KEY',
|
|
'YOUTUBE_CHANNEL_URL',
|
|
'INSTAGRAM_USERNAME',
|
|
'INSTAGRAM_PASSWORD',
|
|
'TIKTOK_TARGET',
|
|
'NAS_PATH'
|
|
]
|
|
|
|
missing = []
|
|
for var in required_vars:
|
|
value = os.getenv(var)
|
|
if value:
|
|
# Don't print sensitive values
|
|
if 'PASSWORD' in var or 'KEY' in var:
|
|
print(f"✓ {var}: ***SET***")
|
|
else:
|
|
print(f"✓ {var}: {value[:20]}..." if len(value) > 20 else f"✓ {var}: {value}")
|
|
else:
|
|
print(f"✗ {var}: MISSING")
|
|
missing.append(var)
|
|
|
|
if missing:
|
|
print(f"\n❌ Missing variables: {', '.join(missing)}")
|
|
return False
|
|
|
|
print("\n✅ All environment variables set")
|
|
return True
|
|
|
|
def test_directories():
|
|
"""Test required directories exist and are writable"""
|
|
print("\n=== Testing Directory Structure ===")
|
|
|
|
dirs_to_test = [
|
|
Path("/opt/hvac-kia-content"),
|
|
Path("/var/log/hvac-content"),
|
|
Path(os.getenv('NAS_PATH', '/mnt/nas/hvacknowitall'))
|
|
]
|
|
|
|
all_good = True
|
|
for dir_path in dirs_to_test:
|
|
if dir_path.exists():
|
|
# Test write permissions
|
|
test_file = dir_path / f"test_{datetime.now():%Y%m%d_%H%M%S}.txt"
|
|
try:
|
|
test_file.write_text("test")
|
|
test_file.unlink()
|
|
print(f"✓ {dir_path}: Exists and writable")
|
|
except PermissionError:
|
|
print(f"✗ {dir_path}: Exists but not writable")
|
|
all_good = False
|
|
else:
|
|
print(f"✗ {dir_path}: Does not exist")
|
|
all_good = False
|
|
|
|
if all_good:
|
|
print("\n✅ All directories accessible")
|
|
else:
|
|
print("\n❌ Directory issues found")
|
|
|
|
return all_good
|
|
|
|
def test_config_validation():
|
|
"""Test configuration validation"""
|
|
print("\n=== Testing Configuration Validation ===")
|
|
|
|
try:
|
|
from run_production import validate_config
|
|
validate_config()
|
|
print("✅ Configuration validation passed")
|
|
return True
|
|
except Exception as e:
|
|
print(f"❌ Configuration validation failed: {e}")
|
|
return False
|
|
|
|
def test_scrapers():
|
|
"""Test each scraper can initialize"""
|
|
print("\n=== Testing Scraper Initialization ===")
|
|
|
|
from src.base_scraper import ScraperConfig
|
|
from pathlib import Path
|
|
|
|
test_config = ScraperConfig(
|
|
source_name="test",
|
|
brand_name="hvacknowitall",
|
|
data_dir=Path("/tmp/test_data"),
|
|
logs_dir=Path("/tmp/test_logs"),
|
|
timezone="America/Halifax"
|
|
)
|
|
|
|
scrapers_to_test = [
|
|
("WordPress", "src.wordpress_scraper", "WordPressScraper"),
|
|
("YouTube", "src.youtube_scraper", "YouTubeScraper"),
|
|
("Instagram", "src.instagram_scraper", "InstagramScraper"),
|
|
("TikTok", "src.tiktok_scraper_advanced", "TikTokScraperAdvanced"),
|
|
("MailChimp", "src.rss_scraper", "RSSScraperMailChimp"),
|
|
("Podcast", "src.rss_scraper", "RSSScraperPodcast")
|
|
]
|
|
|
|
all_good = True
|
|
for name, module_path, class_name in scrapers_to_test:
|
|
try:
|
|
module = __import__(module_path, fromlist=[class_name])
|
|
scraper_class = getattr(module, class_name)
|
|
scraper = scraper_class(test_config)
|
|
print(f"✓ {name}: Initialized successfully")
|
|
except Exception as e:
|
|
print(f"✗ {name}: Failed to initialize - {e}")
|
|
all_good = False
|
|
|
|
if all_good:
|
|
print("\n✅ All scrapers initialized")
|
|
else:
|
|
print("\n⚠️ Some scrapers failed to initialize")
|
|
|
|
return all_good
|
|
|
|
def test_systemd_files():
|
|
"""Test systemd service files exist"""
|
|
print("\n=== Testing Systemd Files ===")
|
|
|
|
systemd_files = [
|
|
Path("systemd/hvac-content-aggregator.service"),
|
|
Path("systemd/hvac-content-aggregator.timer"),
|
|
Path("systemd/hvac-content-aggregator@.service"),
|
|
Path("systemd/hvac-tiktok-captions.service"),
|
|
Path("systemd/hvac-tiktok-captions.timer")
|
|
]
|
|
|
|
all_good = True
|
|
for file_path in systemd_files:
|
|
if file_path.exists():
|
|
print(f"✓ {file_path}: Exists")
|
|
else:
|
|
print(f"✗ {file_path}: Missing")
|
|
all_good = False
|
|
|
|
if all_good:
|
|
print("\n✅ All systemd files present")
|
|
else:
|
|
print("\n❌ Some systemd files missing")
|
|
|
|
return all_good
|
|
|
|
def test_python_dependencies():
|
|
"""Test all required Python packages are installed"""
|
|
print("\n=== Testing Python Dependencies ===")
|
|
|
|
required_packages = [
|
|
"requests",
|
|
"pytz",
|
|
"python-dotenv",
|
|
"feedparser",
|
|
"markitdown",
|
|
"scrapling",
|
|
"instaloader",
|
|
"yt-dlp",
|
|
"tenacity"
|
|
]
|
|
|
|
all_good = True
|
|
for package in required_packages:
|
|
try:
|
|
__import__(package.replace("-", "_"))
|
|
print(f"✓ {package}: Installed")
|
|
except ImportError:
|
|
print(f"✗ {package}: Not installed")
|
|
all_good = False
|
|
|
|
if all_good:
|
|
print("\n✅ All dependencies installed")
|
|
else:
|
|
print("\n❌ Some dependencies missing")
|
|
print("Run: pip install -r requirements.txt")
|
|
|
|
return all_good
|
|
|
|
def test_dry_run():
|
|
"""Test a dry run of the production script"""
|
|
print("\n=== Testing Dry Run ===")
|
|
|
|
try:
|
|
# Import and test validation only
|
|
from run_production import validate_environment, validate_config
|
|
|
|
validate_environment()
|
|
print("✓ Environment validation passed")
|
|
|
|
validate_config()
|
|
print("✓ Configuration validation passed")
|
|
|
|
print("\n✅ Dry run successful")
|
|
return True
|
|
|
|
except Exception as e:
|
|
print(f"\n❌ Dry run failed: {e}")
|
|
return False
|
|
|
|
def main():
|
|
"""Run all tests"""
|
|
print("=" * 50)
|
|
print("HVAC Know It All - Production Deployment Test")
|
|
print("=" * 50)
|
|
|
|
# Load environment
|
|
from dotenv import load_dotenv
|
|
load_dotenv()
|
|
|
|
tests = [
|
|
("Environment Variables", test_environment),
|
|
("Python Dependencies", test_python_dependencies),
|
|
("Configuration Validation", test_config_validation),
|
|
("Scraper Initialization", test_scrapers),
|
|
("Systemd Files", test_systemd_files),
|
|
("Dry Run", test_dry_run)
|
|
]
|
|
|
|
# Don't test directories in development
|
|
if os.path.exists("/opt/hvac-kia-content"):
|
|
tests.insert(2, ("Directory Structure", test_directories))
|
|
|
|
results = []
|
|
for test_name, test_func in tests:
|
|
try:
|
|
result = test_func()
|
|
results.append((test_name, result))
|
|
except Exception as e:
|
|
print(f"\n❌ {test_name} crashed: {e}")
|
|
results.append((test_name, False))
|
|
|
|
# Summary
|
|
print("\n" + "=" * 50)
|
|
print("TEST SUMMARY")
|
|
print("=" * 50)
|
|
|
|
passed = 0
|
|
failed = 0
|
|
|
|
for test_name, result in results:
|
|
status = "✅ PASS" if result else "❌ FAIL"
|
|
print(f"{status}: {test_name}")
|
|
if result:
|
|
passed += 1
|
|
else:
|
|
failed += 1
|
|
|
|
print(f"\nTotal: {passed} passed, {failed} failed")
|
|
|
|
if failed == 0:
|
|
print("\n🎉 READY FOR PRODUCTION DEPLOYMENT 🎉")
|
|
return 0
|
|
else:
|
|
print(f"\n⚠️ Fix {failed} issue(s) before deployment")
|
|
return 1
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main()) |