c6be191699
## Backend Implementation (100% Complete)
### Core Components
- **WatchlistManager**: JSON-based storage with full CRUD operations
- User-scoped data access for multi-tenant support
- Statistics and query functions
- Settings management with persistence
- **EpisodeChecker**: Automatic new episode detection
- Checks for new episodes using existing downloaders
- Automatic download with error handling
- Manual and scheduled check support
- Lazy initialization to avoid circular imports
- **AutoDownloadScheduler**: APScheduler-based periodic checking
- Configurable intervals (1-168 hours)
- Start/stop/restart controls
- Next run time tracking
### API Endpoints (15 endpoints)
- POST /api/watchlist - Add anime to watchlist
- GET /api/watchlist - Get user's watchlist (with status filter)
- GET /api/watchlist/{id} - Get specific item
- PUT /api/watchlist/{id} - Update item
- DELETE /api/watchlist/{id} - Delete item
- POST /api/watchlist/{id}/check - Check for new episodes
- POST /api/watchlist/{id}/pause - Pause tracking
- POST /api/watchlist/{id}/resume - Resume tracking
- GET /api/watchlist/settings - Get settings
- PUT /api/watchlist/settings - Update settings
- GET /api/watchlist/stats - Get statistics
- POST /api/watchlist/check-all - Check all items
- GET /api/watchlist/scheduler/status - Scheduler status
- POST /api/watchlist/scheduler/start - Start scheduler
- POST /api/watchlist/scheduler/stop - Stop scheduler
### Bug Fixes
- Fixed WatchlistManager.update() to accept both dict and WatchlistItemUpdate
- Added asyncio import to AutoDownloadScheduler for event loop detection
- Improved scheduler start() with better error handling
## Frontend Implementation (100% Complete)
### UI Components
- **Watchlist Page** (/watchlist)
- Scheduler status panel with start/stop/check all buttons
- Filter tabs (all/active/paused/completed)
- Statistics display with color-coded cards
- Watchlist items with pause/resume/delete controls
- Auto-refresh every 30 seconds
- Authentication check
- **Settings Modal**
- Check interval configuration (1-168h)
- Auto-download toggle
- Max concurrent downloads slider
- Notifications toggle
- Live settings update with scheduler restart
- **"Suivre" Button**
- Added to anime search result cards
- Purple gradient with heart icon
- Quick-add to watchlist functionality
- State tracking (disabled when already in watchlist)
### JavaScript Files
- **static/js/watchlist.js**: API client functions
- All watchlist API calls with token auth
- Error handling and response parsing
- **static/js/watchlist-ui.js**: UI functions
- Display watchlist with stats
- Handle add/pause/resume/delete
- Filter by status
- Settings modal management
- **static/js/tabs.js**: Watchlist tab handler
- Redirects to /watchlist page
## Testing
### Test Suite (test_watchlist_simple.py)
All tests passing (3/3):
1. **Watchlist Manager Tests** ✅
- Create/read/update/delete operations
- User-scoped queries
- Statistics generation
- Check time updates
2. **Settings Tests** ✅
- Get current settings
- Update settings with validation
- Reset to defaults
3. **Scheduler Tests** ✅
- Start/stop/restart controls
- Running status verification
- Next run time tracking
### Dependencies
- APScheduler 3.11.0 installed in virtual environment
- tzlocal 5.3.1 (APScheduler dependency)
## Documentation
- docs/WATCHLIST_AUTO_DOWNLOAD.md: Complete system documentation
- API endpoints with examples
- Architecture overview
- Usage examples
- Troubleshooting guide
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
288 lines
8.8 KiB
Python
288 lines
8.8 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Simple test script for the watchlist & auto-download system
|
|
"""
|
|
|
|
import asyncio
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Add project to path
|
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
|
|
from app.watchlist import watchlist_manager
|
|
from app.episode_checker import episode_checker
|
|
from app.auto_download_scheduler import auto_download_scheduler
|
|
from app.models.watchlist import (
|
|
WatchlistItemCreate,
|
|
WatchlistStatus,
|
|
QualityPreference,
|
|
WatchlistSettings
|
|
)
|
|
|
|
|
|
def test_watchlist_basics():
|
|
"""Test basic watchlist operations"""
|
|
print("\n" + "="*60)
|
|
print("🧪 TEST 1: Watchlist Manager Basics")
|
|
print("="*60)
|
|
|
|
# Test user ID
|
|
test_user = "test_user_simple"
|
|
|
|
# Clean up any existing test items first
|
|
print("\n0. Cleaning up any existing test items...")
|
|
all_items = watchlist_manager.get_all()
|
|
for item in all_items:
|
|
if item.user_id == test_user:
|
|
watchlist_manager.delete(item.id)
|
|
print(f" ✓ Deleted old test item: {item.id}")
|
|
|
|
# Create a test item
|
|
print("\n1. Creating watchlist item...")
|
|
item_data = WatchlistItemCreate(
|
|
anime_title="Test Anime Simple",
|
|
anime_url="https://anime-sama.si/catalogue/test-simple/vostfr/",
|
|
provider_id="animesama",
|
|
lang="vostfr",
|
|
auto_download=True,
|
|
quality_preference=QualityPreference.AUTO
|
|
)
|
|
|
|
try:
|
|
item = watchlist_manager.create(test_user, item_data)
|
|
print(f" ✅ Item created: {item.id}")
|
|
print(f" Title: {item.anime_title}")
|
|
print(f" Status: {item.status}")
|
|
print(f" Auto-download: {item.auto_download}")
|
|
except Exception as e:
|
|
print(f" ❌ Create failed: {e}")
|
|
return False
|
|
|
|
# Get all items for user
|
|
print("\n2. Getting user's items...")
|
|
try:
|
|
items = watchlist_manager.get_all(test_user)
|
|
print(f" ✅ Found {len(items)} items for user")
|
|
if len(items) > 0:
|
|
print(f" First item: {items[0].anime_title}")
|
|
except Exception as e:
|
|
print(f" ❌ Get all failed: {e}")
|
|
return False
|
|
|
|
# Get stats
|
|
print("\n3. Getting statistics...")
|
|
try:
|
|
stats = watchlist_manager.get_stats(test_user)
|
|
print(f" ✅ Stats: total={stats['total']}, active={stats['active']}, paused={stats['paused']}")
|
|
except Exception as e:
|
|
print(f" ❌ Stats failed: {e}")
|
|
return False
|
|
|
|
# Update item
|
|
print("\n4. Updating item to paused...")
|
|
try:
|
|
updated = watchlist_manager.update(item.id, {"status": WatchlistStatus.PAUSED})
|
|
print(f" ✅ Item updated to status: {updated.status}")
|
|
except Exception as e:
|
|
print(f" ❌ Update failed: {e}")
|
|
return False
|
|
|
|
# Update check time
|
|
print("\n5. Updating check time...")
|
|
try:
|
|
updated = watchlist_manager.update_check_time(item.id, 5)
|
|
print(f" ✅ Check time updated")
|
|
print(f" Last episode: {updated.last_episode_downloaded}")
|
|
except Exception as e:
|
|
print(f" ❌ Update check time failed: {e}")
|
|
return False
|
|
|
|
# Delete item (cleanup)
|
|
print("\n6. Cleaning up - deleting test item...")
|
|
try:
|
|
result = watchlist_manager.delete(item.id)
|
|
print(f" ✅ Item deleted: {result}")
|
|
except Exception as e:
|
|
print(f" ❌ Delete failed: {e}")
|
|
return False
|
|
|
|
print("\n✅ Watchlist Manager tests PASSED")
|
|
return True
|
|
|
|
|
|
def test_settings():
|
|
"""Test settings management"""
|
|
print("\n" + "="*60)
|
|
print("🧪 TEST 2: Settings Management")
|
|
print("="*60)
|
|
|
|
print("\n1. Getting current settings...")
|
|
try:
|
|
settings = watchlist_manager.get_settings()
|
|
print(f" ✅ Current settings:")
|
|
print(f" - Check interval: {settings.check_interval_hours}h")
|
|
print(f" - Auto-download: {settings.auto_download_enabled}")
|
|
print(f" - Max concurrent: {settings.max_concurrent_auto_downloads}")
|
|
print(f" - Notifications: {settings.notify_on_new_episodes}")
|
|
except Exception as e:
|
|
print(f" ❌ Get settings failed: {e}")
|
|
return False
|
|
|
|
print("\n2. Updating settings...")
|
|
try:
|
|
new_settings = WatchlistSettings(
|
|
check_interval_hours=12,
|
|
auto_download_enabled=True,
|
|
max_concurrent_auto_downloads=3,
|
|
notify_on_new_episodes=False
|
|
)
|
|
watchlist_manager.update_settings(new_settings)
|
|
print(f" ✅ Settings updated")
|
|
except Exception as e:
|
|
print(f" ❌ Update settings failed: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
return False
|
|
|
|
print("\n3. Verifying settings...")
|
|
try:
|
|
settings = watchlist_manager.get_settings()
|
|
if settings.check_interval_hours != 12:
|
|
print(f" ❌ Settings not saved correctly: check_interval is {settings.check_interval_hours}, expected 12")
|
|
return False
|
|
|
|
print(f" ✅ Settings verified:")
|
|
print(f" - Check interval: {settings.check_interval_hours}h ✓")
|
|
except Exception as e:
|
|
print(f" ❌ Verify settings failed: {e}")
|
|
return False
|
|
|
|
# Reset to defaults
|
|
print("\n4. Resetting to default settings...")
|
|
try:
|
|
default_settings = WatchlistSettings()
|
|
watchlist_manager.update_settings(default_settings)
|
|
print(f" ✅ Settings reset to defaults")
|
|
except Exception as e:
|
|
print(f" ❌ Reset settings failed: {e}")
|
|
return False
|
|
|
|
print("\n✅ Settings tests PASSED")
|
|
return True
|
|
|
|
|
|
async def test_scheduler():
|
|
"""Test scheduler controls"""
|
|
print("\n" + "="*60)
|
|
print("🧪 TEST 3: Auto-Download Scheduler")
|
|
print("="*60)
|
|
|
|
print("\n1. Testing scheduler start (async)...")
|
|
try:
|
|
auto_download_scheduler.start()
|
|
print(f" ✅ Scheduler started")
|
|
print(f" Status: running={auto_download_scheduler.is_running()}")
|
|
except Exception as e:
|
|
print(f" ❌ Start failed: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
return False
|
|
|
|
if not auto_download_scheduler.is_running():
|
|
print(f" ❌ Scheduler not running after start")
|
|
return False
|
|
|
|
print("\n2. Testing scheduler stop...")
|
|
try:
|
|
auto_download_scheduler.stop()
|
|
print(f" ✅ Scheduler stopped")
|
|
print(f" Status: running={auto_download_scheduler.is_running()}")
|
|
except Exception as e:
|
|
print(f" ❌ Stop failed: {e}")
|
|
return False
|
|
|
|
if auto_download_scheduler.is_running():
|
|
print(f" ❌ Scheduler still running after stop")
|
|
return False
|
|
|
|
print("\n3. Testing scheduler restart...")
|
|
try:
|
|
auto_download_scheduler.start()
|
|
print(f" ✅ Scheduler restarted")
|
|
|
|
# Get next run time
|
|
next_run = auto_download_scheduler.get_next_run_time()
|
|
if next_run:
|
|
print(f" Next run: {next_run}")
|
|
|
|
auto_download_scheduler.stop()
|
|
print(f" ✅ Scheduler stopped again")
|
|
except Exception as e:
|
|
print(f" ❌ Restart failed: {e}")
|
|
return False
|
|
|
|
print("\n✅ Scheduler tests PASSED")
|
|
return True
|
|
|
|
|
|
async def run_all_tests():
|
|
"""Run all tests"""
|
|
print("\n" + "="*60)
|
|
print("🚀 WATCHLIST SYSTEM TEST SUITE")
|
|
print("="*60)
|
|
|
|
tests = [
|
|
("Watchlist Manager", lambda: test_watchlist_basics()),
|
|
("Settings", lambda: test_settings()),
|
|
("Scheduler", test_scheduler) # This one is async
|
|
]
|
|
|
|
results = []
|
|
|
|
for name, test_func in tests:
|
|
try:
|
|
# Check if it's a coroutine function
|
|
import asyncio
|
|
if asyncio.iscoroutinefunction(test_func):
|
|
result = await test_func()
|
|
else:
|
|
result = test_func()
|
|
results.append((name, result))
|
|
except Exception as e:
|
|
print(f"\n❌ {name} test crashed: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
results.append((name, False))
|
|
|
|
# Summary
|
|
print("\n" + "="*60)
|
|
print("📊 TEST SUMMARY")
|
|
print("="*60)
|
|
|
|
passed = sum(1 for _, result in results if result)
|
|
total = len(results)
|
|
|
|
for name, result in results:
|
|
status = "✅ PASSED" if result else "❌ FAILED"
|
|
print(f"{status}: {name}")
|
|
|
|
print(f"\nTotal: {passed}/{total} tests passed")
|
|
|
|
if passed == total:
|
|
print("\n🎉 ALL TESTS PASSED! The watchlist system is ready to use.")
|
|
print("\n📝 Next steps:")
|
|
print(" 1. Start the server: uvicorn main:app --reload")
|
|
print(" 2. Open http://localhost:3000/watchlist")
|
|
print(" 3. Add anime to your watchlist")
|
|
print(" 4. Start the scheduler")
|
|
return True
|
|
else:
|
|
print("\n⚠️ Some tests failed. Please review the errors above.")
|
|
return False
|
|
|
|
|
|
if __name__ == "__main__":
|
|
success = asyncio.run(run_all_tests())
|
|
sys.exit(0 if success else 1)
|