Skip to content

Commit

Permalink
add transcript credentials api (#210)
Browse files Browse the repository at this point in the history
add transcript credentials api
  • Loading branch information
DawoudSheraz committed Apr 1, 2020
1 parent 2317310 commit dfe5c54
Show file tree
Hide file tree
Showing 16 changed files with 502 additions and 30 deletions.
19 changes: 18 additions & 1 deletion edxval/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,15 @@
EncodedVideo,
Profile,
ThirdPartyTranscriptCredentialsState,
TranscriptCredentials,
TranscriptPreference,
TranscriptProviderType,
Video,
VideoImage,
VideoTranscript,
)
from edxval.serializers import TranscriptPreferenceSerializer, TranscriptSerializer, VideoSerializer
from edxval.transcript_utils import Transcript
from edxval.transcript_utils import Transcript, validate_transcript_credentials
from edxval.utils import THIRD_PARTY_TRANSCRIPTION_PLANS, TranscriptFormat, create_file_in_fs, get_transcript_format

logger = logging.getLogger(__name__) # pylint: disable=C0103
Expand Down Expand Up @@ -1230,3 +1231,19 @@ def create_transcript_objects(xml, edx_video_id, resource_fs, static_dir, extern
resource_fs=file_system,
static_dir=static_dir
)


def create_or_update_transcript_credentials(**credentials):
"""
Internal API method to create or update transcript credentials.
"""
provider = credentials.pop('provider', None)
error_type, error_message, validated_credentials = validate_transcript_credentials(
provider=provider, **credentials
)
if not error_message:
TranscriptCredentials.objects.update_or_create(
org=validated_credentials.pop('org'), provider=provider, defaults=validated_credentials
)

return dict(error_type=error_type, message=error_message)
12 changes: 12 additions & 0 deletions edxval/enum.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""
Module containing all the Enumerations used in the API.
"""


class TranscriptionProviderErrorType:
"""
Transcription provider's errors enumeration.
"""
INVALID_CREDENTIALS = 1
INVALID_PROVIDER = 2
MISSING_REQUIRED_ATTRIBUTES = 3
8 changes: 8 additions & 0 deletions edxval/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,11 @@

# Set this value in the environment-specific files (e.g. local.py, production.py, test.py)
FERNET_KEYS = ['insecure-ferent-key']

# Transcript provider settings variables, which will be overridden at deployment if needed
# NOTE: These settings must be added in edx-platform settings to allow deployment override and val integration
CIELO24_SETTINGS = dict(
CIELO24_API_VERSION=1,
CIELO24_BASE_API_URL="https://sandbox.cielo24.com/api",
CIELO24_LOGIN_URL="https://sandbox.cielo24.com/api/account/login"
)
49 changes: 49 additions & 0 deletions edxval/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from tempfile import mkdtemp

import mock
import responses
import six
from ddt import data, ddt, unpack
from django.conf import settings
Expand Down Expand Up @@ -45,6 +46,7 @@
EncodedVideo,
Profile,
ThirdPartyTranscriptCredentialsState,
TranscriptCredentials,
TranscriptPreference,
TranscriptProviderType,
Video,
Expand Down Expand Up @@ -3073,3 +3075,50 @@ def test_get_credentials_state(self, org, provider, result):
"""
credentials_state = api.get_transcript_credentials_state_for_org(org=org, provider=provider)
self.assertEqual(credentials_state, result)


@ddt
class CreateUpdateTranscriptCredentialsTest(TestCase):
"""
Test Suite for transcript credentials create or update internal API.
"""
CIELO24_LOGIN_URL = "https://sandbox.cielo24.com/api/account/login"

@data(
{
'org': 'test',
'provider': TranscriptProviderType.CIELO24,
'api_key': 'test-api-key',
'username': 'test-cielo-user'
},
{
'org': 'test',
'provider': TranscriptProviderType.THREE_PLAY_MEDIA,
'api_key': 'test-api-key',
'api_secret_key': 'test-secret-key'
}
)
@responses.activate
def test_transcript_credentials_success(self, credentials):
"""
Test that creating credentials works as expected with correct set of data.
"""
responses.add(
responses.GET,
self.CIELO24_LOGIN_URL,
body='{"ApiToken": "cielo-api-token"}',
status=status.HTTP_200_OK
)

transcript_credentials = TranscriptCredentials.objects.filter(
org=credentials.get('org'),
provider=credentials.get('provider')
)
self.assertFalse(transcript_credentials.exists())

_ = api.create_or_update_transcript_credentials(**credentials)
transcript_credentials = TranscriptCredentials.objects.filter(
org=credentials.get('org'),
provider=credentials.get('provider')
)
self.assertTrue(transcript_credentials.exists())
113 changes: 106 additions & 7 deletions edxval/tests/test_transcript_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@

import json
import textwrap
import unittest

import ddt
import responses
from ddt import data, ddt, unpack
from django.test import TestCase
from mock import patch
from rest_framework import status

from edxval.enum import TranscriptionProviderErrorType
from edxval.exceptions import TranscriptsGenerationException
from edxval.transcript_utils import Transcript
from edxval.models import TranscriptProviderType
from edxval.transcript_utils import Transcript, validate_transcript_credentials


@ddt.ddt
class TestTranscriptUtils(unittest.TestCase):
@ddt
class TestTranscriptUtils(TestCase):
"""
Tests transcripts conversion util.
"""
Expand Down Expand Up @@ -50,12 +55,12 @@ def setUp(self):
}
""").encode('utf8')

@ddt.data(
@data(
('invalid_input_format', 'sjson'),
('sjson', 'invalid_output_format'),
('invalid_input_format', 'invalid_output_format')
)
@ddt.unpack
@unpack
def test_invalid_transcript_format(self, input_format, output_format):
"""
Tests that transcript conversion raises `AssertionError` on invalid input/output formats.
Expand Down Expand Up @@ -95,3 +100,97 @@ def test_convert_invalid_srt_to_sjson(self):
invalid_srt_transcript = b'invalid SubRip file content'
with self.assertRaises(TranscriptsGenerationException):
Transcript.convert(invalid_srt_transcript, 'srt', 'sjson')


@ddt
class TestCredentialsUtils(TestCase):
"""
Test Suite for various transcript credential utilities.
"""

CIELO24_LOGIN_URL = "https://sandbox.cielo24.com/api/account/login"

@patch('edxval.transcript_utils.LOGGER')
@responses.activate
def test_cielo24_error(self, mock_logger):
"""
Test that when invalid cielo credentials are supplied, we get correct error response.
"""
expected_error_message = 'Invalid credentials supplied.'
responses.add(
responses.GET,
self.CIELO24_LOGIN_URL,
body=json.dumps({'error': expected_error_message}),
status=status.HTTP_400_BAD_REQUEST
)

credentials = {
'org': 'test',
'provider': TranscriptProviderType.CIELO24,
'api_key': 'test-api-key',
'username': 'test-cielo-user',
'api_secret_key': ''
}
error_type, error_message, _ = validate_transcript_credentials(**credentials)
self.assertEqual(error_type, TranscriptionProviderErrorType.INVALID_CREDENTIALS)
self.assertEqual(error_message, expected_error_message)

mock_logger.warning.assert_called_with(
'[Transcript Credentials] Unable to get api token -- response %s -- status %s.',
json.dumps({'error': error_message}),
status.HTTP_400_BAD_REQUEST
)

@data(
{
'provider': 'unsupported-provider'
},
{
'org': 'test',
'api_key': 'test-api-key'
}
)
def test_transcript_credentials_invalid_provider(self, credentials):
"""
Test that validating credentials gives proper error in case of invalid provider.
"""
provider = credentials.pop('provider', '')
error_type, error_message, _ = validate_transcript_credentials(provider, **credentials)
self.assertEqual(error_type, TranscriptionProviderErrorType.INVALID_PROVIDER)
self.assertEqual(error_message, 'Invalid provider {provider}.'.format(provider=provider))

@data(
(
{'provider': TranscriptProviderType.CIELO24},
'org and api_key and username'
),
(
{'provider': TranscriptProviderType.THREE_PLAY_MEDIA},
'org and api_key and api_secret_key'
),
(
{'provider': TranscriptProviderType.CIELO24, 'org': 'test-org'},
'api_key and username'
),
(
{'provider': TranscriptProviderType.CIELO24, 'org': 'test-org', 'api_key': 'test-api-key'},
'username'
),
(
{'org': 'test', 'provider': TranscriptProviderType.THREE_PLAY_MEDIA, 'api_key': 'test-api-key'},
'api_secret_key'
)
)
@unpack
def test_transcript_credentials_error(self, credentials, missing_keys):
"""
Test that validating credentials gives proper error in case of invalid input.
"""
provider = credentials.pop('provider')
expected_error_message = '{missing} must be specified for {provider}.'.format(
provider=provider,
missing=missing_keys
)
error_type, error_message, _ = validate_transcript_credentials(provider, **credentials)
self.assertEqual(error_type, TranscriptionProviderErrorType.MISSING_REQUIRED_ATTRIBUTES)
self.assertEqual(error_message, expected_error_message)
88 changes: 87 additions & 1 deletion edxval/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,15 @@
from django.urls import reverse
from rest_framework import status

from edxval.models import CourseVideo, EncodedVideo, Profile, TranscriptProviderType, Video, VideoTranscript
from edxval.models import (
CourseVideo,
EncodedVideo,
Profile,
TranscriptCredentials,
TranscriptProviderType,
Video,
VideoTranscript,
)
from edxval.serializers import TranscriptSerializer
from edxval.tests import APIAuthTestCase, constants
from edxval.utils import TranscriptFormat
Expand Down Expand Up @@ -1054,3 +1062,81 @@ def test_update_hls_encodes_for_video(self):
self.assertEqual(actual_encoded_video.url, expected_data['encode_data']['url'])
self.assertEqual(actual_encoded_video.file_size, expected_data['encode_data']['file_size'])
self.assertEqual(actual_encoded_video.bitrate, expected_data['encode_data']['bitrate'])


@ddt
class TranscriptCredentialsTest(APIAuthTestCase):
"""
Transcript credentials tests.
"""

def test_transcript_credentials_unauthorized(self):
"""
Tests that if user is not logged in we get Unauthorized response.
"""
self.client.logout()
url = reverse('transcript-credentials', kwargs={'org': 'test', 'provider': 'provider'})
response = self.client.get(
url,
content_type='application/json'
)
response_status_code = response.status_code
self.assertEqual(response_status_code, status.HTTP_401_UNAUTHORIZED)

@data(
({'org': '', 'provider': ''}, 'org and provider must be specified.'),
({'org': 'org', 'provider': ''}, 'provider must be specified.'),
({'org': '', 'provider': 'provider'}, 'org must be specified.')
)
@unpack
def test_fetch_credentials_missing_parameters(self, query_params, error_message):
"""
Verify that if query parameters are missing, then request fails with missing params error.
"""
url = reverse('transcript-credentials', kwargs=query_params)
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

response = json.loads(response.content.decode('utf-8'))
self.assertDictEqual(response, {
'message': error_message,
})

def test_get_non_existent_credentials(self):
"""
Test that fetching a non-existing set of credentials results in failure.
"""
provider, org = "provider", "org"
query_params = {'provider': provider, 'org': org}
url = reverse('transcript-credentials', kwargs=query_params)
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

response = json.loads(response.content.decode('utf-8'))
self.assertDictEqual(response, {
'message': "Credentials not found for provider {provider} & organization {org}".format(
provider=provider,
org=org
),
})

def test_successful_fetch(self):
"""
Verify that persistent credentials are returned for correct query params.
"""
provider, org = "provider", "org"
credentials_dict = dict(
provider=provider,
org=org,
api_key='key',
api_secret='secret'
)
TranscriptCredentials(**credentials_dict).save()
query_params = {'provider': provider, 'org': org}
url = reverse('transcript-credentials', kwargs=query_params)
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)

response = json.loads(response.content.decode('utf-8'))
self.assertEqual(response['api_key'], credentials_dict['api_key'])
self.assertEqual(response['api_secret_key'], credentials_dict['api_secret'])
Loading

0 comments on commit dfe5c54

Please sign in to comment.