- Redesigned frontend with Outfit/Figtree typography, coral accent palette, noise texture, glassmorphism header, and staggered animations - Split monolithic index.html into modular JS (app, api, upload, batch, results, page-viewer, utils) and extracted CSS - Fixed worker.py to generate page images for Visual Page Inspector - Added Docker Compose stack (web, worker, redis, postgres) - Added batch upload, HTML report export, rate limiting, and Redis queue - Extended test suite with checker, remediation, worker, and DB tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
187 lines
5.9 KiB
Python
187 lines
5.9 KiB
Python
"""
|
|
Integration tests for API (api.php)
|
|
"""
|
|
|
|
import pytest
|
|
import subprocess
|
|
import time
|
|
import requests
|
|
from pathlib import Path
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def php_server():
|
|
"""Start PHP development server for testing"""
|
|
# Start PHP server on a test port
|
|
port = 8888
|
|
env = {**subprocess.os.environ, 'DEV_MODE': 'true'}
|
|
process = subprocess.Popen(
|
|
["php", "-S", f"localhost:{port}"],
|
|
cwd=Path(__file__).parent.parent,
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
env=env
|
|
)
|
|
|
|
# Wait for server to start
|
|
time.sleep(2)
|
|
|
|
# Check if server is running
|
|
try:
|
|
requests.get(f"http://localhost:{port}/", timeout=5)
|
|
except requests.RequestException:
|
|
process.terminate()
|
|
pytest.skip("Could not start PHP server")
|
|
|
|
yield f"http://localhost:{port}"
|
|
|
|
# Cleanup
|
|
process.terminate()
|
|
time.sleep(1)
|
|
|
|
|
|
class TestAPIAuthentication:
|
|
"""Test API authentication"""
|
|
|
|
def test_api_rejects_no_auth(self, php_server):
|
|
"""Test that API handles requests without authentication"""
|
|
response = requests.get(f"{php_server}/api.php")
|
|
|
|
# In dev mode (DEV_MODE=true), auth is bypassed so we get 400 (invalid action)
|
|
# In production mode, we would get 401
|
|
assert response.status_code in [400, 401]
|
|
data = response.json()
|
|
assert data['success'] is False
|
|
assert 'error' in data
|
|
|
|
def test_api_accepts_valid_key(self, php_server):
|
|
"""Test that API accepts requests with valid dev key"""
|
|
headers = {'X-API-Key': 'dev_key_12345'}
|
|
response = requests.get(f"{php_server}/api.php", headers=headers)
|
|
|
|
# Should return 200 and different error (invalid action, not auth error)
|
|
assert response.status_code != 401
|
|
data = response.json()
|
|
|
|
# Should get past authentication
|
|
if 'error' in data:
|
|
assert 'Unauthorized' not in data['error']
|
|
assert 'API key' not in data['error']
|
|
|
|
def test_api_accepts_bearer_token(self, php_server):
|
|
"""Test that API accepts Bearer token authentication"""
|
|
headers = {'Authorization': 'Bearer dev_key_12345'}
|
|
response = requests.get(f"{php_server}/api.php", headers=headers)
|
|
|
|
# Should get past authentication
|
|
assert response.status_code != 401
|
|
|
|
|
|
class TestAuthModule:
|
|
"""Test authentication module directly"""
|
|
|
|
def test_auth_key_generation(self, php_server):
|
|
"""Test API key generation endpoint"""
|
|
response = requests.get(f"{php_server}/auth.php?generate")
|
|
|
|
assert response.status_code == 200
|
|
text = response.text
|
|
|
|
# Should contain a generated key
|
|
assert len(text) > 50 # Keys are 64 chars hex
|
|
assert 'API Key' in text or 'New' in text
|
|
|
|
def test_auth_test_endpoint(self, php_server):
|
|
"""Test authentication test endpoint"""
|
|
headers = {'X-API-Key': 'dev_key_12345'}
|
|
response = requests.get(f"{php_server}/auth.php?test", headers=headers)
|
|
|
|
assert response.status_code == 200
|
|
text = response.text
|
|
|
|
# Should indicate successful authentication
|
|
assert '✅' in text or 'successful' in text.lower()
|
|
|
|
|
|
class TestAPIEndpoints:
|
|
"""Test API endpoint structure"""
|
|
|
|
def test_api_returns_json(self, php_server):
|
|
"""Test that API returns JSON"""
|
|
headers = {'X-API-Key': 'dev_key_12345'}
|
|
response = requests.get(f"{php_server}/api.php", headers=headers)
|
|
|
|
assert response.headers.get('Content-Type') == 'application/json'
|
|
|
|
# Should be valid JSON
|
|
try:
|
|
data = response.json()
|
|
assert isinstance(data, dict)
|
|
except ValueError:
|
|
pytest.fail("API did not return valid JSON")
|
|
|
|
def test_cors_headers_present(self, php_server):
|
|
"""Test that CORS headers are present"""
|
|
headers = {'X-API-Key': 'dev_key_12345'}
|
|
response = requests.get(f"{php_server}/api.php", headers=headers)
|
|
|
|
assert 'Access-Control-Allow-Origin' in response.headers
|
|
# CORS now returns specific origin or localhost in dev mode
|
|
origin = response.headers['Access-Control-Allow-Origin']
|
|
assert origin in ['*', 'https://ai-sandbox.oliver.solutions', 'http://localhost:8888', 'http://localhost:8000', 'null']
|
|
|
|
def test_api_handles_options(self, php_server):
|
|
"""Test that API handles OPTIONS preflight requests"""
|
|
response = requests.options(f"{php_server}/api.php")
|
|
|
|
# OPTIONS should not require authentication
|
|
assert response.status_code == 200 or response.status_code == 204
|
|
|
|
|
|
class TestHelperModules:
|
|
"""Test helper modules"""
|
|
|
|
def test_logger_config_import(self):
|
|
"""Test logger_config module"""
|
|
from logger_config import setup_logger
|
|
|
|
logger = setup_logger("test", "test_api.log")
|
|
assert logger is not None
|
|
|
|
# Test logging
|
|
logger.info("Test message from API tests")
|
|
|
|
def test_retry_helper_import(self):
|
|
"""Test retry_helper module"""
|
|
from retry_helper import retry_with_backoff, safe_execute
|
|
|
|
assert callable(retry_with_backoff)
|
|
assert callable(safe_execute)
|
|
|
|
def test_retry_decorator_works(self):
|
|
"""Test that retry decorator functions"""
|
|
from retry_helper import retry_with_backoff
|
|
|
|
@retry_with_backoff(max_retries=2, initial_delay=0.1)
|
|
def always_succeeds():
|
|
return "success"
|
|
|
|
result = always_succeeds()
|
|
assert result == "success"
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
not Path("Test_files/sample_good.pdf").exists(),
|
|
reason="Sample PDF not available"
|
|
)
|
|
class TestAPIWithFile:
|
|
"""Test API with actual file upload (if samples available)"""
|
|
|
|
def test_api_file_structure_exists(self):
|
|
"""Test that test files exist"""
|
|
assert Path("Test_files").exists()
|
|
assert Path("Test_files").is_dir()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main([__file__, "-v"])
|