Commit 8e35484f by Clinton Blackburn Committed by Clinton Blackburn

Added ability to execute the refresh_course_metadata management command via HTTP

ECOM-4204
parent a2fdb164
import mock
from django.core.urlresolvers import reverse
from rest_framework.test import APITestCase
from course_discovery.apps.core.tests.factories import UserFactory
class RefreshCourseMetadataTests(APITestCase):
""" Tests for the refresh_course_metadata management endpoint. """
path = reverse('api:v1:management-refresh-course-metadata')
call_command_path = 'course_discovery.apps.api.v1.views.call_command'
def setUp(self):
super(RefreshCourseMetadataTests, self).setUp()
self.superuser = UserFactory(is_superuser=True)
self.client.force_authenticate(self.superuser) # pylint: disable=no-member
def assert_access_forbidden(self):
""" Asserts that a call to the endpoint fails with HTTP status 403. """
response = self.client.post(self.path)
self.assertEqual(response.status_code, 403)
def test_superuser_required(self):
""" Verify only superusers can access the endpoint. """
with mock.patch(self.call_command_path, return_value=None):
response = self.client.post(self.path)
self.assertEqual(response.status_code, 200)
# Anonymous user
self.client.logout()
self.assert_access_forbidden()
# Normal and staff users
users = (UserFactory(), UserFactory(is_staff=True),)
for user in users:
self.client.force_authenticate(user) # pylint: disable=no-member
self.assert_access_forbidden()
def test_success_response(self):
""" Verify a successful response calls the management command and returns the plain text output. """
self.assert_successful_response()
self.assert_successful_response('abc123')
def assert_successful_response(self, access_token=None):
""" Asserts the endpoint called the refresh_course_metadata management command with the correct arguments,
and the endpoint returns HTTP 200 with text/plain content type. """
data = {'access_token': access_token} if access_token else None
with mock.patch(self.call_command_path, return_value=None) as mocked_call_command:
response = self.client.post(self.path, data)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content_type, 'text/plain')
args, kwargs = mocked_call_command.call_args
expected = {
'settings': 'course_discovery.settings.test'
}
if access_token:
expected['access_token'] = access_token
self.assertTrue(mocked_call_command.called)
self.assertEqual(args[0], 'refresh_course_metadata')
self.assertDictContainsSubset(expected, kwargs)
...@@ -9,5 +9,6 @@ router = routers.SimpleRouter() ...@@ -9,5 +9,6 @@ router = routers.SimpleRouter()
router.register(r'catalogs', views.CatalogViewSet) router.register(r'catalogs', views.CatalogViewSet)
router.register(r'courses', views.CourseViewSet, base_name='course') router.register(r'courses', views.CourseViewSet, base_name='course')
router.register(r'course_runs', views.CourseRunViewSet, base_name='course_run') router.register(r'course_runs', views.CourseRunViewSet, base_name='course_run')
router.register(r'management', views.ManagementViewSet, base_name='management')
urlpatterns += router.urls urlpatterns += router.urls
import logging import logging
import os
from io import StringIO
from django.core.management import call_command
from django.db.models.functions import Lower from django.db.models.functions import Lower
from dry_rest_permissions.generics import DRYPermissions from dry_rest_permissions.generics import DRYPermissions
from edx_rest_framework_extensions.permissions import IsSuperuser
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.decorators import detail_route from rest_framework.decorators import detail_route, list_route
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
...@@ -156,3 +160,45 @@ class CourseRunViewSet(viewsets.ReadOnlyModelViewSet): ...@@ -156,3 +160,45 @@ class CourseRunViewSet(viewsets.ReadOnlyModelViewSet):
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
""" Retrieve details for a course run. """ """ Retrieve details for a course run. """
return super(CourseRunViewSet, self).retrieve(request, *args, **kwargs) return super(CourseRunViewSet, self).retrieve(request, *args, **kwargs)
class ManagementViewSet(viewsets.ViewSet):
permission_classes = (IsSuperuser,)
@list_route(methods=['post'])
def refresh_course_metadata(self, request):
""" Refresh the course metadata from external data sources.
---
parameters:
- name: access_token
description: OAuth access token to use in lieu of that issued to the service.
required: false
type: string
paramType: form
multiple: false
"""
access_token = request.data.get('access_token')
# Capture all output and logging
out = StringIO()
err = StringIO()
log = StringIO()
root_logger = logging.getLogger()
log_handler = logging.StreamHandler(log)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
log_handler.setFormatter(formatter)
root_logger.addHandler(log_handler)
logger.info('Updating course metadata per request of [%s]...', request.user.username)
kwargs = {'access_token': access_token} if access_token else {}
call_command('refresh_course_metadata', settings=os.environ['DJANGO_SETTINGS_MODULE'], stdout=out, stderr=err,
**kwargs)
# Format the output for display
output = 'STDOUT\n{out}\n\nSTDERR\n{err}\n\nLOG\n{log}'.format(out=out.getvalue(), err=err.getvalue(),
log=log.getvalue())
return Response(output, content_type='text/plain')
...@@ -7,11 +7,11 @@ django-sortedm2m==1.1.1 ...@@ -7,11 +7,11 @@ django-sortedm2m==1.1.1
django-waffle==0.11 django-waffle==0.11
djangorestframework==3.3.3 djangorestframework==3.3.3
djangorestframework-jwt==1.7.2 djangorestframework-jwt==1.7.2
django-rest-swagger[reST]==0.3.4 django-rest-swagger[reST]==0.3.5
dry-rest-permissions==0.1.6 dry-rest-permissions==0.1.6
edx-auth-backends==0.1.3 edx-auth-backends==0.1.3
edx-ccx-keys==0.2.0 edx-ccx-keys==0.2.0
edx-drf-extensions==0.2.0 edx-drf-extensions==0.3.0
edx-opaque-keys==0.3.0 edx-opaque-keys==0.3.0
edx-rest-api-client==1.5.0 edx-rest-api-client==1.5.0
elasticsearch>=1.0.0,<2.0.0 elasticsearch>=1.0.0,<2.0.0
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment