Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-val
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-val
Commits
61efdfa2
Commit
61efdfa2
authored
Aug 11, 2014
by
Christopher Lee
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #9 from edx/clee/put
tests for PUT functionality
parents
49c58413
595d4f28
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
211 additions
and
122 deletions
+211
-122
edxval/models.py
+3
-28
edxval/serializers.py
+36
-1
edxval/tests/constants.py
+83
-71
edxval/tests/test_api.py
+75
-9
edxval/tests/test_serializers.py
+2
-9
edxval/tests/test_views.py
+0
-0
edxval/urls.py
+1
-1
edxval/views.py
+11
-2
urls.py
+0
-1
No files found.
edxval/models.py
View file @
61efdfa2
...
...
@@ -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 field
s that do not change across formats.
A video can have multiple formats. This model
are the fields that represent
the collection of those video
s 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
)
edxval/serializers.py
View file @
61efdfa2
...
...
@@ -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
edxval/tests/constants.py
View file @
61efdfa2
...
...
@@ -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
]
edxval/tests/test_api.py
View file @
61efdfa2
...
...
@@ -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"
))
edxval/tests/test_serializers.py
View file @
61efdfa2
...
...
@@ -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
)
edxval/tests/test_views.py
View file @
61efdfa2
This diff is collapsed.
Click to expand it.
edxval/urls.py
View file @
61efdfa2
...
...
@@ -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
)),
...
...
edxval/views.py
View file @
61efdfa2
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
):
"""
...
...
urls.py
View file @
61efdfa2
from
django.conf.urls
import
patterns
,
include
,
url
from
rest_framework.urlpatterns
import
format_suffix_patterns
from
edxval
import
views
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment