Commit 61efdfa2 by Christopher Lee

Merge pull request #9 from edx/clee/put

tests for PUT functionality
parents 49c58413 595d4f28
......@@ -13,26 +13,18 @@ class Profile(models.Model):
profile_name = models.CharField(
max_length=50,
unique=True,
)
)
extension = models.CharField(max_length=10)
width = models.PositiveIntegerField()
height = models.PositiveIntegerField()
def __repr__(self):
return (
u"Profile(profile_name={0.profile_name})"
).format(self)
def __unicode__(self):
return repr(self)
class Video(models.Model):
"""
Model for a Video group with the same content.
A video can have multiple formats. This model is the collection of those
videos with fields that do not change across formats.
A video can have multiple formats. This model are the fields that represent
the collection of those videos that do not change across formats.
"""
edx_video_id = models.CharField(
max_length=50,
......@@ -48,14 +40,6 @@ class Video(models.Model):
client_video_id = models.CharField(max_length=255, db_index=True)
duration = models.FloatField(validators=[MinValueValidator(0)])
def __repr__(self):
return (
u"Video(client_video_id={0.client_video_id}, duration={0.duration})"
).format(self)
def __unicode__(self):
return repr(self)
class CourseVideos(models.Model):
"""
......@@ -83,12 +67,3 @@ class EncodedVideo(models.Model):
profile = models.ForeignKey(Profile, related_name="+")
video = models.ForeignKey(Video, related_name="encoded_videos")
def __repr__(self):
return (
u"EncodedVideo(video={0.video.client_video_id}, "
u"profile={0.profile.profile_name})"
).format(self)
def __unicode__(self):
return repr(self)
......@@ -2,6 +2,7 @@
Serializers for Video Abstraction Layer
"""
from rest_framework import serializers
from django.core.exceptions import ValidationError
from edxval.models import Profile, Video, EncodedVideo
......@@ -31,10 +32,44 @@ class EncodedVideoSerializer(serializers.ModelSerializer):
"profile",
)
def get_identity(self, data):
"""
This hook is required for bulk update.
We need to override the default, to use the slug as the identity.
"""
return data.get('profile', None)
class VideoSerializer(serializers.HyperlinkedModelSerializer):
encoded_videos = EncodedVideoSerializer(many=True, allow_add_remove=True)
encoded_videos = EncodedVideoSerializer(
many=True,
allow_add_remove=True
)
class Meta:
model = Video
lookup_field = "edx_video_id"
def restore_fields(self, data, files):
"""
Converts a dictionary of data into a dictionary of deserialized fields. Also
checks if there are duplicate profile_name(s). If there is, the deserialization
is rejected.
"""
reverted_data = {}
if data is not None and not isinstance(data, dict):
self._errors['non_field_errors'] = ['Invalid data']
return None
profiles = [ev["profile"] for ev in data.get("encoded_videos", [])]
if len(profiles) != len(set(profiles)):
self._errors['non_field_errors'] = ['Invalid data: duplicate profiles']
for field_name, field in self.fields.items():
field.initialize(parent=self, field_name=field_name)
try:
field.field_from_native(data, files, field_name, reverted_data)
except ValidationError as err:
self._errors[field_name] = list(err.messages)
return reverted_data
......@@ -23,7 +23,6 @@ ENCODED_VIDEO_DICT_MOBILE = dict(
file_size=4545,
bitrate=6767,
)
ENCODED_VIDEO_DICT_DESKTOP = dict(
url="http://www.meowmagic.com",
file_size=1212,
......@@ -38,28 +37,27 @@ VIDEO_DICT_NEGATIVE_DURATION = dict(
edx_video_id="thisis12char-thisis7",
encoded_videos=[]
)
ENCODED_VIDEO_DICT_NEGATIVE_FILESIZE = dict(
url="http://www.meowmix.com",
file_size=-25556,
bitrate=9600,
)
ENCODED_VIDEO_DICT_NEGATIVE_BITRATE = dict(
url="http://www.meowmix.com",
file_size=25556,
bitrate=-9600,
)
VIDEO_DICT_BEE_INVALID = dict(
client_video_id="Barking Bee",
duration=111.00,
edx_video_id="wa/sps",
)
VIDEO_DICT_INVALID_ID = dict(
client_video_id="SuperSloth",
duration=42,
edx_video_id="sloppy/sloth!!",
encoded_videos=[]
)
ENCODED_VIDEO_DICT_NEGATIVE_FILESIZE = dict(
url="http://www.meowmix.com",
file_size=-25556,
bitrate=9600,
)
ENCODED_VIDEO_DICT_NEGATIVE_BITRATE = dict(
url="http://www.meowmix.com",
file_size=25556,
bitrate=-9600,
)
"""
Non-latin/invalid
"""
......@@ -87,30 +85,43 @@ Fish
VIDEO_DICT_FISH = dict(
client_video_id="Shallow Swordfish",
duration=122.00,
edx_video_id="supersoaker"
edx_video_id="super-soaker"
)
VIDEO_DICT_DIFFERENT_ID_FISH = dict(
client_video_id="Shallow Swordfish",
duration=122.00,
edx_video_id="medium-soaker"
)
ENCODED_VIDEO_DICT_FISH_MOBILE = dict(
url="https://www.swordsingers.com",
file_size=9000,
bitrate=42,
profile="mobile",
)
ENCODED_VIDEO_DICT_FISH_DESKTOP = dict(
url="https://www.swordsplints.com",
file_size=1234,
bitrate=4222,
profile="desktop",
)
ENCODED_VIDEO_DICT_UPDATE_FISH_MOBILE = dict(
url="https://www.fishfellow.com",
file_size=1,
bitrate=1,
profile="mobile",
)
ENCODED_VIDEO_DICT_UPDATE_FISH_DESKTOP = dict(
url="https://www.furryfish.com",
file_size=2,
bitrate=2,
profile="desktop",
)
ENCODED_VIDEO_DICT_FISH_INVALID_PROFILE = dict(
url="https://www.swordsplints.com",
file_size=1234,
bitrate=4222,
profile=11,
profile="bird"
)
COMPLETE_SET_FISH = dict(
encoded_videos=[
ENCODED_VIDEO_DICT_FISH_MOBILE,
......@@ -118,7 +129,40 @@ COMPLETE_SET_FISH = dict(
],
**VIDEO_DICT_FISH
)
COMPLETE_SET_TWO_MOBILE_FISH = dict(
encoded_videos=[
ENCODED_VIDEO_DICT_FISH_MOBILE,
ENCODED_VIDEO_DICT_FISH_MOBILE
],
**VIDEO_DICT_FISH
)
COMPLETE_SET_UPDATE_FISH = dict(
encoded_videos=[
ENCODED_VIDEO_DICT_UPDATE_FISH_MOBILE,
ENCODED_VIDEO_DICT_UPDATE_FISH_DESKTOP
],
**VIDEO_DICT_FISH
)
COMPLETE_SET_DIFFERENT_ID_UPDATE_FISH = dict(
encoded_videos=[
ENCODED_VIDEO_DICT_UPDATE_FISH_MOBILE,
ENCODED_VIDEO_DICT_UPDATE_FISH_DESKTOP
],
**VIDEO_DICT_DIFFERENT_ID_FISH
)
COMPLETE_SET_FIRST_HALF_UPDATE_FISH = dict(
encoded_videos=[
ENCODED_VIDEO_DICT_UPDATE_FISH_MOBILE,
ENCODED_VIDEO_DICT_FISH_DESKTOP
],
**VIDEO_DICT_FISH
)
COMPLETE_SET_UPDATE_ONLY_DESKTOP_FISH = dict(
encoded_videos=[
ENCODED_VIDEO_DICT_UPDATE_FISH_DESKTOP
],
**VIDEO_DICT_FISH
)
COMPLETE_SET_INVALID_ENCODED_VIDEO_FISH = dict(
encoded_videos=[
ENCODED_VIDEO_DICT_FISH_MOBILE,
......@@ -126,7 +170,6 @@ COMPLETE_SET_INVALID_ENCODED_VIDEO_FISH = dict(
],
**VIDEO_DICT_FISH
)
COMPLETE_SET_INVALID_VIDEO_FISH = dict(
client_video_id="Shallow Swordfish",
duration=122.00,
......@@ -141,8 +184,6 @@ COMPLETE_SETS_ALL_INVALID = [
COMPLETE_SET_INVALID_VIDEO_FISH,
COMPLETE_SET_INVALID_VIDEO_FISH
]
"""
Star
"""
......@@ -157,14 +198,24 @@ ENCODED_VIDEO_DICT_STAR = dict(
bitrate=42,
profile="mobile"
)
ENCODED_VIDEO_UPDATE_DICT_STAR = dict(
url="https://www.whatyouare.com",
file_size=9000,
bitrate=42,
profile="mobile"
)
COMPLETE_SET_STAR = dict(
encoded_videos=[
ENCODED_VIDEO_DICT_STAR
],
**VIDEO_DICT_STAR
)
COMPLETE_SET_UPDATE_STAR = dict(
encoded_videos=[
ENCODED_VIDEO_UPDATE_DICT_STAR
],
**VIDEO_DICT_STAR
)
COMPLETE_SET_NOT_A_LIST = dict(
encoded_videos=dict(
url="https://www.howIwonder.com",
......@@ -174,7 +225,6 @@ COMPLETE_SET_NOT_A_LIST = dict(
),
**VIDEO_DICT_STAR
)
COMPLETE_SET_EXTRA_VIDEO_FIELD = dict(
encoded_videos=[
dict(
......@@ -188,12 +238,13 @@ COMPLETE_SET_EXTRA_VIDEO_FIELD = dict(
**VIDEO_DICT_STAR
)
"""
Unsorted
Other
"""
VIDEO_DICT_COAT = dict(
client_video_id="Callous Coat",
VIDEO_DICT_ZEBRA = dict(
client_video_id="Zesty Zebra",
duration=111.00,
edx_video_id="itchyjacket"
edx_video_id="zestttt",
encoded_videos=[]
)
VIDEO_DICT_ANIMAL = dict(
client_video_id="Average Animal",
......@@ -201,48 +252,9 @@ VIDEO_DICT_ANIMAL = dict(
edx_video_id="mediocrity",
encoded_videos=[]
)
VIDEO_DICT_ZEBRA = dict(
client_video_id="Zesty Zebra",
duration=111.00,
edx_video_id="zestttt",
encoded_videos=[]
)
VIDEO_DICT_UPDATE_ANIMAL = dict(
client_video_id="Lolcat",
duration=122.00,
client_video_id="Above Average Animal",
duration=999.00,
edx_video_id="mediocrity",
encoded_videos=[]
)
VIDEO_DICT_CRAYFISH = dict(
client_video_id="Crazy Crayfish",
duration=111.00,
edx_video_id="craycray",
)
VIDEO_DICT_DUPLICATES = [
VIDEO_DICT_CRAYFISH,
VIDEO_DICT_CRAYFISH,
VIDEO_DICT_CRAYFISH
]
COMPLETE_SETS = [
COMPLETE_SET_STAR,
COMPLETE_SET_FISH
]
COMPLETE_SETS_ONE_INVALID = [
COMPLETE_SET_STAR,
COMPLETE_SET_INVALID_VIDEO_FISH
]
VIDEO_DICT_SET_OF_THREE = [
VIDEO_DICT_COAT,
VIDEO_DICT_ANIMAL,
VIDEO_DICT_CRAYFISH
]
VIDEO_DICT_INVALID_SET = [
VIDEO_DICT_COAT,
VIDEO_DICT_INVALID_ID,
VIDEO_DICT_BEE_INVALID
]
......@@ -7,6 +7,9 @@ import mock
from django.test import TestCase
from django.db import DatabaseError
from django.core.urlresolvers import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from edxval.models import Profile, Video, EncodedVideo
from edxval import api as api
......@@ -16,26 +19,23 @@ from edxval.tests import constants
class GetVideoInfoTest(TestCase):
#TODO When upload portion is finished, do not forget to create tests for validating
#TODO regex for models. Currently, objects are created manually and validators
#TODO are not triggered.
def setUp(self):
"""
Creates EncodedVideo objects in database
"""
Profile.objects.create(**constants.PROFILE_DICT_MOBILE)
Profile.objects.create(**constants.PROFILE_DICT_DESKTOP)
Video.objects.create(**constants.VIDEO_DICT_COAT)
Video.objects.create(**constants.VIDEO_DICT_FISH)
EncodedVideo.objects.create(
video=Video.objects.get(
edx_video_id=constants.VIDEO_DICT_COAT.get("edx_video_id")
edx_video_id=constants.VIDEO_DICT_FISH.get("edx_video_id")
),
profile=Profile.objects.get(profile_name="mobile"),
**constants.ENCODED_VIDEO_DICT_MOBILE
)
EncodedVideo.objects.create(
video=Video.objects.get(
edx_video_id=constants.VIDEO_DICT_COAT.get("edx_video_id")
edx_video_id=constants.VIDEO_DICT_FISH.get("edx_video_id")
),
profile=Profile.objects.get(profile_name="desktop"),
**constants.ENCODED_VIDEO_DICT_DESKTOP
......@@ -45,7 +45,9 @@ class GetVideoInfoTest(TestCase):
"""
Tests for successful video request
"""
self.assertIsNotNone(api.get_video_info(constants.EDX_VIDEO_ID))
self.assertIsNotNone(api.get_video_info(
constants.VIDEO_DICT_FISH.get("edx_video_id"))
)
def test_no_such_video(self):
"""
......@@ -71,7 +73,9 @@ class GetVideoInfoTest(TestCase):
"""
mock_init.side_effect = Exception("Mock error")
with self.assertRaises(api.ValInternalError):
api.get_video_info(constants.EDX_VIDEO_ID)
api.get_video_info(
constants.VIDEO_DICT_FISH.get("edx_video_id")
)
@mock.patch.object(Video.objects, 'get')
def test_force_database_error(self, mock_get):
......@@ -80,4 +84,66 @@ class GetVideoInfoTest(TestCase):
"""
mock_get.side_effect = DatabaseError("DatabaseError")
with self.assertRaises(api.ValInternalError):
api.get_video_info(constants.EDX_VIDEO_ID)
api.get_video_info(
constants.VIDEO_DICT_FISH.get("edx_video_id")
)
class GetVideoInfoTestWithHttpCalls(APITestCase):
def setUp(self):
"""
Creates EncodedVideo objects in database with HTTP requests.
The tests are similar to the GetVideoInfoTest class. This class
is to tests that we have the same results, using a populated
database via HTTP uploads.
"""
Profile.objects.create(**constants.PROFILE_DICT_MOBILE)
Profile.objects.create(**constants.PROFILE_DICT_DESKTOP)
url = reverse('video-list')
response = self.client.post(
url, constants.COMPLETE_SET_FISH, format='json'
)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def test_get_video_found(self):
"""
Tests for successful video request
"""
self.assertIsNotNone(
api.get_video_info(
constants.COMPLETE_SET_FISH.get("edx_video_id")
)
)
def test_get_info_queries_for_two_encoded_video(self):
"""
Tests number of queries for a Video/EncodedVideo(1) pair
"""
with self.assertNumQueries(4):
api.get_video_info(constants.COMPLETE_SET_FISH.get("edx_video_id"))
def test_get_info_queries_for_one_encoded_video(self):
"""
Tests number of queries for a Video/EncodedVideo(1) pair
"""
url = reverse('video-list')
response = self.client.post(
url, constants.COMPLETE_SET_STAR, format='json'
)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
with self.assertNumQueries(3):
api.get_video_info(constants.COMPLETE_SET_STAR.get("edx_video_id"))
def test_get_info_queries_for_only_video(self):
"""
Tests number of queries for a Video with no Encoded Videopair
"""
url = reverse('video-list')
response = self.client.post(
url, constants.VIDEO_DICT_ZEBRA, format='json'
)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
with self.assertNumQueries(2):
api.get_video_info(constants.VIDEO_DICT_ZEBRA.get("edx_video_id"))
......@@ -63,13 +63,6 @@ class SerializerTests(TestCase):
ProfileSerializer
)
def test_non_latin_deserialization(self):
"""
Tests deserialization of non-latin data
"""
#TODO write a test for this when we understand what we want
pass
def test_invalid_edx_video_id(self):
"""
Test the Video model regex validation for edx_video_id field
......@@ -84,7 +77,7 @@ class SerializerTests(TestCase):
"""
Tests for basic structure of EncodedVideoSetSerializer
"""
video = Video.objects.create(**constants.VIDEO_DICT_COAT)
video = Video.objects.create(**constants.VIDEO_DICT_FISH)
EncodedVideo.objects.create(
video=video,
profile=Profile.objects.get(profile_name="desktop"),
......@@ -99,4 +92,4 @@ class SerializerTests(TestCase):
# Check for 2 EncodedVideo entries
self.assertEqual(len(result.get("encoded_videos")), 2)
# Check for original Video data
self.assertDictContainsSubset(constants.VIDEO_DICT_COAT, result)
self.assertDictContainsSubset(constants.VIDEO_DICT_FISH, result)
......@@ -9,7 +9,7 @@ admin.autodiscover()
urlpatterns = patterns('',
url(r'^edxval/video/$', views.VideoList.as_view(),
name="video-list"),
url(r'^edxval/video/(?P<edx_video_id>\w+)',
url(r'^edxval/video/(?P<edx_video_id>[-\w]+)',
views.VideoDetail.as_view(),
name="video-detail"),
url(r'^admin/', include(admin.site.urls)),
......
from rest_framework import generics
from edxval.models import Video
from edxval.models import Video, Profile
from edxval.serializers import (
VideoSerializer
VideoSerializer,
ProfileSerializer
)
......@@ -14,6 +15,14 @@ class VideoList(generics.ListCreateAPIView):
lookup_field = "edx_video_id"
serializer_class = VideoSerializer
class ProfileList(generics.ListCreateAPIView):
"""
GETs or POST video objects
"""
queryset = Profile.objects.all()
lookup_field = "profile_name"
serializer_class = ProfileSerializer
class VideoDetail(generics.RetrieveUpdateDestroyAPIView):
"""
......
from django.conf.urls import patterns, include, url
from rest_framework.urlpatterns import format_suffix_patterns
from edxval import views
......
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