Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Django==2.2.28
elasticsearch_dsl==7.4.1
geonamescache==1.3.0
requests==2.22.0
requests==2.32.4
requests-aws4auth==0.9
mock==3.0.5
base32_crockford==0.3.0
Expand Down
Empty file added rorapi/middleware/__init__.py
Empty file.
33 changes: 33 additions & 0 deletions rorapi/middleware/deprecation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from django.http import JsonResponse
from django.conf import settings


class V1DeprecationMiddleware:
"""
Middleware to return 410 Gone status for deprecated v1 API endpoints.

This middleware checks if V1_DEPRECATED setting is enabled, and if so,
returns a 410 status code with a deprecation message for any requests
to /v1 endpoints.
"""

def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
# Check if v1 deprecation is enabled and path starts with /v1
if getattr(settings, 'V1_DEPRECATED', False):
if request.path.startswith('/v1/') or request.path == '/v1':
return JsonResponse(
{
'errors': [{
'status': '410',
'title': 'API Version Deprecated',
'detail': 'The v1 API has been deprecated. Please migrate to v2.'
}]
},
status=410
)

response = self.get_response(request)
return response
4 changes: 4 additions & 0 deletions rorapi/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
MIDDLEWARE = [
'django_prometheus.middleware.PrometheusBeforeMiddleware',
'corsheaders.middleware.CorsMiddleware',
'rorapi.middleware.deprecation.V1DeprecationMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
Expand Down Expand Up @@ -305,3 +306,6 @@
AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY')
AWS_SES_REGION_NAME = os.environ.get('AWS_REGION', 'eu-west-1')

# API Deprecation
V1_DEPRECATED = os.environ.get("V1_DEPRECATED", "False").lower() in ("true", "1", "yes")
131 changes: 131 additions & 0 deletions rorapi/tests/tests_unit/tests_deprecation_middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
from django.test import TestCase, RequestFactory, override_settings
from django.http import JsonResponse
from rorapi.middleware.deprecation import V1DeprecationMiddleware
import json


class V1DeprecationMiddlewareTestCase(TestCase):
"""
Tests for V1DeprecationMiddleware that returns 410 Gone for deprecated v1 endpoints.
"""

def setUp(self):
self.factory = RequestFactory()

# Mock get_response function
def get_response(request):
return JsonResponse({'message': 'success'}, status=200)

self.get_response = get_response
self.middleware = V1DeprecationMiddleware(self.get_response)

@override_settings(V1_DEPRECATED=True)
def test_v1_path_returns_410_when_deprecated(self):
"""Test that /v1/ paths return 410 when V1_DEPRECATED is True"""
request = self.factory.get('/v1/organizations')
response = self.middleware(request)

self.assertEqual(response.status_code, 410)
content = json.loads(response.content.decode('utf-8'))
self.assertIn('errors', content)
self.assertEqual(content['errors'][0]['status'], '410')
self.assertEqual(content['errors'][0]['title'], 'API Version Deprecated')

@override_settings(V1_DEPRECATED=True)
def test_v1_exact_path_returns_410_when_deprecated(self):
"""Test that exact /v1 path returns 410 when V1_DEPRECATED is True"""
request = self.factory.get('/v1')
response = self.middleware(request)

self.assertEqual(response.status_code, 410)
content = json.loads(response.content.decode('utf-8'))
self.assertIn('errors', content)

@override_settings(V1_DEPRECATED=True)
def test_v2_path_passes_through_when_v1_deprecated(self):
"""Test that /v2/ paths work normally even when V1_DEPRECATED is True"""
request = self.factory.get('/v2/organizations')
response = self.middleware(request)

self.assertEqual(response.status_code, 200)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(content['message'], 'success')

@override_settings(V1_DEPRECATED=False)
def test_v1_path_passes_through_when_not_deprecated(self):
"""Test that /v1/ paths work normally when V1_DEPRECATED is False"""
request = self.factory.get('/v1/organizations')
response = self.middleware(request)

self.assertEqual(response.status_code, 200)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(content['message'], 'success')

@override_settings(V1_DEPRECATED=False)
def test_v2_path_passes_through_when_v1_not_deprecated(self):
"""Test that /v2/ paths work normally when V1_DEPRECATED is False"""
request = self.factory.get('/v2/organizations')
response = self.middleware(request)

self.assertEqual(response.status_code, 200)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(content['message'], 'success')

@override_settings(V1_DEPRECATED=None)
def test_v1_path_passes_through_when_setting_not_set(self):
"""Test that /v1/ paths work when V1_DEPRECATED setting doesn't exist"""
# Don't use override_settings, rely on default behavior
request = self.factory.get('/v1/organizations')
response = self.middleware(request)

self.assertEqual(response.status_code, 200)
content = json.loads(response.content.decode('utf-8'))
self.assertEqual(content['message'], 'success')

@override_settings(V1_DEPRECATED=True)
def test_root_path_passes_through(self):
"""Test that root path is not affected by middleware"""
request = self.factory.get('/')
response = self.middleware(request)

self.assertEqual(response.status_code, 200)

@override_settings(V1_DEPRECATED=True)
def test_other_paths_pass_through(self):
"""Test that non-v1 paths pass through normally"""
request = self.factory.get('/heartbeat')
response = self.middleware(request)

self.assertEqual(response.status_code, 200)

@override_settings(V1_DEPRECATED=True)
def test_v1_with_query_params_returns_410(self):
"""Test that /v1/ paths with query parameters return 410"""
request = self.factory.get('/v1/organizations?query=test')
response = self.middleware(request)

self.assertEqual(response.status_code, 410)

@override_settings(V1_DEPRECATED=True)
def test_v1_post_request_returns_410(self):
"""Test that POST requests to /v1/ paths return 410"""
request = self.factory.post('/v1/organizations')
response = self.middleware(request)

self.assertEqual(response.status_code, 410)

@override_settings(V1_DEPRECATED=True)
def test_deprecation_error_message_format(self):
"""Test that the deprecation error message follows the expected format"""
request = self.factory.get('/v1/organizations')
response = self.middleware(request)

content = json.loads(response.content.decode('utf-8'))
self.assertIn('errors', content)
self.assertEqual(len(content['errors']), 1)

error = content['errors'][0]
self.assertIn('status', error)
self.assertIn('title', error)
self.assertIn('detail', error)
self.assertIn('migrate to v2', error['detail'])
Loading