Download tests/integration/test_a11y_api_response_headers.py from techprotrade/annator-command-center: direct link, hf CLI and curl.
- Browser
- Download file 15.7 kB
-
https://huggingface.co/spaces/techprotrade/annator-command-center/resolve/main/tests/integration/test_a11y_api_response_headers.py
- Command line
-
hf download hf://spaces/techprotrade/annator-command-center/tests/integration/test_a11y_api_response_headers.py
-
curl -L -o test_a11y_api_response_headers.py https://huggingface.co/spaces/techprotrade/annator-command-center/resolve/main/tests/integration/test_a11y_api_response_headers.py
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 | |