Implement application setup and flow middleware

- Add SetupService for detecting application setup completion
- Create ApplicationFlowMiddleware to enforce setup  auth  main flow
- Add setup processing endpoints (/api/auth/setup, /api/auth/setup/status)
- Add Pydantic models for setup requests and responses
- Integrate middleware into FastAPI application
- Fix logging paths to use ./logs consistently
- All existing templates (setup.html, login.html) already working
This commit is contained in:
2025-10-06 12:48:18 +02:00
parent 3b8ca8b8f3
commit 3f98dd6ebb
37 changed files with 1051 additions and 233 deletions

View File

@@ -1,6 +1,6 @@
from src.core.SerieScanner import SerieScanner
from src.core.entities.SerieList import SerieList
from src.core.providers.provider_factory import Loaders
from src.core.SerieScanner import SerieScanner
class SeriesApp:

View File

@@ -34,6 +34,10 @@ from fastapi.templating import Jinja2Templates
from pydantic import BaseModel, Field
from pydantic_settings import BaseSettings
# Import application flow services
from src.server.middleware.application_flow_middleware import ApplicationFlowMiddleware
from src.server.services.setup_service import SetupService
# Import our custom middleware - temporarily disabled due to file corruption
# from src.server.web.middleware.fastapi_auth_middleware import AuthMiddleware
# from src.server.web.middleware.fastapi_logging_middleware import (
@@ -46,7 +50,7 @@ logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('./logs/aniworld.log'),
logging.FileHandler('./logs/aniworld.log'),
logging.StreamHandler()
]
)
@@ -138,6 +142,23 @@ class ErrorResponse(BaseModel):
code: Optional[str] = None
details: Optional[Dict[str, Any]] = None
class SetupRequest(BaseModel):
"""Setup request model."""
password: str = Field(..., min_length=8, description="Master password (min 8 characters)")
directory: str = Field(..., min_length=1, description="Anime directory path")
class SetupResponse(BaseModel):
"""Setup response model."""
status: str
message: str
redirect_url: Optional[str] = None
class SetupStatusResponse(BaseModel):
"""Setup status response model."""
setup_complete: bool
requirements: Dict[str, bool]
missing_requirements: List[str]
# Authentication utilities
def hash_password(password: str) -> str:
"""Hash password with salt using SHA-256."""
@@ -311,6 +332,10 @@ app.add_middleware(
allow_headers=["*"],
)
# Add application flow middleware
setup_service = SetupService()
app.add_middleware(ApplicationFlowMiddleware, setup_service=setup_service)
# Add custom middleware - temporarily disabled
# app.add_middleware(EnhancedLoggingMiddleware)
# app.add_middleware(AuthMiddleware)
@@ -360,6 +385,144 @@ async def legacy_download(
except Exception as e:
return {"status": "error", "message": f"Failed to start download: {str(e)}"}
# Setup endpoints
@app.get("/api/auth/setup/status", response_model=SetupStatusResponse, tags=["Setup"])
async def get_setup_status() -> SetupStatusResponse:
"""
Check the current setup status of the application.
Returns information about what setup requirements are met and which are missing.
"""
try:
setup_service = SetupService()
requirements = setup_service.get_setup_requirements()
missing = setup_service.get_missing_requirements()
return SetupStatusResponse(
setup_complete=setup_service.is_setup_complete(),
requirements=requirements,
missing_requirements=missing
)
except Exception as e:
logger.error(f"Error checking setup status: {e}")
return SetupStatusResponse(
setup_complete=False,
requirements={},
missing_requirements=["Error checking setup status"]
)
@app.post("/api/auth/setup", response_model=SetupResponse, tags=["Setup"])
async def process_setup(request_data: SetupRequest) -> SetupResponse:
"""
Process the initial application setup.
- **password**: Master password (minimum 8 characters)
- **directory**: Anime directory path
"""
try:
setup_service = SetupService()
# Check if setup is already complete
if setup_service.is_setup_complete():
return SetupResponse(
status="error",
message="Setup has already been completed"
)
# Validate directory path
from pathlib import Path
directory_path = Path(request_data.directory)
if not directory_path.is_absolute():
return SetupResponse(
status="error",
message="Please provide an absolute directory path"
)
# Create directory if it doesn't exist
try:
directory_path.mkdir(parents=True, exist_ok=True)
except Exception as e:
logger.error(f"Failed to create directory: {e}")
return SetupResponse(
status="error",
message=f"Failed to create directory: {str(e)}"
)
# Hash the password
password_hash = hash_password(request_data.password)
# Prepare configuration updates
config_updates = {
"security": {
"master_password_hash": password_hash,
"salt": settings.password_salt,
"session_timeout_hours": settings.token_expiry_hours,
"max_failed_attempts": 5,
"lockout_duration_minutes": 30
},
"anime": {
"directory": str(directory_path),
"download_threads": 3,
"download_speed_limit": None,
"auto_rescan_time": "03:00",
"auto_download_after_rescan": False
},
"logging": {
"level": "INFO",
"enable_console_logging": True,
"enable_console_progress": False,
"enable_fail2ban_logging": True,
"log_file": "aniworld.log",
"max_log_size_mb": 10,
"log_backup_count": 5
},
"providers": {
"default_provider": "aniworld.to",
"preferred_language": "German Dub",
"fallback_providers": ["aniworld.to"],
"provider_timeout": 30,
"retry_attempts": 3,
"provider_settings": {
"aniworld.to": {
"enabled": True,
"priority": 1,
"quality_preference": "720p"
}
}
},
"advanced": {
"max_concurrent_downloads": 3,
"download_buffer_size": 8192,
"connection_timeout": 30,
"read_timeout": 300,
"enable_debug_mode": False,
"cache_duration_minutes": 60
}
}
# Mark setup as complete and save configuration
success = setup_service.mark_setup_complete(config_updates)
if success:
logger.info("Application setup completed successfully")
return SetupResponse(
status="success",
message="Setup completed successfully",
redirect_url="/login"
)
else:
return SetupResponse(
status="error",
message="Failed to save configuration"
)
except Exception as e:
logger.error(f"Setup processing error: {e}")
return SetupResponse(
status="error",
message="Setup failed due to internal error"
)
# Authentication endpoints
@app.post("/auth/login", response_model=LoginResponse, tags=["Authentication"])
async def login(request_data: LoginRequest, request: Request) -> LoginResponse:

View File

@@ -0,0 +1,248 @@
"""
Application Flow Middleware for FastAPI.
This middleware enforces the application flow priorities:
1. Setup page (if setup is not complete)
2. Authentication page (if user is not authenticated)
3. Main application (for authenticated users with completed setup)
The middleware redirects users to the appropriate page based on their current state
and the state of the application setup.
"""
import logging
from typing import Optional
from fastapi import Request
from fastapi.responses import RedirectResponse
from starlette.middleware.base import BaseHTTPMiddleware
# Import the setup service
try:
from ..services.setup_service import SetupService
except ImportError:
# Handle case where service is not available
class SetupService:
def is_setup_complete(self):
return True
logger = logging.getLogger(__name__)
class ApplicationFlowMiddleware(BaseHTTPMiddleware):
"""
Middleware to enforce application flow: setup → auth → main application.
This middleware:
1. Checks if setup is complete
2. Validates authentication status
3. Redirects to appropriate page based on state
4. Allows API endpoints and static files to pass through
"""
def __init__(self, app, setup_service: Optional[SetupService] = None):
"""
Initialize the application flow middleware.
Args:
app: FastAPI application instance
setup_service: Setup service instance (optional, will create if not provided)
"""
super().__init__(app)
self.setup_service = setup_service or SetupService()
# Define paths that should bypass flow enforcement
self.bypass_paths = {
"/static", # Static files
"/favicon.ico", # Browser favicon requests
"/robots.txt", # Robots.txt
"/health", # Health check endpoints
"/docs", # OpenAPI documentation
"/redoc", # ReDoc documentation
"/openapi.json" # OpenAPI spec
}
# API paths that should bypass flow but may require auth
self.api_paths = {
"/api",
"/auth"
}
# Pages that are part of the flow and should be accessible
self.flow_pages = {
"/setup",
"/login",
"/app"
}
async def dispatch(self, request: Request, call_next):
"""
Process the request and enforce application flow.
Args:
request: Incoming HTTP request
call_next: Next middleware/handler in chain
Returns:
Response: Either a redirect response or the result of call_next
"""
try:
# Get the request path
path = request.url.path
# Skip flow enforcement for certain paths
if self._should_bypass_flow(path):
return await call_next(request)
# Check application setup status
setup_complete = self.setup_service.is_setup_complete()
# Check authentication status
is_authenticated = await self._is_user_authenticated(request)
# Determine the appropriate action
redirect_response = self._determine_redirect(path, setup_complete, is_authenticated)
if redirect_response:
logger.info(f"Redirecting {path} to {redirect_response.headers.get('location')}")
return redirect_response
# Continue with the request
return await call_next(request)
except Exception as e:
logger.error(f"Error in ApplicationFlowMiddleware: {e}", exc_info=True)
# In case of error, allow the request to continue
return await call_next(request)
def _should_bypass_flow(self, path: str) -> bool:
"""
Check if the given path should bypass flow enforcement.
Args:
path: Request path
Returns:
bool: True if path should bypass flow enforcement
"""
# Check exact bypass paths
for bypass_path in self.bypass_paths:
if path.startswith(bypass_path):
return True
# API paths bypass flow enforcement (but may have their own auth)
for api_path in self.api_paths:
if path.startswith(api_path):
return True
return False
async def _is_user_authenticated(self, request: Request) -> bool:
"""
Check if the user is authenticated by validating JWT token.
Args:
request: HTTP request object
Returns:
bool: True if user is authenticated, False otherwise
"""
try:
# Check for Authorization header
auth_header = request.headers.get("authorization")
if not auth_header or not auth_header.startswith("Bearer "):
return False
# Extract and validate token
token = auth_header.split(" ")[1]
# Import JWT validation function (avoid circular imports)
try:
from ..fastapi_app import verify_jwt_token
payload = verify_jwt_token(token)
return payload is not None
except ImportError:
# Fallback if import fails
logger.warning("Could not import JWT verification function")
return False
except Exception as e:
logger.error(f"Error checking authentication: {e}")
return False
def _determine_redirect(self, path: str, setup_complete: bool, is_authenticated: bool) -> Optional[RedirectResponse]:
"""
Determine if a redirect is needed based on current state.
Args:
path: Current request path
setup_complete: Whether application setup is complete
is_authenticated: Whether user is authenticated
Returns:
Optional[RedirectResponse]: Redirect response if needed, None otherwise
"""
# If setup is not complete
if not setup_complete:
# Allow access to setup page
if path == "/setup":
return None
# Redirect everything else to setup
return RedirectResponse(url="/setup", status_code=302)
# Setup is complete, check authentication
if not is_authenticated:
# Allow access to login page
if path == "/login":
return None
# Redirect unauthenticated users to login (except for specific pages)
if path in self.flow_pages or path == "/":
return RedirectResponse(url="/login", status_code=302)
# User is authenticated and setup is complete
else:
# Redirect from setup/login pages to main app
if path in ["/setup", "/login", "/"]:
return RedirectResponse(url="/app", status_code=302)
# No redirect needed
return None
def get_flow_status(self, request: Request) -> dict:
"""
Get current flow status for debugging/monitoring.
Args:
request: HTTP request object
Returns:
dict: Current flow status information
"""
try:
setup_complete = self.setup_service.is_setup_complete()
is_authenticated = self._is_user_authenticated(request)
return {
"setup_complete": setup_complete,
"authenticated": is_authenticated,
"path": request.url.path,
"should_bypass": self._should_bypass_flow(request.url.path)
}
except Exception as e:
return {
"error": str(e),
"path": request.url.path
}
def create_application_flow_middleware(setup_service: Optional[SetupService] = None) -> ApplicationFlowMiddleware:
"""
Factory function to create application flow middleware.
Args:
setup_service: Setup service instance (optional)
Returns:
ApplicationFlowMiddleware: Configured middleware instance
"""
return ApplicationFlowMiddleware(app=None, setup_service=setup_service)

View File

@@ -0,0 +1,268 @@
"""
Setup service for detecting and managing application setup state.
This service determines if the application is properly configured and set up,
following the application flow pattern: setup → auth → main application.
"""
import json
import sqlite3
from datetime import datetime
from pathlib import Path
from typing import Dict, Any, Optional, List
import logging
logger = logging.getLogger(__name__)
class SetupService:
"""Service for managing application setup detection and configuration."""
def __init__(self, config_path: str = "data/config.json", db_path: str = "data/aniworld.db"):
"""Initialize the setup service with configuration and database paths."""
self.config_path = Path(config_path)
self.db_path = Path(db_path)
self._config_cache: Optional[Dict[str, Any]] = None
def is_setup_complete(self) -> bool:
"""
Check if the application setup is complete.
Setup is considered complete if:
1. Configuration file exists and is valid
2. Database exists and is accessible
3. Master password is configured
4. Setup completion flag is set (if present)
Returns:
bool: True if setup is complete, False otherwise
"""
try:
# Check if configuration file exists and is valid
if not self._is_config_valid():
logger.info("Setup incomplete: Configuration file is missing or invalid")
return False
# Check if database exists and is accessible
if not self._is_database_accessible():
logger.info("Setup incomplete: Database is not accessible")
return False
# Check if master password is configured
if not self._is_master_password_configured():
logger.info("Setup incomplete: Master password is not configured")
return False
# Check for explicit setup completion flag
config = self.get_config()
if config and config.get("setup", {}).get("completed") is False:
logger.info("Setup incomplete: Setup completion flag is False")
return False
logger.debug("Setup validation complete: All checks passed")
return True
except Exception as e:
logger.error(f"Error checking setup completion: {e}")
return False
def _is_config_valid(self) -> bool:
"""Check if the configuration file exists and contains valid JSON."""
try:
if not self.config_path.exists():
return False
config = self.get_config()
return config is not None and isinstance(config, dict)
except Exception as e:
logger.error(f"Configuration validation error: {e}")
return False
def _is_database_accessible(self) -> bool:
"""Check if the database exists and is accessible."""
try:
if not self.db_path.exists():
return False
# Try to connect and perform a simple query
with sqlite3.connect(str(self.db_path)) as conn:
cursor = conn.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' LIMIT 1")
return True
except Exception as e:
logger.error(f"Database accessibility check failed: {e}")
return False
def _is_master_password_configured(self) -> bool:
"""Check if master password is properly configured."""
try:
config = self.get_config()
if not config:
return False
security_config = config.get("security", {})
# Check if password hash exists
password_hash = security_config.get("master_password_hash")
salt = security_config.get("salt")
return bool(password_hash and salt and len(password_hash) > 0 and len(salt) > 0)
except Exception as e:
logger.error(f"Master password configuration check failed: {e}")
return False
def get_config(self, force_reload: bool = False) -> Optional[Dict[str, Any]]:
"""
Get the configuration data from the config file.
Args:
force_reload: If True, reload config from file even if cached
Returns:
dict: Configuration data or None if not accessible
"""
try:
if self._config_cache is None or force_reload:
if not self.config_path.exists():
return None
with open(self.config_path, 'r', encoding='utf-8') as f:
self._config_cache = json.load(f)
return self._config_cache
except Exception as e:
logger.error(f"Error loading configuration: {e}")
return None
def mark_setup_complete(self, config_updates: Optional[Dict[str, Any]] = None) -> bool:
"""
Mark the setup as completed and optionally update configuration.
Args:
config_updates: Additional configuration updates to apply
Returns:
bool: True if successful, False otherwise
"""
try:
config = self.get_config() or {}
# Update configuration with any provided updates
if config_updates:
config.update(config_updates)
# Set setup completion flag
if "setup" not in config:
config["setup"] = {}
config["setup"]["completed"] = True
config["setup"]["completed_at"] = str(datetime.utcnow())
# Save updated configuration
return self._save_config(config)
except Exception as e:
logger.error(f"Error marking setup as complete: {e}")
return False
def reset_setup(self) -> bool:
"""
Reset the setup completion status (for development/testing).
Returns:
bool: True if successful, False otherwise
"""
try:
config = self.get_config()
if not config:
return False
# Remove or set setup completion flag to false
if "setup" in config:
config["setup"]["completed"] = False
return self._save_config(config)
except Exception as e:
logger.error(f"Error resetting setup: {e}")
return False
def _save_config(self, config: Dict[str, Any]) -> bool:
"""Save configuration to file."""
try:
# Ensure directory exists
self.config_path.parent.mkdir(parents=True, exist_ok=True)
# Save configuration
with open(self.config_path, 'w', encoding='utf-8') as f:
json.dump(config, f, indent=4, ensure_ascii=False)
# Clear cache to force reload on next access
self._config_cache = None
logger.info(f"Configuration saved to {self.config_path}")
return True
except Exception as e:
logger.error(f"Error saving configuration: {e}")
return False
def get_setup_requirements(self) -> Dict[str, bool]:
"""
Get detailed breakdown of setup requirements and their status.
Returns:
dict: Dictionary with requirement names and their completion status
"""
config = self.get_config()
return {
"config_file_exists": self.config_path.exists(),
"config_file_valid": self._is_config_valid(),
"database_exists": self.db_path.exists(),
"database_accessible": self._is_database_accessible(),
"master_password_configured": self._is_master_password_configured(),
"setup_marked_complete": bool(config and config.get("setup", {}).get("completed", True))
}
def get_missing_requirements(self) -> List[str]:
"""
Get list of missing setup requirements.
Returns:
list: List of missing requirement descriptions
"""
requirements = self.get_setup_requirements()
missing = []
if not requirements["config_file_exists"]:
missing.append("Configuration file is missing")
elif not requirements["config_file_valid"]:
missing.append("Configuration file is invalid or corrupted")
if not requirements["database_exists"]:
missing.append("Database file is missing")
elif not requirements["database_accessible"]:
missing.append("Database is not accessible or corrupted")
if not requirements["master_password_configured"]:
missing.append("Master password is not configured")
if not requirements["setup_marked_complete"]:
missing.append("Setup process was not completed")
return missing
# Convenience functions for easy import
def is_setup_complete() -> bool:
"""Convenience function to check if setup is complete."""
service = SetupService()
return service.is_setup_complete()
def get_setup_service() -> SetupService:
"""Get a configured setup service instance."""
return SetupService()

View File

@@ -2,11 +2,12 @@
Pytest configuration file for AniWorld application tests.
"""
import pytest
import os
import sys
from unittest.mock import Mock
import pytest
# Add source directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
@@ -79,8 +80,9 @@ def sample_episode_data():
@pytest.fixture
def valid_jwt_token():
"""Valid JWT token for testing."""
import jwt
from datetime import datetime, timedelta
import jwt
payload = {
"user": "test_user",
@@ -92,8 +94,9 @@ def valid_jwt_token():
@pytest.fixture
def expired_jwt_token():
"""Expired JWT token for testing."""
import jwt
from datetime import datetime, timedelta
import jwt
payload = {
"user": "test_user",

View File

@@ -5,12 +5,13 @@ Tests complete user authentication scenarios including login/logout flow
and session management.
"""
import pytest
import sys
import os
from fastapi.testclient import TestClient
import sys
from unittest.mock import patch
import pytest
from fastapi.testclient import TestClient
# Add source directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))

View File

@@ -5,11 +5,12 @@ This module tests complete user workflows for bulk operations including
download flows, export processes, and error handling scenarios.
"""
import pytest
import time
from fastapi.testclient import TestClient
from unittest.mock import patch, AsyncMock
import asyncio
import time
from unittest.mock import AsyncMock, patch
import pytest
from fastapi.testclient import TestClient
from src.server.fastapi_app import app

View File

@@ -5,11 +5,12 @@ Tests complete CLI workflows including progress bar functionality,
retry logic, user interactions, and error scenarios.
"""
import pytest
import sys
import os
from unittest.mock import Mock, patch
import sys
import tempfile
from unittest.mock import Mock, patch
import pytest
# Add source directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))

View File

@@ -5,11 +5,12 @@ This module tests complete user workflows for changing preferences and verifying
that the UI responds appropriately to preference changes.
"""
import pytest
import time
from fastapi.testclient import TestClient
from unittest.mock import patch
import pytest
from fastapi.testclient import TestClient
from src.server.fastapi_app import app

View File

@@ -5,12 +5,13 @@ Tests anime search, anime details, episode retrieval with pagination,
valid/invalid IDs, and search filtering functionality.
"""
import pytest
import sys
import os
from fastapi.testclient import TestClient
import sys
from unittest.mock import patch
import pytest
from fastapi.testclient import TestClient
# Add source directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))

View File

@@ -5,11 +5,12 @@ Tests POST /auth/login, GET /auth/verify, POST /auth/logout endpoints
with valid/invalid credentials and tokens.
"""
import pytest
import sys
import os
import sys
from unittest.mock import Mock, patch
import pytest
from fastapi.testclient import TestClient
from unittest.mock import patch, Mock
# Add source directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))

View File

@@ -6,9 +6,10 @@ Tests include authentication, validation, and error handling.
"""
import json
from unittest.mock import Mock, patch
import pytest
from fastapi.testclient import TestClient
from unittest.mock import patch, Mock
from src.server.fastapi_app import app

View File

@@ -5,12 +5,13 @@ Tests database info, maintenance operations (vacuum, analyze, integrity-check,
reindex, optimize, stats), and storage management functionality.
"""
import pytest
import sys
import os
from fastapi.testclient import TestClient
import sys
from unittest.mock import patch
import pytest
from fastapi.testclient import TestClient
# Add source directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))

View File

@@ -4,11 +4,12 @@ Integration tests for diagnostics API endpoints.
This module tests the diagnostics endpoints for error reporting and system diagnostics.
"""
import os
import tempfile
from unittest.mock import Mock, patch
import pytest
from fastapi.testclient import TestClient
from unittest.mock import patch, Mock
import tempfile
import os
from src.server.fastapi_app import app

View File

@@ -5,12 +5,13 @@ Tests /health, /api/health/* endpoints including system metrics,
database health, dependencies, performance, and monitoring.
"""
import pytest
import sys
import os
from fastapi.testclient import TestClient
from unittest.mock import patch
import sys
from datetime import datetime
from unittest.mock import patch
import pytest
from fastapi.testclient import TestClient
# Add source directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))

View File

@@ -5,11 +5,12 @@ This module tests the integration endpoints for managing API keys, webhook confi
and third-party service integrations.
"""
import pytest
from fastapi.testclient import TestClient
from unittest.mock import patch, Mock
import json
import uuid
from unittest.mock import Mock, patch
import pytest
from fastapi.testclient import TestClient
from src.server.fastapi_app import app

View File

@@ -5,13 +5,14 @@ Tests configuration system integration, error handling pipelines,
and modular architecture component interactions.
"""
import pytest
import sys
import os
from unittest.mock import Mock
import json
import os
import sys
import tempfile
from pathlib import Path
from unittest.mock import Mock
import pytest
# Add source directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))

View File

@@ -5,10 +5,11 @@ This module tests the performance-related endpoints for speed limiting, cache ma
memory management, and download task handling.
"""
import time
from unittest.mock import Mock, patch
import pytest
from fastapi.testclient import TestClient
from unittest.mock import patch, Mock
import time
from src.server.fastapi_app import app

View File

@@ -5,9 +5,10 @@ This module tests the user preferences endpoints for theme management, language
accessibility settings, keyboard shortcuts, and UI density configurations.
"""
from unittest.mock import patch
import pytest
from fastapi.testclient import TestClient
from unittest.mock import patch
from src.server.fastapi_app import app

3
src/tests/simple_test.py Normal file
View File

@@ -0,0 +1,3 @@
from src.server.web.middleware.fastapi_auth_middleware_new import AuthMiddleware
print("Success importing AuthMiddleware")

14
src/tests/test_auth.ps1 Normal file
View File

@@ -0,0 +1,14 @@
$loginResponse = Invoke-WebRequest -Uri "http://127.0.0.1:8000/auth/login" -Method POST -ContentType "application/json" -Body '{"password": "admin123"}'
$loginData = $loginResponse.Content | ConvertFrom-Json
$token = $loginData.token
Write-Host "Token: $token"
# Test the anime search with authentication
$headers = @{
"Authorization" = "Bearer $token"
"Content-Type" = "application/json"
}
$searchResponse = Invoke-WebRequest -Uri "http://127.0.0.1:8000/api/anime/search?query=naruto" -Headers $headers
Write-Host "Search Response:"
Write-Host $searchResponse.Content

View File

@@ -0,0 +1,35 @@
# Test complete authentication flow
# Step 1: Login
Write-Host "=== Testing Login ==="
$loginResponse = Invoke-WebRequest -Uri "http://127.0.0.1:8000/auth/login" -Method POST -ContentType "application/json" -Body '{"password": "admin123"}'
$loginData = $loginResponse.Content | ConvertFrom-Json
$token = $loginData.token
Write-Host "Login successful. Token received: $($token.Substring(0,20))..."
# Step 2: Verify token
Write-Host "`n=== Testing Token Verification ==="
$headers = @{ "Authorization" = "Bearer $token" }
$verifyResponse = Invoke-WebRequest -Uri "http://127.0.0.1:8000/auth/verify" -Headers $headers
Write-Host "Token verification response: $($verifyResponse.Content)"
# Step 3: Test protected endpoint
Write-Host "`n=== Testing Protected Endpoint ==="
$authStatusResponse = Invoke-WebRequest -Uri "http://127.0.0.1:8000/api/auth/status" -Headers $headers
Write-Host "Auth status response: $($authStatusResponse.Content)"
# Step 4: Logout
Write-Host "`n=== Testing Logout ==="
$logoutResponse = Invoke-WebRequest -Uri "http://127.0.0.1:8000/auth/logout" -Method POST -Headers $headers
Write-Host "Logout response: $($logoutResponse.Content)"
# Step 5: Test expired/invalid token
Write-Host "`n=== Testing Invalid Token ==="
try {
$invalidResponse = Invoke-WebRequest -Uri "http://127.0.0.1:8000/auth/verify" -Headers @{ "Authorization" = "Bearer invalid_token" }
Write-Host "Invalid token response: $($invalidResponse.Content)"
} catch {
Write-Host "Invalid token correctly rejected: $($_.Exception.Message)"
}
Write-Host "`n=== Authentication Flow Test Complete ==="

View File

@@ -0,0 +1,17 @@
# Test database connectivity
# Get token
$loginResponse = Invoke-WebRequest -Uri "http://127.0.0.1:8000/auth/login" -Method POST -ContentType "application/json" -Body '{"password": "admin123"}'
$loginData = $loginResponse.Content | ConvertFrom-Json
$token = $loginData.token
# Test database health
$headers = @{ "Authorization" = "Bearer $token" }
$dbHealthResponse = Invoke-WebRequest -Uri "http://127.0.0.1:8000/api/system/database/health" -Headers $headers
Write-Host "Database Health Response:"
Write-Host $dbHealthResponse.Content
# Test system config
$configResponse = Invoke-WebRequest -Uri "http://127.0.0.1:8000/api/system/config" -Headers $headers
Write-Host "`nSystem Config Response:"
Write-Host $configResponse.Content

View File

@@ -0,0 +1,15 @@
import os
import sys
# Add parent directory to path
sys.path.insert(0, os.path.abspath('.'))
try:
from src.server.fastapi_app import app
print("✓ FastAPI app imported successfully")
except Exception as e:
print(f"✗ Error importing FastAPI app: {e}")
import traceback
traceback.print_exc()
print("Test completed.")

22
src/tests/test_imports.py Normal file
View File

@@ -0,0 +1,22 @@
#!/usr/bin/env python3
try:
from src.server.web.middleware.fastapi_auth_middleware import AuthMiddleware
print("Auth middleware imported successfully")
except Exception as e:
print(f"Error importing auth middleware: {e}")
try:
from src.server.web.middleware.fastapi_logging_middleware import (
EnhancedLoggingMiddleware,
)
print("Logging middleware imported successfully")
except Exception as e:
print(f"Error importing logging middleware: {e}")
try:
from src.server.web.middleware.fastapi_validation_middleware import (
ValidationMiddleware,
)
print("Validation middleware imported successfully")
except Exception as e:
print(f"Error importing validation middleware: {e}")

View File

@@ -5,9 +5,10 @@ Tests search algorithms, filtering functions, sorting mechanisms,
and data processing for anime and episode management.
"""
import pytest
import sys
import os
import sys
import pytest
# Add source directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))

View File

@@ -5,23 +5,24 @@ Tests password hashing, JWT creation/validation, session timeout logic,
and secure environment variable management.
"""
import pytest
import hashlib
import jwt
import sys
import os
import sys
from datetime import datetime, timedelta
from unittest.mock import patch
import jwt
import pytest
# Add source directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))
from src.server.fastapi_app import (
hash_password,
verify_master_password,
Settings,
generate_jwt_token,
hash_password,
verify_jwt_token,
Settings
verify_master_password,
)

View File

@@ -5,16 +5,21 @@ Tests CLI commands (scan, search, download, rescan, display series),
user input handling, and command-line interface logic.
"""
import pytest
import sys
import os
import sys
from unittest.mock import Mock, patch
import pytest
# Add source directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))
# Import after path setup
from src.cli.Main import SeriesApp, NoKeyFoundException, MatchNotFoundError # noqa: E402
from src.cli.Main import ( # noqa: E402
MatchNotFoundError,
NoKeyFoundException,
SeriesApp,
)
@pytest.fixture
@@ -409,7 +414,7 @@ class TestCLIEnvironmentVariables:
# Import and run main module simulation
import src.cli.Main
# The default should be the hardcoded path
default_path = "\\\\sshfs.r\\ubuntu@192.168.178.43\\media\\serien\\Serien"
result = os.getenv("ANIME_DIRECTORY", default_path)

View File

@@ -5,11 +5,12 @@ Tests database maintenance functions, storage optimization,
integrity checking, and database management utilities.
"""
import pytest
import sys
import os
import sys
from unittest.mock import Mock
import pytest
# Add source directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))

View File

@@ -5,12 +5,13 @@ This module tests the logging configuration, log file management,
and error reporting components.
"""
import pytest
import logging
import tempfile
import os
from unittest.mock import patch, Mock, mock_open
import tempfile
from pathlib import Path
from unittest.mock import Mock, mock_open, patch
import pytest
# Import logging components
try:
@@ -109,7 +110,7 @@ class TestLogFileManagement:
def test_log_file_rotation(self):
"""Test log file rotation functionality."""
from logging.handlers import RotatingFileHandler
# Create rotating file handler
handler = RotatingFileHandler(
self.log_file,

View File

@@ -5,12 +5,13 @@ Tests configuration loading, centralized error handling, module structure,
and architectural component integration.
"""
import pytest
import sys
import os
from unittest.mock import Mock, patch
import tempfile
import json
import os
import sys
import tempfile
from unittest.mock import Mock, patch
import pytest
# Add source directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))
@@ -131,10 +132,10 @@ class TestErrorHandling:
# Import custom exceptions if they exist
try:
from src.core.exceptions.Exceptions import ( # noqa: F401
MatchNotFoundError,
NoKeyFoundException,
MatchNotFoundError
)
# Test exception creation
key_error = NoKeyFoundException("Key not found")
assert str(key_error) == "Key not found"

View File

@@ -5,11 +5,12 @@ Tests CPU, memory, disk usage collection, performance monitoring,
and system health assessment logic.
"""
import pytest
import sys
import os
from unittest.mock import patch, Mock
import sys
from datetime import datetime
from unittest.mock import Mock, patch
import pytest
# Add source directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))