diff --git a/ansible_base/api_documentation/urls.py b/ansible_base/api_documentation/urls.py index afbdbb838..42d91f263 100644 --- a/ansible_base/api_documentation/urls.py +++ b/ansible_base/api_documentation/urls.py @@ -2,10 +2,12 @@ from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView from ansible_base.api_documentation.apps import ApiDocumentationConfig +from ansible_base.api_documentation.views import DocsRootView app_name = ApiDocumentationConfig.label api_version_urls = [ - path('docs/schema/', SpectacularAPIView.as_view(), name='schema'), - path('docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), + path('docs/', DocsRootView.as_view(), name='docs-root'), + path('docs/swagger/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), path('docs/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'), + path('docs/schema/', SpectacularAPIView.as_view(), name='schema'), ] diff --git a/ansible_base/api_documentation/views.py b/ansible_base/api_documentation/views.py new file mode 100644 index 000000000..59199c77a --- /dev/null +++ b/ansible_base/api_documentation/views.py @@ -0,0 +1,19 @@ +from collections import OrderedDict + +from rest_framework import permissions +from rest_framework.response import Response + +from ansible_base.lib.utils.response import get_relative_url +from ansible_base.lib.utils.views.django_app_api import AnsibleBaseDjangoAppApiView + + +class DocsRootView(AnsibleBaseDjangoAppApiView): + permission_classes = [permissions.AllowAny] + + def get(self, request, format=None): + '''Index of API documentation endpoints''' + data = OrderedDict() + data['swagger'] = get_relative_url('swagger-ui') + data['redoc'] = get_relative_url('redoc') + data['schema'] = get_relative_url('schema') + return Response(data) diff --git a/docs/apps/api_documentation.md b/docs/apps/api_documentation.md index c484c1a3a..f11676041 100644 --- a/docs/apps/api_documentation.md +++ b/docs/apps/api_documentation.md @@ -54,6 +54,12 @@ see the instructions in the [x_ai_description_guide](./ai_documentation/x_ai_des This feature includes URLs which you will get if you are using [dynamic urls](../..//Installation.md) +The following endpoints are provided: +- `docs/` - Index page listing available documentation endpoints +- `docs/swagger/` - Swagger UI +- `docs/redoc/` - ReDoc UI +- `docs/schema/` - OpenAPI schema export + If you want to manually add the urls without dynamic urls add the following to your urls.py: ``` from ansible_base.api_documentation import urls diff --git a/test_app/tests/api_documentation/test_views.py b/test_app/tests/api_documentation/test_views.py new file mode 100644 index 000000000..fa1fff8f0 --- /dev/null +++ b/test_app/tests/api_documentation/test_views.py @@ -0,0 +1,46 @@ +""" +Tests for API documentation views. +""" + + +def test_docs_root_view_returns_index(unauthenticated_api_client): + """Test that the docs root endpoint returns an index of documentation endpoints.""" + url = '/api/v1/docs/' + response = unauthenticated_api_client.get(url) + + assert response.status_code == 200 + data = response.data + + # Verify all expected keys are present + assert 'swagger' in data + assert 'redoc' in data + assert 'schema' in data + + # Verify URLs point to the correct endpoints + assert '/docs/swagger/' in data['swagger'] + assert '/docs/redoc/' in data['redoc'] + assert '/docs/schema/' in data['schema'] + + +def test_docs_root_view_allows_unauthenticated_access(unauthenticated_api_client): + """Test that the docs root endpoint is accessible without authentication.""" + url = '/api/v1/docs/' + response = unauthenticated_api_client.get(url) + + assert response.status_code == 200 + + +def test_swagger_ui_accessible(unauthenticated_api_client): + """Test that Swagger UI is accessible at the new URL.""" + url = '/api/v1/docs/swagger/' + response = unauthenticated_api_client.get(url) + + assert response.status_code == 200 + + +def test_redoc_accessible(unauthenticated_api_client): + """Test that ReDoc is accessible.""" + url = '/api/v1/docs/redoc/' + response = unauthenticated_api_client.get(url) + + assert response.status_code == 200