|
|
"""Integration tests for backwards compatibility.""" |
|
|
import pytest |
|
|
from fastapi.testclient import TestClient |
|
|
from unittest.mock import Mock, patch, AsyncMock |
|
|
|
|
|
from main import app |
|
|
|
|
|
|
|
|
class TestBackwardsCompatibility: |
|
|
"""Test that existing clients continue to work.""" |
|
|
|
|
|
def setup_method(self): |
|
|
"""Set up test fixtures.""" |
|
|
self.client = TestClient(app) |
|
|
|
|
|
def test_info_endpoint_remains_public(self): |
|
|
"""Test that info endpoint doesn't require authentication.""" |
|
|
response = self.client.get("/api/v1/info") |
|
|
|
|
|
assert response.status_code == 200 |
|
|
response_data = response.json() |
|
|
|
|
|
|
|
|
assert "version" in response_data |
|
|
assert "supported_video_formats" in response_data |
|
|
assert "supported_audio_formats" in response_data |
|
|
assert "quality_levels" in response_data |
|
|
assert "endpoints" in response_data |
|
|
|
|
|
|
|
|
assert ".mp4" in response_data["supported_video_formats"] |
|
|
assert "mp3" in response_data["supported_audio_formats"] |
|
|
assert "medium" in response_data["quality_levels"] |
|
|
|
|
|
def test_health_endpoint_if_exists(self): |
|
|
"""Test health endpoint doesn't require authentication if it exists.""" |
|
|
response = self.client.get("/api/v1/health") |
|
|
|
|
|
|
|
|
if response.status_code != 404: |
|
|
assert response.status_code in [200, 204] |
|
|
|
|
|
def test_root_endpoint_behavior(self): |
|
|
"""Test that root endpoint behavior is preserved.""" |
|
|
response = self.client.get("/") |
|
|
|
|
|
|
|
|
|
|
|
assert response.status_code in [200, 301, 302, 404] |
|
|
assert response.status_code != 401 |
|
|
|
|
|
def test_protected_endpoints_now_require_auth(self): |
|
|
"""Test that previously unprotected endpoints now require authentication.""" |
|
|
protected_endpoints = [ |
|
|
("GET", "/api/v1/jobs/test-job-id"), |
|
|
("GET", "/api/v1/jobs/test-job-id/download"), |
|
|
("POST", "/api/v1/extract"), |
|
|
] |
|
|
|
|
|
for method, endpoint in protected_endpoints: |
|
|
if method == "GET": |
|
|
response = self.client.get(endpoint) |
|
|
elif method == "POST": |
|
|
|
|
|
response = self.client.post( |
|
|
endpoint, |
|
|
files={"video": ("test.mp4", b"fake content", "video/mp4")}, |
|
|
data={"output_format": "mp3", "quality": "medium"} |
|
|
) |
|
|
|
|
|
assert response.status_code == 401 |
|
|
assert "Authorization" in response.json()["error"] or \ |
|
|
"authentication" in response.json()["error"].lower() |
|
|
|
|
|
def test_error_response_format_compatibility(self): |
|
|
"""Test that error response format is compatible with existing clients.""" |
|
|
|
|
|
response = self.client.get("/api/v1/jobs/test-job") |
|
|
|
|
|
assert response.status_code == 401 |
|
|
response_data = response.json() |
|
|
|
|
|
|
|
|
assert "error" in response_data |
|
|
assert isinstance(response_data["error"], str) |
|
|
|
|
|
|
|
|
if "code" in response_data: |
|
|
assert isinstance(response_data["code"], str) |
|
|
if "details" in response_data: |
|
|
assert isinstance(response_data["details"], str) |
|
|
|
|
|
def test_form_parameter_names_unchanged(self): |
|
|
"""Test that form parameter names haven't changed.""" |
|
|
|
|
|
test_file_content = b"fake video content" |
|
|
|
|
|
response = self.client.post( |
|
|
"/api/v1/extract", |
|
|
files={"video": ("test.mp4", test_file_content, "video/mp4")}, |
|
|
data={ |
|
|
"output_format": "mp3", |
|
|
"quality": "medium", |
|
|
|
|
|
} |
|
|
) |
|
|
|
|
|
|
|
|
assert response.status_code == 401 |
|
|
assert "authorization" in response.json()["error"].lower() |
|
|
|
|
|
def test_existing_response_fields_preserved(self): |
|
|
"""Test that existing response fields are preserved when using new auth.""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
expected_job_response_fields = [ |
|
|
"job_id", "status", "message", "check_url", "file_size_mb" |
|
|
] |
|
|
|
|
|
expected_status_response_fields = [ |
|
|
"job_id", "status", "created_at", "updated_at", "filename", |
|
|
"file_size_mb", "output_format", "quality" |
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
assert len(expected_job_response_fields) > 0 |
|
|
assert len(expected_status_response_fields) > 0 |
|
|
|
|
|
|
|
|
class TestLegacyClientCompatibility: |
|
|
"""Test compatibility with legacy client patterns.""" |
|
|
|
|
|
def setup_method(self): |
|
|
"""Set up test fixtures.""" |
|
|
self.client = TestClient(app) |
|
|
|
|
|
def test_content_type_handling_unchanged(self): |
|
|
"""Test that content type handling for uploads hasn't changed.""" |
|
|
test_file_content = b"fake video content" |
|
|
|
|
|
|
|
|
response = self.client.post( |
|
|
"/api/v1/extract", |
|
|
files={"video": ("test.mp4", test_file_content, "video/mp4")}, |
|
|
data={"output_format": "mp3", "quality": "medium"} |
|
|
) |
|
|
|
|
|
|
|
|
assert response.status_code == 401 |
|
|
assert "content-type" not in response.json()["error"].lower() |
|
|
|
|
|
def test_http_methods_unchanged(self): |
|
|
"""Test that HTTP methods for endpoints haven't changed.""" |
|
|
endpoint_methods = [ |
|
|
("/api/v1/info", "GET"), |
|
|
("/api/v1/extract", "POST"), |
|
|
("/api/v1/jobs/test-job", "GET"), |
|
|
("/api/v1/jobs/test-job/download", "GET"), |
|
|
] |
|
|
|
|
|
for endpoint, expected_method in endpoint_methods: |
|
|
if expected_method == "GET": |
|
|
response = self.client.get(endpoint) |
|
|
elif expected_method == "POST": |
|
|
response = self.client.post( |
|
|
endpoint, |
|
|
files={"video": ("test.mp4", b"fake", "video/mp4")}, |
|
|
data={"output_format": "mp3"} |
|
|
) |
|
|
|
|
|
|
|
|
assert response.status_code != 405 |
|
|
|
|
|
def test_url_paths_unchanged(self): |
|
|
"""Test that URL paths haven't changed.""" |
|
|
|
|
|
old_urls = [ |
|
|
"/api/v1/info", |
|
|
"/api/v1/extract", |
|
|
"/api/v1/jobs/test-job-id", |
|
|
"/api/v1/jobs/test-job-id/download", |
|
|
] |
|
|
|
|
|
for url in old_urls: |
|
|
response = self.client.get(url) |
|
|
|
|
|
|
|
|
assert response.status_code != 404 |
|
|
|
|
|
def test_supported_formats_unchanged(self): |
|
|
"""Test that supported video and audio formats haven't changed.""" |
|
|
response = self.client.get("/api/v1/info") |
|
|
|
|
|
assert response.status_code == 200 |
|
|
info = response.json() |
|
|
|
|
|
|
|
|
expected_video_formats = [".mp4", ".avi", ".mov"] |
|
|
expected_audio_formats = ["mp3", "aac", "wav"] |
|
|
|
|
|
for fmt in expected_video_formats: |
|
|
assert fmt in info["supported_video_formats"] |
|
|
|
|
|
for fmt in expected_audio_formats: |
|
|
assert fmt in info["supported_audio_formats"] |
|
|
|
|
|
def test_quality_levels_unchanged(self): |
|
|
"""Test that quality levels haven't changed.""" |
|
|
response = self.client.get("/api/v1/info") |
|
|
|
|
|
assert response.status_code == 200 |
|
|
info = response.json() |
|
|
|
|
|
expected_quality_levels = ["high", "medium", "low"] |
|
|
|
|
|
for quality in expected_quality_levels: |
|
|
assert quality in info["quality_levels"] |
|
|
|
|
|
|
|
|
class TestMigrationPath: |
|
|
"""Test migration path for existing clients.""" |
|
|
|
|
|
def setup_method(self): |
|
|
"""Set up test fixtures.""" |
|
|
self.client = TestClient(app) |
|
|
|
|
|
def test_clear_authentication_error_messages(self): |
|
|
"""Test that authentication errors provide clear migration guidance.""" |
|
|
response = self.client.get("/api/v1/jobs/test-job") |
|
|
|
|
|
assert response.status_code == 401 |
|
|
error_msg = response.json()["error"] |
|
|
|
|
|
|
|
|
assert "authorization" in error_msg.lower() or "bearer" in error_msg.lower() |
|
|
|
|
|
|
|
|
assert "WWW-Authenticate" in response.headers |
|
|
assert response.headers["WWW-Authenticate"] == "Bearer" |
|
|
|
|
|
def test_gradual_migration_support(self): |
|
|
"""Test that clients can gradually migrate to new features.""" |
|
|
|
|
|
test_file_content = b"fake video content" |
|
|
|
|
|
|
|
|
response = self.client.post( |
|
|
"/api/v1/extract", |
|
|
files={"video": ("test.mp4", test_file_content, "video/mp4")}, |
|
|
data={ |
|
|
"output_format": "mp3", |
|
|
"quality": "medium" |
|
|
|
|
|
} |
|
|
) |
|
|
|
|
|
|
|
|
assert response.status_code == 401 |
|
|
assert "job_id" not in response.json()["error"].lower() |
|
|
|
|
|
def test_response_structure_backwards_compatibility(self): |
|
|
"""Test that response structures are backwards compatible.""" |
|
|
|
|
|
|
|
|
|
|
|
response = self.client.get("/api/v1/info") |
|
|
info = response.json() |
|
|
|
|
|
|
|
|
required_fields = ["version", "supported_video_formats", "supported_audio_formats"] |
|
|
|
|
|
for field in required_fields: |
|
|
assert field in info, f"Required field '{field}' missing from info response" |
|
|
|
|
|
def test_error_codes_are_new_additions(self): |
|
|
"""Test that error codes are new additions, not changes to existing behavior.""" |
|
|
response = self.client.get("/api/v1/jobs/nonexistent") |
|
|
|
|
|
assert response.status_code == 401 |
|
|
|
|
|
|
|
|
response_data = response.json() |
|
|
if "code" in response_data: |
|
|
|
|
|
assert isinstance(response_data["code"], str) |
|
|
assert len(response_data["code"]) > 0 |
|
|
|
|
|
|
|
|
class TestDocumentationCompatibility: |
|
|
"""Test that API documentation remains compatible.""" |
|
|
|
|
|
def setup_method(self): |
|
|
"""Set up test fixtures.""" |
|
|
self.client = TestClient(app) |
|
|
|
|
|
def test_openapi_schema_accessible(self): |
|
|
"""Test that OpenAPI schema is still accessible.""" |
|
|
response = self.client.get("/openapi.json") |
|
|
|
|
|
assert response.status_code == 200 |
|
|
schema = response.json() |
|
|
|
|
|
|
|
|
assert "openapi" in schema |
|
|
assert "paths" in schema |
|
|
assert "info" in schema |
|
|
|
|
|
def test_api_endpoints_documented(self): |
|
|
"""Test that API endpoints are documented.""" |
|
|
response = self.client.get("/openapi.json") |
|
|
schema = response.json() |
|
|
|
|
|
expected_paths = [ |
|
|
"/api/v1/info", |
|
|
"/api/v1/extract", |
|
|
"/api/v1/jobs/{job_id}", |
|
|
"/api/v1/jobs/{job_id}/download" |
|
|
] |
|
|
|
|
|
for path in expected_paths: |
|
|
assert path in schema["paths"], f"Path {path} not documented in OpenAPI schema" |
|
|
|
|
|
def test_security_requirements_documented(self): |
|
|
"""Test that security requirements are properly documented.""" |
|
|
response = self.client.get("/openapi.json") |
|
|
schema = response.json() |
|
|
|
|
|
|
|
|
if "components" in schema and "securitySchemes" in schema["components"]: |
|
|
|
|
|
security_schemes = schema["components"]["securitySchemes"] |
|
|
|
|
|
|
|
|
bearer_scheme_found = False |
|
|
for scheme_name, scheme_def in security_schemes.items(): |
|
|
if scheme_def.get("type") == "http" and scheme_def.get("scheme") == "bearer": |
|
|
bearer_scheme_found = True |
|
|
break |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestPerformanceCompatibility: |
|
|
"""Test that performance characteristics haven't degraded significantly.""" |
|
|
|
|
|
def setup_method(self): |
|
|
"""Set up test fixtures.""" |
|
|
self.client = TestClient(app) |
|
|
|
|
|
def test_public_endpoint_performance_unchanged(self): |
|
|
"""Test that public endpoints maintain good performance.""" |
|
|
import time |
|
|
|
|
|
|
|
|
start_time = time.time() |
|
|
response = self.client.get("/api/v1/info") |
|
|
end_time = time.time() |
|
|
|
|
|
assert response.status_code == 200 |
|
|
|
|
|
|
|
|
response_time = end_time - start_time |
|
|
assert response_time < 1.0, f"Info endpoint took {response_time:.2f}s, expected < 1.0s" |
|
|
|
|
|
def test_auth_validation_performance(self): |
|
|
"""Test that authentication validation doesn't add significant overhead.""" |
|
|
import time |
|
|
|
|
|
|
|
|
start_time = time.time() |
|
|
response = self.client.get("/api/v1/jobs/test-job") |
|
|
end_time = time.time() |
|
|
|
|
|
assert response.status_code == 401 |
|
|
|
|
|
|
|
|
response_time = end_time - start_time |
|
|
assert response_time < 0.5, f"Auth validation took {response_time:.2f}s, expected < 0.5s" |
|
|
|
|
|
def test_multiple_request_performance(self): |
|
|
"""Test that authentication doesn't degrade under multiple requests.""" |
|
|
import time |
|
|
|
|
|
start_time = time.time() |
|
|
|
|
|
|
|
|
for i in range(10): |
|
|
response = self.client.get(f"/api/v1/jobs/test-job-{i}") |
|
|
assert response.status_code == 401 |
|
|
|
|
|
end_time = time.time() |
|
|
|
|
|
|
|
|
total_time = end_time - start_time |
|
|
avg_time = total_time / 10 |
|
|
|
|
|
assert avg_time < 0.1, f"Average request time {avg_time:.3f}s, expected < 0.1s" |