Files
mve-micro-docs/tests/test_api.py

236 lines
7.6 KiB
Python

"""Tests for API endpoints."""
import pytest
from io import BytesIO
class TestHealthEndpoint:
"""Test health check endpoint."""
def test_health_check(self, client):
"""Test health endpoint returns correct status."""
response = client.get("/health")
assert response.status_code == 200
data = response.json()
assert data["status"] == "ok"
assert data["service"] == "mve-incrementables-parser"
assert data["version"] == "1.0.0"
class TestAuthEndpoint:
"""Test authentication endpoints."""
def test_login_success(self, client):
"""Test successful login."""
response = client.post(
"/auth/login",
json={"username": "testuser", "password": "test_password_123"}
)
assert response.status_code == 200
data = response.json()
assert "access_token" in data
assert data["token_type"] == "bearer"
assert data["expires_in"] == 3600
def test_login_invalid_username(self, client):
"""Test login with invalid username."""
response = client.post(
"/auth/login",
json={"username": "wronguser", "password": "test_password_123"}
)
assert response.status_code == 401
data = response.json()
assert data["detail"] == "Invalid credentials"
def test_login_invalid_password(self, client):
"""Test login with invalid password."""
response = client.post(
"/auth/login",
json={"username": "testuser", "password": "wrongpassword"}
)
assert response.status_code == 401
data = response.json()
assert data["detail"] == "Invalid credentials"
def test_login_missing_fields(self, client):
"""Test login with missing fields."""
response = client.post(
"/auth/login",
json={"username": "testuser"}
)
assert response.status_code == 422 # Validation error
class TestParseEndpoint:
"""Test incrementables parse endpoint."""
def create_mock_pdf(self, content: str) -> BytesIO:
"""Create a simple mock PDF file for testing."""
# This is a minimal PDF structure
pdf_content = f"""%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R >>
endobj
4 0 obj
<< /Length {len(content)} >>
stream
{content}
endstream
endobj
xref
0 5
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000115 00000 n
0000000214 00000 n
trailer
<< /Size 5 /Root 1 0 R >>
startxref
{300 + len(content)}
%%EOF"""
return BytesIO(pdf_content.encode())
def test_parse_requires_auth(self, client):
"""Test parse endpoint requires authentication."""
files = {"file": ("test.pdf", BytesIO(b"fake pdf"), "application/pdf")}
response = client.post("/v1/incrementables/parse", files=files)
assert response.status_code == 403 # Forbidden without auth
def test_parse_invalid_file_type(self, client, auth_headers):
"""Test parse endpoint rejects non-PDF files."""
files = {"file": ("test.txt", BytesIO(b"not a pdf"), "text/plain")}
response = client.post(
"/v1/incrementables/parse",
files=files,
headers=auth_headers
)
assert response.status_code == 400
assert "PDF" in response.json()["detail"]
def test_parse_invalid_file_extension(self, client, auth_headers):
"""Test parse endpoint rejects files without .pdf extension."""
files = {"file": ("test.txt", BytesIO(b"content"), "application/pdf")}
response = client.post(
"/v1/incrementables/parse",
files=files,
headers=auth_headers
)
assert response.status_code == 400
assert "PDF" in response.json()["detail"]
def test_parse_with_correlation_id(self, client, auth_headers):
"""Test parse endpoint accepts correlation ID."""
pdf_content = self.create_mock_pdf("test content")
files = {"file": ("test.pdf", pdf_content, "application/pdf")}
headers = {**auth_headers, "X-Correlation-Id": "test-correlation-123"}
response = client.post(
"/v1/incrementables/parse",
files=files,
headers=headers
)
# Will fail at extraction/parsing stage, but should accept the correlation ID
# Either 422 (parsing failed) or 500 (extraction failed)
assert response.status_code in [422, 500]
def test_parse_with_document_ref(self, client, auth_headers):
"""Test parse endpoint accepts document reference."""
pdf_content = self.create_mock_pdf("test content")
files = {"file": ("test.pdf", pdf_content, "application/pdf")}
data = {"document_ref": "REF-12345"}
response = client.post(
"/v1/incrementables/parse",
files=files,
data=data,
headers=auth_headers
)
# Will fail at extraction/parsing stage
assert response.status_code in [422, 500]
def test_parse_generates_correlation_id(self, client, auth_headers):
"""Test parse endpoint generates correlation ID if not provided."""
pdf_content = self.create_mock_pdf("test content")
files = {"file": ("test.pdf", pdf_content, "application/pdf")}
response = client.post(
"/v1/incrementables/parse",
files=files,
headers=auth_headers
)
# Even on error, should have correlation_id in some responses
# This test verifies the endpoint is reachable
assert response.status_code in [422, 500]
class TestAuthIntegration:
"""Test authentication integration."""
def test_expired_token_rejected(self, client, test_settings):
"""Test that expired tokens are rejected."""
from app.core.security import create_access_token
from datetime import timedelta
# Create an already-expired token
expired_token = create_access_token(
data={"sub": "testuser"},
expires_delta=timedelta(seconds=-1)
)
headers = {"Authorization": f"Bearer {expired_token}"}
pdf_content = BytesIO(b"fake pdf")
files = {"file": ("test.pdf", pdf_content, "application/pdf")}
response = client.post(
"/v1/incrementables/parse",
files=files,
headers=headers
)
assert response.status_code == 401
def test_invalid_token_format(self, client):
"""Test that invalid token format is rejected."""
headers = {"Authorization": "Bearer invalid_token_xyz"}
pdf_content = BytesIO(b"fake pdf")
files = {"file": ("test.pdf", pdf_content, "application/pdf")}
response = client.post(
"/v1/incrementables/parse",
files=files,
headers=headers
)
assert response.status_code == 401
def test_missing_bearer_prefix(self, client, auth_token):
"""Test that token without Bearer prefix is rejected."""
headers = {"Authorization": auth_token}
pdf_content = BytesIO(b"fake pdf")
files = {"file": ("test.pdf", pdf_content, "application/pdf")}
response = client.post(
"/v1/incrementables/parse",
files=files,
headers=headers
)
assert response.status_code == 403