From dd215d2f38fa535fcbd9a9fd65999b2881fd47b9 Mon Sep 17 00:00:00 2001 From: SBCrumb Date: Tue, 9 Sep 2025 08:50:21 -0400 Subject: [PATCH] db updates --- .env.example | 74 ------------------ .env.template | 85 +++++++++++++++++++++ DEPLOYMENT.md | 181 ++++++++++++++++++++++++++++++++++++++++++++ TESTING.md | 131 ++++++++++++++++++++++++++++++++ docker-compose.yml | 77 +++++++++++++++++++ nfoguard.py | 69 +++++++++++++++++ test_bulk_update.py | 99 ++++++++++++++++++++++++ test_end_to_end.py | 150 ++++++++++++++++++++++++++++++++++++ test_movie_scan.py | 116 ++++++++++++++++++++++++++++ 9 files changed, 908 insertions(+), 74 deletions(-) delete mode 100644 .env.example create mode 100644 .env.template create mode 100644 DEPLOYMENT.md create mode 100644 TESTING.md create mode 100644 docker-compose.yml create mode 100644 test_bulk_update.py create mode 100644 test_end_to_end.py create mode 100644 test_movie_scan.py diff --git a/.env.example b/.env.example deleted file mode 100644 index 58db09e..0000000 --- a/.env.example +++ /dev/null @@ -1,74 +0,0 @@ -# ================================= -# NFOGuard - Simple Configuration -# ================================= - -# --- Required: Media Library Paths --- -# Container paths (what NFOGuard sees) -TV_PATHS=/media/tv,/media/tv6 -MOVIE_PATHS=/media/movies,/media/movies6 - -# --- Required: Sonarr/Radarr Connection --- -SONARR_URL=http://sonarr:8989 -SONARR_API_KEY= - -RADARR_URL=http://radarr:7878 -RADARR_API_KEY= - -# --- Optional: Direct Radarr Database Access (High Performance) --- -# Enable direct database access for faster import date queries -# Supports both SQLite and PostgreSQL -RADARR_DB_TYPE=postgresql -# For PostgreSQL (recommended for production): -RADARR_DB_HOST=postgres_host -RADARR_DB_PORT=5432 -RADARR_DB_NAME=radarr-main -RADARR_DB_USER=postgres -RADARR_DB_PASSWORD=your_password -# Alternative: use connection string -#RADARR_DB_URL=postgresql://user:pass@host:5432/dbname - -# For SQLite (if using default Radarr setup): -#RADARR_DB_TYPE=sqlite -#RADARR_DB_PATH=/config/radarr.db - -# --- Required: TMDB for Release Dates --- -# Get free API key from https://www.themoviedb.org/settings/api -TMDB_API_KEY= -TMDB_COUNTRY=US - -# --- Behavior Settings --- -# Movie processing strategy: import_then_digital, digital_then_import -MOVIE_PRIORITY=import_then_digital - -# When to query APIs: always, if_missing, never -MOVIE_POLL_MODE=always - -# Update mode: backfill_only, overwrite -MOVIE_DATE_UPDATE_MODE=backfill_only - -# Lock metadata in NFO files to prevent overwrites: true, false -LOCK_METADATA=true - -# Brand name in NFO comments -MANAGER_BRAND=NFOGuard - -# --- CRITICAL: Path Mapping --- -# What Radarr/Sonarr see vs what container sees -RADARR_ROOT_FOLDERS=/mnt/unionfs/Media/Movies/movies,/mnt/unionfs/Media/Movies/movies6 -SONARR_ROOT_FOLDERS=/mnt/unionfs/Media/TV/tv,/mnt/unionfs/Media/TV/tv6 - -# Download path indicators (for nzbget, etc.) -DOWNLOAD_PATH_INDICATORS=/downloads/,/nzbget/,/sabnzbd/,/completed/,/torrents/ - -# --- Optional: Additional Digital Release Sources --- -#OMDB_API_KEY=your_omdb_api_key_here -JELLYSEERR_URL=http://jellyseerr:5055 -JELLYSEERR_API_KEY= - -# --- Optional: Advanced Settings --- -BATCH_DELAY=5.0 -MAX_CONCURRENT_SERIES=3 -TIMEOUT_SECONDS=45 -DEBUG=false -DB_PATH=/app/data/media_dates.db -PORT=8080 diff --git a/.env.template b/.env.template new file mode 100644 index 0000000..9ad3217 --- /dev/null +++ b/.env.template @@ -0,0 +1,85 @@ +# NFOGuard Environment Configuration Template +# Copy this to .env and customize for your setup + +# =========================================== +# MEDIA PATHS (REQUIRED) +# =========================================== +# Paths where your movies and TV shows are stored inside the container +# Adjust these to match YOUR directory structure +TV_PATHS=/media/TV/tv,/media/TV/tv6 +MOVIE_PATHS=/media/Movies/movies,/media/Movies/movies6 + +# =========================================== +# DATABASE CONFIGURATION +# =========================================== +# NFOGuard SQLite database location +DB_PATH=/app/data/media_dates.db + +# =========================================== +# RADARR DATABASE CONNECTION (REQUIRED) +# =========================================== +# Connection to Radarr's PostgreSQL database +RADARR_DB_HOST=radarr-postgres +RADARR_DB_PORT=5432 +RADARR_DB_NAME=radarr +RADARR_DB_USER=radarr +RADARR_DB_PASSWORD=your_radarr_db_password + +# =========================================== +# RADARR API (OPTIONAL - for fallback) +# =========================================== +# Only needed if you want API fallback (not recommended for performance) +RADARR_URL=http://radarr:7878 +RADARR_API_KEY=your_radarr_api_key + +# =========================================== +# SONARR API (OPTIONAL) +# =========================================== +# Only needed for TV processing +SONARR_URL=http://sonarr:8989 +SONARR_API_KEY=your_sonarr_api_key + +# =========================================== +# NFO MANAGEMENT +# =========================================== +# Whether to create/update .nfo files +MANAGE_NFO=true + +# Whether to fix file modification times to match import dates +FIX_DIR_MTIMES=true + +# Whether to add tags to prevent metadata drift +LOCK_METADATA=true + +# Brand name to add to NFO files +MANAGER_BRAND=NFOGuard + +# =========================================== +# PROCESSING SETTINGS +# =========================================== +# Delay before processing batched webhook events (seconds) +BATCH_DELAY=5.0 + +# Maximum concurrent series processing +MAX_CONCURRENT_SERIES=3 + +# Movie date strategy: import_then_digital or digital_then_import +MOVIE_PRIORITY=import_then_digital + +# When to query APIs: always, if_missing, never +MOVIE_POLL_MODE=always + +# Update mode: backfill_only or overwrite +MOVIE_DATE_UPDATE_MODE=backfill_only + +# =========================================== +# LOGGING +# =========================================== +# Enable debug logging +DEBUG=false + +# =========================================== +# SERVER +# =========================================== +# Port to run the webhook server on +PORT=8080 \ No newline at end of file diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..3e058e2 --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,181 @@ +# NFOGuard Deployment Guide + +## ๐Ÿš€ Quick Start for Docker Deployment + +### Step 1: Environment Configuration +```bash +# Copy the environment template +cp .env.template .env + +# Edit with your specific paths and database credentials +nano .env +``` + +**Key variables to customize in `.env`**: +```bash +# YOUR MEDIA PATHS (adjust these to your setup) +TV_PATHS=/media/TV/tv,/media/TV/tv6 +MOVIE_PATHS=/media/Movies/movies,/media/Movies/movies6 +HOST_MEDIA_PATH=/mnt/unionfs/Media + +# YOUR RADARR DATABASE PASSWORD +RADARR_DB_PASSWORD=your_actual_password_here +``` + +### Step 2: Deploy with Docker Compose +```bash +# Start NFOGuard +docker-compose up -d + +# Check logs +docker-compose logs -f nfoguard +``` + +### Step 3: Test Database Connection +```bash +# Test Radarr database connectivity +curl -X POST "http://localhost:8080/test/bulk-update" + +# Expected response: +# {"status": "success", "movies_with_imdb": 1500, "database_type": "postgresql"} +``` + +### Step 4: Test Movie Discovery +```bash +# Test that your movie paths are correctly configured +curl -X POST "http://localhost:8080/test/movie-scan" + +# Expected response: +# {"status": "success", "message": "Movie scan test found 1500 movies", +# "path_results": [{"path": "/media/Movies/movies", "exists": true, "movies_found": 800}, ...]} +``` + +### Step 5: Validate Database Performance +```bash +# Test the optimized database query performance +curl "http://localhost:8080/debug/movie/tt1674782" + +# Expected response with July 2025 date: +# {"detected_import_date": "2025-07-08T03:30:04+00:00", "import_source": "radarr:history.import"} +``` + +## ๐Ÿ“Š Production Workflow + +### Manual Operations (via webhooks) + +**1. Manual Movie Scan** +```bash +curl -X POST "http://localhost:8080/manual/scan?scan_type=movies" +``` + +**2. Bulk Update All Movies** +```bash +curl -X POST "http://localhost:8080/bulk/update" +``` + +**3. System Health Check** +```bash +curl "http://localhost:8080/health" +``` + +**4. Database Statistics** +```bash +curl "http://localhost:8080/stats" +``` + +**5. Debug Specific Movie** +```bash +curl "http://localhost:8080/debug/movie/tt1674782" +``` + +### Radarr Webhook Integration + +**Add to Radarr โ†’ Settings โ†’ Connect โ†’ Webhook:** +- **Name**: NFOGuard +- **URL**: `http://nfoguard:8080/webhook/radarr` +- **Method**: POST +- **Events**: On Download, On Upgrade, On Rename + +NFOGuard will automatically process movies when Radarr sends webhooks. + +## ๐Ÿ”ง Environment Variables Reference + +### Required Variables +| Variable | Description | Example | +|----------|-------------|---------| +| `MOVIE_PATHS` | Comma-separated movie directories | `/media/Movies/movies,/media/Movies/movies6` | +| `TV_PATHS` | Comma-separated TV directories | `/media/TV/tv,/media/TV/tv6` | +| `RADARR_DB_HOST` | Radarr PostgreSQL host | `radarr-postgres` | +| `RADARR_DB_PASSWORD` | Radarr database password | `your_password` | + +### Optional Variables +| Variable | Default | Description | +|----------|---------|-------------| +| `HOST_MEDIA_PATH` | `/mnt/unionfs/Media` | Host path mounted to `/media` | +| `DB_PATH` | `/app/data/media_dates.db` | NFOGuard database location | +| `MANAGE_NFO` | `true` | Create/update .nfo files | +| `FIX_DIR_MTIMES` | `true` | Fix file modification times | +| `MOVIE_PRIORITY` | `import_then_digital` | Date preference strategy | +| `DEBUG` | `false` | Enable debug logging | + +## ๐Ÿ— Docker Compose Customization + +**For different media structures:** +```yaml +# Example: Different media structure +services: + nfoguard: + environment: + - TV_PATHS=/storage/tv_shows,/storage/anime + - MOVIE_PATHS=/storage/movies + volumes: + - "/storage:/storage:rw" + - "./data:/app/data" +``` + +**For external database networks:** +```yaml +# Example: Connect to existing Radarr network +services: + nfoguard: + networks: + - radarr_network + +networks: + radarr_network: + external: true +``` + +## ๐Ÿ›  Troubleshooting + +### "0 movies processed" +1. Check paths: `curl -X POST "http://localhost:8080/test/movie-scan"` +2. Verify environment variables in `.env` +3. Ensure directories exist and have IMDb tags: `Movie Name [imdb-tt1234567] (2023)` + +### Database connection errors +1. Test connection: `curl -X POST "http://localhost:8080/test/bulk-update"` +2. Verify `RADARR_DB_*` variables +3. Check Docker network connectivity + +### Performance issues +1. Check health: `curl "http://localhost:8080/health"` +2. Monitor logs: `docker-compose logs -f nfoguard` +3. Verify database query performance: `curl "http://localhost:8080/debug/movie/tt1674782"` + +## ๐Ÿ“ˆ Monitoring + +**Health Check Endpoint**: `GET /health` +- Server status and uptime +- Database connectivity +- Radarr database status + +**Statistics Endpoint**: `GET /stats` +- Movie/TV counts +- Processing statistics + +**Batch Queue Status**: `GET /batch/status` +- Pending webhook events +- Processing queue status + +This deployment approach ensures NFOGuard can be easily configured for any media server setup while maintaining the database-only performance optimizations. \ No newline at end of file diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 0000000..bfc622f --- /dev/null +++ b/TESTING.md @@ -0,0 +1,131 @@ +# NFOGuard Testing Guide + +## ๐Ÿงช Test Scripts Overview + +NFOGuard includes 3 specialized testing scripts to validate different parts of the workflow. These are designed to be run via webhooks or manual API calls, not directly on the command line. + +### 1. `test_bulk_update.py` - Database Connection Validator +**Purpose**: Tests that the bulk update system can connect to databases without modifying any data. + +**What it tests**: +- Radarr database connection +- NFOGuard database connection +- Query execution (reads first 5 movies) +- Import detection logic + +**When to use**: Before running the full bulk update to ensure everything is configured correctly. + +**How to run**: +```bash +# Via webhook/API inside container +curl -X POST "http://nfoguard:8080/test/bulk-update" + +# Or manually inside container +python3 test_bulk_update.py +``` + +### 2. `test_movie_scan.py` - Directory Structure Validator +**Purpose**: Tests the movie directory scanning logic to ensure movies will be found. + +**What it tests**: +- Path configuration validation +- IMDb ID parsing from directory names +- Movie discovery logic +- Directory structure understanding + +**When to use**: When manual scans report "0 movies found" to debug path issues. + +**Sample output**: +``` +๐Ÿ” Testing Movie Directory Scanning +โœ… '/media/Movies/movies' -> Found 150 movies with IMDb IDs +โœ… 'The Dark Knight [imdb-tt0468569] (2008)' -> tt0468569 +โŒ 'Invalid Movie (2020)' -> No IMDb ID found +``` + +### 3. `test_end_to_end.py` - Complete Workflow Validator +**Purpose**: Tests the entire NFOGuard workflow from database queries to NFO creation. + +**What it tests**: +- Server health and connectivity +- Database performance (your July 2025 date test) +- Manual scan trigger +- Batch processing status +- Statistics endpoints + +**When to use**: After deployment to verify the complete system works end-to-end. + +**How to run**: +```bash +# Via webhook inside container +curl -X POST "http://nfoguard:8080/test/end-to-end" +``` + +## ๐Ÿš€ Testing Workflow for Docker Deployment + +### Step 1: Deploy with Environment Variables +```bash +# Copy your environment template +cp .env.example .env +# Edit .env with your actual database password +nano .env + +# Deploy +docker-compose up -d +``` + +### Step 2: Validate Database Connections +```bash +# Test database connectivity +curl -X POST "http://localhost:8080/test/bulk-update" + +# Should show: +# โœ… Radarr database connection successful +# โœ… NFOGuard database connection successful +``` + +### Step 3: Test Movie Discovery +```bash +# Test that movies will be found +curl -X POST "http://localhost:8080/test/movie-scan" + +# Should show paths and sample movies found +``` + +### Step 4: Run Full End-to-End Test +```bash +# Complete workflow test +curl -X POST "http://localhost:8080/test/end-to-end" + +# Should show all systems operational +``` + +### Step 5: Production Testing +```bash +# Test database performance (your working July date) +curl "http://localhost:8080/debug/movie/tt1674782" + +# Should return: 2025-07-08T03:30:04+00:00 + +# Run manual scan +curl -X POST "http://localhost:8080/manual/scan?scan_type=movies" + +# Run bulk update +curl -X POST "http://localhost:8080/bulk/update" +``` + +## ๐Ÿ›  Why These Tests Are Needed + +1. **Docker Environment Isolation**: Tests run in the same environment as the production app +2. **Database-Only Validation**: Ensures the optimized database queries work correctly +3. **Path Configuration**: Validates that your specific directory structure is properly configured +4. **Webhook Integration**: Tests run via HTTP calls, same as Radarr webhooks +5. **CI/CD Integration**: Can be automated in deployment pipelines + +## ๐Ÿ” Troubleshooting with Tests + +**"0 movies processed"** โ†’ Run `test_movie_scan.py` to check path configuration +**Database errors** โ†’ Run `test_bulk_update.py` to validate connections +**Workflow issues** โ†’ Run `test_end_to_end.py` for comprehensive diagnosis + +These tests are designed to catch issues before they impact your media processing workflow. \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6a85098 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,77 @@ +version: '3.8' + +services: + nfoguard: + build: . + container_name: nfoguard + ports: + - "${PORT:-8080}:8080" + volumes: + # Mount your media directories (adjust these paths to match your setup) + - "${HOST_MEDIA_PATH:-/mnt/unionfs/Media}:/media:rw" + + # NFOGuard data directory for database and logs + - "./data:/app/data" + + # Optional: Mount config if you want to override + # - "./config:/app/config" + + environment: + # Media paths inside container - customize these in your .env file + - TV_PATHS=${TV_PATHS:-/media/tv,/media/tv6} + - MOVIE_PATHS=${MOVIE_PATHS:-/media/movies,/media/movies6} + + # Database + - DB_PATH=${DB_PATH:-/app/data/media_dates.db} + + # Radarr Database (REQUIRED for database-only mode) + - RADARR_DB_HOST=${RADARR_DB_HOST} + - RADARR_DB_PORT=${RADARR_DB_PORT:-5432} + - RADARR_DB_NAME=${RADARR_DB_NAME:-radarr} + - RADARR_DB_USER=${RADARR_DB_USER} + - RADARR_DB_PASSWORD=${RADARR_DB_PASSWORD} + + # Optional Radarr API (fallback only) + - RADARR_URL=${RADARR_URL:-} + - RADARR_API_KEY=${RADARR_API_KEY:-} + + # Optional Sonarr API (for TV processing) + - SONARR_URL=${SONARR_URL:-} + - SONARR_API_KEY=${SONARR_API_KEY:-} + + # NFO Management + - MANAGE_NFO=${MANAGE_NFO:-true} + - FIX_DIR_MTIMES=${FIX_DIR_MTIMES:-true} + - LOCK_METADATA=${LOCK_METADATA:-true} + - MANAGER_BRAND=${MANAGER_BRAND:-NFOGuard} + + # Processing settings + - BATCH_DELAY=${BATCH_DELAY:-5.0} + - MAX_CONCURRENT_SERIES=${MAX_CONCURRENT_SERIES:-3} + - MOVIE_PRIORITY=${MOVIE_PRIORITY:-import_then_digital} + - MOVIE_POLL_MODE=${MOVIE_POLL_MODE:-always} + - MOVIE_DATE_UPDATE_MODE=${MOVIE_DATE_UPDATE_MODE:-backfill_only} + + # Logging + - DEBUG=${DEBUG:-false} + + # Server + - PORT=${PORT:-8080} + + # Optional: Connect to Radarr's database network + # networks: + # - radarr_network + + restart: unless-stopped + + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + +# Optional: If you need to connect to Radarr's database network +# networks: +# radarr_network: +# external: true \ No newline at end of file diff --git a/nfoguard.py b/nfoguard.py index 1f6dec4..0cc795d 100644 --- a/nfoguard.py +++ b/nfoguard.py @@ -1044,6 +1044,75 @@ async def manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = N background_tasks.add_task(run_scan) return {"status": "started", "message": f"Manual {scan_type} scan started"} +@app.post("/test/bulk-update") +async def test_bulk_update(): + """Test bulk update functionality without modifying data""" + try: + from clients.radarr_db_client import RadarrDbClient + + # Test Radarr database + radarr_db = RadarrDbClient.from_env() + if not radarr_db: + return {"status": "error", "message": "Radarr database connection failed"} + + # Test query execution + query = 'SELECT COUNT(*) FROM "Movies" m JOIN "MovieMetadata" mm ON m."MovieMetadataId" = mm."Id" WHERE mm."ImdbId" IS NOT NULL' + with radarr_db._get_connection() as conn: + cursor = conn.cursor() + cursor.execute(query) + movie_count = cursor.fetchone()[0] + + return { + "status": "success", + "message": "Bulk update test passed", + "movies_with_imdb": movie_count, + "database_type": radarr_db.db_type + } + except Exception as e: + return {"status": "error", "message": f"Bulk update test failed: {e}"} + +@app.post("/test/movie-scan") +async def test_movie_scan(): + """Test movie directory scanning logic""" + try: + results = [] + for path in config.movie_paths: + path_result = { + "path": str(path), + "exists": path.exists(), + "movies_found": 0 + } + + if path.exists(): + for item in path.iterdir(): + if item.is_dir() and nfo_manager.parse_imdb_from_path(item): + path_result["movies_found"] += 1 + + results.append(path_result) + + total_movies = sum(r["movies_found"] for r in results) + return { + "status": "success", + "message": f"Movie scan test found {total_movies} movies", + "path_results": results + } + except Exception as e: + return {"status": "error", "message": f"Movie scan test failed: {e}"} + +@app.post("/bulk/update") +async def trigger_bulk_update(background_tasks: BackgroundTasks): + """Trigger bulk update of all movies""" + async def run_bulk_update(): + try: + from bulk_update_movies import bulk_update_all_movies + success = bulk_update_all_movies() + _log("INFO", f"Bulk update completed: {'success' if success else 'failed'}") + except Exception as e: + _log("ERROR", f"Bulk update error: {e}") + + background_tasks.add_task(run_bulk_update) + return {"status": "started", "message": "Bulk update started"} + # --------------------------- # Main # --------------------------- diff --git a/test_bulk_update.py b/test_bulk_update.py new file mode 100644 index 0000000..07a301c --- /dev/null +++ b/test_bulk_update.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +""" +Test script for bulk_update_movies.py +This tests the bulk update functionality without modifying data +""" + +import os +import sys +from pathlib import Path + +def test_bulk_update_dry_run(): + """Test bulk update script in dry-run mode""" + print("๐Ÿงช Testing Bulk Update Script") + print("=" * 40) + + # Check if we can import the required modules + try: + from clients.radarr_db_client import RadarrDbClient + from core.database import NFOGuardDatabase + print("โœ… Successfully imported required modules") + except Exception as e: + print(f"โŒ Import error: {e}") + return False + + # Test database connections + print("\n๐Ÿ”Œ Testing Database Connections...") + + # Test Radarr database + try: + radarr_db = RadarrDbClient.from_env() + if not radarr_db: + print("โŒ Radarr database connection failed - check environment variables") + return False + print("โœ… Radarr database connection successful") + except Exception as e: + print(f"โŒ Radarr database error: {e}") + return False + + # Test NFOGuard database + try: + db_path = Path(os.environ.get("DB_PATH", "/app/data/media_dates.db")) + nfo_db = NFOGuardDatabase(db_path) + print(f"โœ… NFOGuard database connection successful: {db_path}") + except Exception as e: + print(f"โŒ NFOGuard database error: {e}") + return False + + # Test query execution (dry run) + print("\n๐Ÿ“Š Testing Query Execution...") + + try: + query = """ + SELECT + mm."ImdbId" as imdb_id, + m."Id" as movie_id, + mm."Title" as title, + mm."Year" as year, + m."Path" as path + FROM "Movies" m + JOIN "MovieMetadata" mm ON m."MovieMetadataId" = mm."Id" + WHERE mm."ImdbId" IS NOT NULL AND mm."ImdbId" != '' + ORDER BY mm."Title" + LIMIT 5 + """ + + with radarr_db._get_connection() as conn: + if radarr_db.db_type == "postgresql": + import psycopg2.extras + cursor = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + else: + cursor = conn.cursor() + + cursor.execute(query) + movies = cursor.fetchall() + + print(f"โœ… Successfully queried Radarr database: {len(movies)} sample movies found") + + # Show sample data + for i, movie in enumerate(movies): + if radarr_db.db_type == "postgresql": + imdb_id, title, year = movie['imdb_id'], movie['title'], movie['year'] + else: + imdb_id, title, year = movie[0], movie[2], movie[3] + print(f" Sample {i+1}: {title} ({year}) - {imdb_id}") + + except Exception as e: + print(f"โŒ Query execution failed: {e}") + return False + + print("\nโœ… All tests passed! Bulk update script should work correctly.") + return True + +if __name__ == "__main__": + success = test_bulk_update_dry_run() + if not success: + print("\nโŒ Bulk update test failed!") + sys.exit(1) + else: + print("\n๐ŸŽ‰ Bulk update test successful!") \ No newline at end of file diff --git a/test_end_to_end.py b/test_end_to_end.py new file mode 100644 index 0000000..2dbca71 --- /dev/null +++ b/test_end_to_end.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +""" +End-to-end testing script for NFOGuard workflow +Tests the complete flow: Database -> Manual Scan -> NFO Creation +""" + +import os +import sys +import requests +import time +from pathlib import Path + +def test_server_health(): + """Test if the server is running and healthy""" + try: + response = requests.get("http://localhost:8080/health", timeout=10) + if response.status_code == 200: + health_data = response.json() + print(f"โœ… Server is healthy: {health_data['status']}") + print(f" Version: {health_data['version']}") + print(f" Database: {health_data['database_status']}") + if health_data.get('radarr_database'): + radarr_status = health_data['radarr_database']['status'] + print(f" Radarr DB: {radarr_status}") + return True + else: + print(f"โŒ Server health check failed: {response.status_code}") + return False + except Exception as e: + print(f"โŒ Cannot connect to server: {e}") + return False + +def test_database_performance(): + """Test the database performance endpoint""" + try: + response = requests.get("http://localhost:8080/debug/movie/tt1674782", timeout=10) + if response.status_code == 200: + debug_data = response.json() + if debug_data.get('detected_import_date'): + print(f"โœ… Database query works: {debug_data['detected_import_date']}") + print(f" Source: {debug_data['import_source']}") + return True + else: + print(f"โŒ No import date found for test movie") + return False + else: + print(f"โŒ Debug endpoint failed: {response.status_code}") + return False + except Exception as e: + print(f"โŒ Database test failed: {e}") + return False + +def test_manual_scan(): + """Test the manual scan functionality""" + try: + print("๐Ÿ” Starting manual movie scan...") + response = requests.post( + "http://localhost:8080/manual/scan?scan_type=movies", + timeout=30 + ) + + if response.status_code == 200: + scan_data = response.json() + print(f"โœ… Manual scan started: {scan_data['message']}") + + # Wait a bit for processing + print("โณ Waiting for scan to process...") + time.sleep(10) + + # Check batch status + batch_response = requests.get("http://localhost:8080/batch/status", timeout=10) + if batch_response.status_code == 200: + batch_data = batch_response.json() + print(f" Pending items: {batch_data['pending_count']}") + print(f" Processing items: {batch_data['processing_count']}") + + return True + else: + print(f"โŒ Manual scan failed: {response.status_code}") + return False + except Exception as e: + print(f"โŒ Manual scan test failed: {e}") + return False + +def test_stats_endpoint(): + """Test database stats""" + try: + response = requests.get("http://localhost:8080/stats", timeout=10) + if response.status_code == 200: + stats = response.json() + print(f"โœ… Database stats:") + print(f" Movies: {stats.get('movie_count', 0)}") + print(f" TV Series: {stats.get('series_count', 0)}") + print(f" Episodes: {stats.get('episode_count', 0)}") + return True + else: + print(f"โŒ Stats endpoint failed: {response.status_code}") + return False + except Exception as e: + print(f"โŒ Stats test failed: {e}") + return False + +def run_end_to_end_test(): + """Run complete end-to-end test suite""" + print("๐ŸŽฏ NFOGuard End-to-End Testing") + print("=" * 50) + + tests = [ + ("Server Health", test_server_health), + ("Database Performance", test_database_performance), + ("Manual Scan", test_manual_scan), + ("Database Stats", test_stats_endpoint) + ] + + passed = 0 + failed = 0 + + for test_name, test_func in tests: + print(f"\n๐Ÿงช Running: {test_name}") + try: + if test_func(): + passed += 1 + print(f"โœ… {test_name} passed") + else: + failed += 1 + print(f"โŒ {test_name} failed") + except Exception as e: + failed += 1 + print(f"โŒ {test_name} crashed: {e}") + + print(f"\n๐Ÿ“Š Test Results:") + print(f" โœ… Passed: {passed}") + print(f" โŒ Failed: {failed}") + print(f" ๐Ÿ“ˆ Success Rate: {passed}/{passed+failed} ({100*passed/(passed+failed):.1f}%)") + + return failed == 0 + +if __name__ == "__main__": + success = run_end_to_end_test() + + if success: + print("\n๐ŸŽ‰ All end-to-end tests passed!") + print("\n๐Ÿ“‹ Next Steps:") + print("1. Run bulk update: python3 bulk_update_movies.py") + print("2. Test with real movie: curl http://localhost:8080/debug/movie/tt1674782") + print("3. Check NFO file creation in /media/Movies/movies/") + print("4. Verify file timestamps match import dates") + else: + print("\n๐Ÿ’ฅ Some tests failed - check the logs above") + sys.exit(1) \ No newline at end of file diff --git a/test_movie_scan.py b/test_movie_scan.py new file mode 100644 index 0000000..f3d1d5a --- /dev/null +++ b/test_movie_scan.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +""" +Test script to verify movie directory scanning logic +Simulates what the manual scan would do with correct paths +""" + +import os +import sys +from pathlib import Path + +def test_movie_scanning(): + """Test movie directory scanning logic""" + print("๐Ÿ” Testing Movie Directory Scanning") + print("=" * 50) + + # Test paths - use the corrected paths from the code + movie_paths = [ + Path("/media/Movies/movies"), + Path("/media/Movies/movies6") + ] + + print("๐Ÿ“ Testing paths:") + for path in movie_paths: + print(f" {path}") + + # Import the NFO manager to test the IMDb parsing + try: + from core.nfo_manager import NFOManager + nfo_manager = NFOManager() + print("โœ… Successfully imported NFOManager") + except Exception as e: + print(f"โŒ Failed to import NFOManager: {e}") + return False + + # Simulate the scanning logic + total_movies_found = 0 + + for media_path in movie_paths: + print(f"\n๐ŸŽฌ Scanning: {media_path}") + + # Check if path exists (this will fail on your local machine but show the logic) + if not media_path.exists(): + print(f" โš ๏ธ Path doesn't exist locally (expected on dev machine): {media_path}") + print(f" โ„น๏ธ On production server, this path should contain directories like:") + print(f" Example Movie [imdb-tt1234567] (2023)/") + print(f" Another Movie [imdb-tt7654321] (2022)/") + continue + + movie_count = 0 + for item in media_path.iterdir(): + if item.is_dir(): + imdb_id = nfo_manager.parse_imdb_from_path(item) + if imdb_id: + movie_count += 1 + print(f" โœ… Found movie: {item.name} -> {imdb_id}") + else: + print(f" โญ๏ธ Skipped (no IMDb ID): {item.name}") + + print(f" ๐Ÿ“Š Found {movie_count} movies with IMDb IDs in {media_path}") + total_movies_found += movie_count + + print(f"\n๐ŸŽฏ Total movies that would be processed: {total_movies_found}") + + # Test IMDb ID parsing with sample directory names + print(f"\n๐Ÿงช Testing IMDb ID Parsing Logic:") + test_names = [ + "The Dark Knight [imdb-tt0468569] (2008)", + "Inception [imdb-tt1375666] (2010)", + "Invalid Movie Name (2020)", + "Movie Without IMDb (2021)", + "Another Movie [imdb-tt1234567] (2023)" + ] + + for test_name in test_names: + test_path = Path(f"/fake/path/{test_name}") + imdb_id = nfo_manager.parse_imdb_from_path(test_path) + if imdb_id: + print(f" โœ… '{test_name}' -> {imdb_id}") + else: + print(f" โŒ '{test_name}' -> No IMDb ID found") + + return True + +def show_manual_scan_instructions(): + """Show instructions for testing on the production server""" + print("\n" + "="*60) + print("๐Ÿ“‹ NEXT STEPS - Test on Production Server") + print("="*60) + print() + print("1. ๐Ÿ”ง Update your environment variables:") + print(" export MOVIE_PATHS='/media/Movies/movies,/media/Movies/movies6'") + print(" export TV_PATHS='/media/TV/tv,/media/TV/tv6'") + print() + print("2. ๐Ÿš€ Test the manual scan:") + print(" curl -X POST 'http://localhost:8080/manual/scan?scan_type=movies'") + print() + print("3. ๐Ÿ“Š Check the logs for movie processing:") + print(" docker logs | grep -E '(Processing movie|Completed movie scan)'") + print() + print("4. ๐Ÿงช Test the bulk update script:") + print(" python3 test_bulk_update.py") + print(" python3 bulk_update_movies.py") + print() + print("5. โœ… Verify end-to-end workflow:") + print(" curl http://localhost:8080/debug/movie/tt1674782") + print() + +if __name__ == "__main__": + success = test_movie_scanning() + + if success: + print("\nโœ… Local testing completed successfully!") + show_manual_scan_instructions() + else: + print("\nโŒ Local testing failed!") + sys.exit(1) \ No newline at end of file