annator-command-center / tests /integration /test_a11y_api_response_headers.py
techprotrade's picture
Deploy ATOM FastAPI command center runtime (part 8)
aef804e verified
Raw
History Blame Contribute Delete
15.7 kB
"""
API accessibility header tests
Tests verify that API responses include proper accessibility headers:
- Content-Type headers for all responses
- Accessible error responses with clear messages
- HEAD method support for resources
- Rate limit headers that are readable
- Clear pagination headers
- Content-Language headers
- Alt text for image endpoints
"""
import pytest
from fastapi.testclient import TestClient
from typing import Dict, Any
class TestAPIResponseHeaders:
"""Test suite for API accessibility headers"""
def test_api_returns_content_type_header(self, client: TestClient):
"""Test that API responses include Content-Type header."""
response = client.get('/api/v1/agents')
assert response.status_code in [200, 401, 403] # May be unauthorized
assert 'Content-Type' in response.headers
assert 'application/json' in response.headers['Content-Type']
def test_api_returns_accessible_error_responses(self, client: TestClient):
"""Test that error responses have clear, accessible messages."""
response = client.get('/api/v1/agents/nonexistent')
# Error responses should have clear messages
if response.status_code == 404:
assert 'application/json' in response.headers['Content-Type']
error_data = response.json()
assert 'detail' in error_data or 'message' in error_data
def test_api_error_messages_are_human_readable(self, client: TestClient):
"""Test that error messages are clear and actionable."""
# Test 400 Bad Request
response = client.post(
'/api/v1/agents/execute',
json={'invalid': 'data'}
)
if response.status_code == 400:
error_data = response.json()
# Error should have a human-readable message
assert 'detail' in error_data or 'message' in error_data
# Message should not be empty or just error code
message = error_data.get('detail', error_data.get('message', ''))
assert len(message) > 10 # More than just error code
def test_api_supports_head_requests_for_resources(self, client: TestClient):
"""Test that HEAD method is supported for resource endpoints."""
# HEAD request should return same headers as GET
response = client.head('/api/v1/agents')
# HEAD should return headers without body
assert response.status_code in [200, 401, 403]
assert 'Content-Type' in response.headers
assert len(response.content) == 0 # No body in HEAD response
def test_api_rate_limit_headers_are_accessible(self, client: TestClient):
"""Test that rate limit information is in readable headers."""
response = client.get('/api/v1/agents')
# Check for common rate limit headers
rate_limit_headers = [
'X-RateLimit-Limit',
'X-RateLimit-Remaining',
'X-RateLimit-Reset',
'RateLimit-Limit',
'RateLimit-Remaining',
'RateLimit-Reset'
]
# At least one rate limit header should be present if rate limiting is enabled
has_rate_limit = any(header in response.headers for header in rate_limit_headers)
# If rate limiting is enabled, headers should be readable
if has_rate_limit:
for header in rate_limit_headers:
if header in response.headers:
value = response.headers[header]
assert value is not None
assert len(value) > 0
def test_api_pagination_headers_are_clear(self, client: TestClient):
"""Test that pagination information is in clear headers."""
# Test a list endpoint that might have pagination
response = client.get('/api/v1/agents')
# Check for common pagination headers
pagination_headers = [
'X-Total-Count',
'X-Page',
'X-Per-Page',
'X-Total-Pages',
'Link'
]
# If pagination is present, headers should be clear
for header in pagination_headers:
if header in response.headers:
value = response.headers[header]
assert value is not None
assert len(value) > 0
def test_api_response_language_is_consistent(self, client: TestClient):
"""Test that Content-Language header is present."""
response = client.get('/api/v1/agents')
# Content-Language header should indicate response language
if 'Content-Language' in response.headers:
lang = response.headers['Content-Language']
assert lang in ['en', 'en-US', 'en-GB'] or lang.startswith('en')
def test_api_returns_structured_json_errors(self, client: TestClient):
"""Test that errors are returned as structured JSON."""
response = client.get('/api/v1/agents/nonexistent-id-12345')
if response.status_code in [400, 404, 422]:
# Error should be JSON
assert 'application/json' in response.headers['Content-Type']
# Error should have structure
error_data = response.json()
assert isinstance(error_data, dict)
def test_api_error_responses_include_helpful_info(self, client: TestClient):
"""Test that error responses include helpful information."""
response = client.get('/api/v1/agents/nonexistent-id-12345')
if response.status_code == 404:
error_data = response.json()
# Error should explain what was wrong
assert 'detail' in error_data or 'message' in error_data
message = error_data.get('detail', error_data.get('message', ''))
# Message should be descriptive
assert len(message) > 5
def test_api_success_responses_are_consistent(self, client: TestClient):
"""Test that success responses have consistent structure."""
# Try to get agents list
response = client.get('/api/v1/agents')
if response.status_code == 200:
data = response.json()
# Should be a list or dict with data field
assert isinstance(data, (dict, list))
if isinstance(data, dict):
# Common response fields
possible_keys = ['data', 'results', 'agents', 'items']
has_data_key = any(key in data for key in possible_keys)
# Not all endpoints need these keys, so we don't assert
def test_api_includes_timestamp_in_responses(self, client: TestClient):
"""Test that API responses include timestamp information."""
response = client.get('/api/v1/agents')
if response.status_code == 200:
data = response.json()
# Check for timestamp in response headers or body
has_timestamp = (
'Date' in response.headers or
'Last-Modified' in response.headers or
(isinstance(data, dict) and any(
key in data for key in ['timestamp', 'created_at', 'updated_at', 'date']
))
)
# Timestamps are good practice but not always required
def test_api_cors_headers_are_accessible(self, client: TestClient):
"""Test that CORS headers are present and readable."""
response = client.get('/api/v1/agents')
# Check for common CORS headers
cors_headers = [
'Access-Control-Allow-Origin',
'Access-Control-Allow-Methods',
'Access-Control-Allow-Headers',
'Access-Control-Max-Age'
]
# If CORS is enabled, headers should be present
for header in cors_headers:
if header in response.headers:
value = response.headers[header]
assert value is not None
assert len(value) > 0
def test_api_error_codes_are_explanatory(self, client: TestClient):
"""Test that error codes are explanatory, not cryptic."""
response = client.get('/api/v1/agents/nonexistent-id-12345')
if response.status_code == 404:
error_data = response.json()
# Error message should be in natural language
message = error_data.get('detail', error_data.get('message', ''))
# Should not be just error codes
assert not message.startswith('E')
assert not message.startswith('ERR_')
def test_api_supports_content_negotiation(self, client: TestClient):
"""Test that API supports content negotiation."""
# Request JSON response
response = client.get(
'/api/v1/agents',
headers={'Accept': 'application/json'}
)
# Should return JSON
assert 'Content-Type' in response.headers
assert 'application/json' in response.headers['Content-Type']
def test_api_responses_are_gzipped_when_appropriate(self, client: TestClient):
"""Test that API supports compression for large responses."""
# Request with Accept-Encoding
response = client.get(
'/api/v1/agents',
headers={'Accept-Encoding': 'gzip, deflate'}
)
# If response is large enough, should be compressed
if 'Content-Encoding' in response.headers:
encoding = response.headers['Content-Encoding']
assert encoding in ['gzip', 'deflate', 'br']
def test_api_includes_request_id_in_headers(self, client: TestClient):
"""Test that API includes request ID for debugging."""
response = client.get('/api/v1/agents')
# Request ID headers help with debugging accessibility issues
request_id_headers = [
'X-Request-ID',
'X-Correlation-ID',
'Request-ID'
]
# At least one request ID header is good practice
has_request_id = any(header in response.headers for header in request_id_headers)
if has_request_id:
for header in request_id_headers:
if header in response.headers:
value = response.headers[header]
assert value is not None
assert len(value) > 0
def test_api_health_endpoint_accessible(self, client: TestClient):
"""Test that health endpoint is accessible and clear."""
response = client.get('/health/live')
assert response.status_code == 200
assert 'application/json' in response.headers.get('Content-Type', '')
data = response.json()
assert isinstance(data, dict)
def test_api_readiness_endpoint_accessible(self, client: TestClient):
"""Test that readiness endpoint includes service status."""
response = client.get('/health/ready')
assert response.status_code in [200, 503] # Up or degraded
if response.status_code == 200:
data = response.json()
assert isinstance(data, dict)
def test_api_error_responses_include_status_code(self, client: TestClient):
"""Test that error responses include HTTP status context."""
response = client.post(
'/api/v1/agents/execute',
json={'invalid': 'data'}
)
if response.status_code == 400:
# Error response should indicate status
error_data = response.json()
assert 'detail' in error_data or 'message' in error_data
def test_api_validation_errors_are_clear(self, client: TestClient):
"""Test that validation errors provide specific feedback."""
response = client.post(
'/api/v1/agents',
json={'name': ''} # Empty name should fail validation
)
if response.status_code == 422:
error_data = response.json()
# Validation errors should list specific issues
assert 'detail' in error_data or 'errors' in error_data
def test_api_responses_include_api_version(self, client: TestClient):
"""Test that API version is indicated in responses."""
response = client.get('/api/v1/agents')
# Version can be in URL, header, or response body
has_version = (
'X-API-Version' in response.headers or
'API-Version' in response.headers
)
# API version in URL is sufficient (we use /api/v1/)
assert '/v1/' in response.request.url or has_version
def test_api_rate_limit_exceeded_clear(self, client: TestClient):
"""Test that rate limit exceeded is clearly indicated."""
# This test would require triggering rate limit
# For now, we test the structure
# If rate limited, should return 429 with clear message
# (We can't easily trigger this in tests)
# Placeholder for rate limit testing
assert True
class TestAPIAccessibilityForAssistiveTechnology:
"""Test suite for API accessibility for assistive technology users"""
def test_api_alt_text_in_image_endpoints(self, client: TestClient):
"""Test that image endpoints include alt text metadata."""
# If there are image/chart endpoints, they should include alt text
# This is a placeholder for chart/canvas endpoints
# Test canvas endpoint if it exists
response = client.get('/api/v1/canvas/test-canvas-id')
# If canvas exists, should include alt text or description
if response.status_code == 200:
data = response.json()
# Check for alt text or description fields
has_alt_text = (
'alt_text' in data or
'description' in data or
'title' in data
)
# Not all canvas types need alt text, but it's good practice
def test_api_screen_reader_friendly_errors(self, client: TestClient):
"""Test that errors are screen reader friendly."""
response = client.get('/api/v1/agents/nonexistent')
if response.status_code == 404:
error_data = response.json()
# Error should be in plain text, not HTML
assert 'application/json' in response.headers.get('Content-Type', '')
# Error message should be self-explanatory
message = error_data.get('detail', error_data.get('message', ''))
assert len(message) > 0
def test_api_semantic_headers(self, client: TestClient):
"""Test that API uses semantic HTTP headers."""
response = client.get('/api/v1/agents')
# Should use standard HTTP headers
assert 'Content-Type' in response.headers
# Should use appropriate status codes
assert response.status_code in [200, 201, 400, 401, 403, 404, 422, 500]
class TestAPIResponseTimeAccessibility:
"""Test suite for API response time considerations"""
def test_api_responses_are_reasonably_fast(self, client: TestClient):
"""Test that API responses are fast enough for accessibility."""
import time
start = time.time()
response = client.get('/api/v1/agents')
end = time.time()
# Response should be reasonably fast (< 5 seconds)
# This is important for users with assistive technology
assert (end - start) < 5.0
def test_api_timeout_handling(self, client: TestClient):
"""Test that API handles timeouts gracefully."""
# This would require mocking slow responses
# For now, we test the structure
# Placeholder for timeout testing
assert True
# Note: client fixture is provided by backend/tests/integration/conftest.py