Files
ohm_streaming/test_watchlist_simple.py
root c6be191699 feat: Complete watchlist & auto-download system with UI
## 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>
2026-01-29 21:56:39 +00:00

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)