Commit d2fcb7b3 by Dennis Jen Committed by GitHub

Added ccx library (#143)

* Refactored tests to use a variety of course IDs using ddt
* Bump versions of opaque keys and ccx libraries
* Added timeout check to see if the elasticsearch index is ready in tests (fixes flaky tests)
parent 75467f6e
...@@ -2,26 +2,18 @@ import json ...@@ -2,26 +2,18 @@ import json
import StringIO import StringIO
import csv import csv
from opaque_keys.edx.keys import CourseKey
from rest_framework import status from rest_framework import status
from analytics_data_api.utils import get_filename_safe_course_id
from analytics_data_api.v0.tests.utils import flatten from analytics_data_api.v0.tests.utils import flatten
DEMO_COURSE_ID = u'course-v1:edX+DemoX+Demo_2014' class CourseSamples(object):
SANITIZED_DEMO_COURSE_ID = get_filename_safe_course_id(DEMO_COURSE_ID)
course_ids = [
class DemoCourseMixin(object): 'edX/DemoX/Demo_Course',
course_key = None 'course-v1:edX+DemoX+Demo_2014',
course_id = None 'ccx-v1:edx+1.005x-CCX+rerun+ccx@15'
]
@classmethod
def setUpClass(cls):
cls.course_id = DEMO_COURSE_ID
cls.course_key = CourseKey.from_string(cls.course_id)
super(DemoCourseMixin, cls).setUpClass()
class VerifyCourseIdMixin(object): class VerifyCourseIdMixin(object):
......
...@@ -12,21 +12,21 @@ from analyticsdataserver.tests import TestCaseWithAuthentication ...@@ -12,21 +12,21 @@ from analyticsdataserver.tests import TestCaseWithAuthentication
from analytics_data_api.constants.engagement_events import (ATTEMPTED, COMPLETED, CONTRIBUTED, DISCUSSION, from analytics_data_api.constants.engagement_events import (ATTEMPTED, COMPLETED, CONTRIBUTED, DISCUSSION,
PROBLEM, VIDEO, VIEWED) PROBLEM, VIDEO, VIEWED)
from analytics_data_api.v0 import models from analytics_data_api.v0 import models
from analytics_data_api.v0.tests.views import DemoCourseMixin, VerifyCourseIdMixin from analytics_data_api.v0.tests.views import CourseSamples, VerifyCourseIdMixin
@ddt.ddt @ddt.ddt
class EngagementTimelineTests(DemoCourseMixin, VerifyCourseIdMixin, TestCaseWithAuthentication): class EngagementTimelineTests(VerifyCourseIdMixin, TestCaseWithAuthentication):
DEFAULT_USERNAME = 'ed_xavier' DEFAULT_USERNAME = 'ed_xavier'
path_template = '/api/v0/engagement_timelines/{}/?course_id={}' path_template = '/api/v0/engagement_timelines/{}/?course_id={}'
def create_engagement(self, entity_type, event_type, entity_id, count, date=None): def create_engagement(self, course_id, entity_type, event_type, entity_id, count, date=None):
"""Create a ModuleEngagement model""" """Create a ModuleEngagement model"""
if date is None: if date is None:
date = datetime.datetime(2015, 1, 1, tzinfo=pytz.utc) date = datetime.datetime(2015, 1, 1, tzinfo=pytz.utc)
G( G(
models.ModuleEngagement, models.ModuleEngagement,
course_id=self.course_id, course_id=course_id,
username=self.DEFAULT_USERNAME, username=self.DEFAULT_USERNAME,
date=date, date=date,
entity_type=entity_type, entity_type=entity_type,
...@@ -36,19 +36,19 @@ class EngagementTimelineTests(DemoCourseMixin, VerifyCourseIdMixin, TestCaseWith ...@@ -36,19 +36,19 @@ class EngagementTimelineTests(DemoCourseMixin, VerifyCourseIdMixin, TestCaseWith
) )
@ddt.data( @ddt.data(
(PROBLEM, ATTEMPTED, 'problems_attempted', True), (CourseSamples.course_ids[0], PROBLEM, ATTEMPTED, 'problems_attempted', True),
(PROBLEM, COMPLETED, 'problems_completed', True), (CourseSamples.course_ids[1], PROBLEM, COMPLETED, 'problems_completed', True),
(VIDEO, VIEWED, 'videos_viewed', True), (CourseSamples.course_ids[2], VIDEO, VIEWED, 'videos_viewed', True),
(DISCUSSION, CONTRIBUTED, 'discussion_contributions', False), (CourseSamples.course_ids[0], DISCUSSION, CONTRIBUTED, 'discussion_contributions', False),
) )
@ddt.unpack @ddt.unpack
def test_metric_aggregation(self, entity_type, event_type, metric_display_name, expect_id_aggregation): def test_metric_aggregation(self, course_id, entity_type, event_type, metric_display_name, expect_id_aggregation):
""" """
Verify that some metrics are counted by unique ID, while some are Verify that some metrics are counted by unique ID, while some are
counted by total interactions. counted by total interactions.
""" """
self.create_engagement(entity_type, event_type, 'entity-id', count=5) self.create_engagement(course_id, entity_type, event_type, 'entity-id', count=5)
self.create_engagement(entity_type, event_type, 'entity-id', count=5) self.create_engagement(course_id, entity_type, event_type, 'entity-id', count=5)
expected_data = { expected_data = {
'days': [ 'days': [
{ {
...@@ -64,7 +64,7 @@ class EngagementTimelineTests(DemoCourseMixin, VerifyCourseIdMixin, TestCaseWith ...@@ -64,7 +64,7 @@ class EngagementTimelineTests(DemoCourseMixin, VerifyCourseIdMixin, TestCaseWith
expected_data['days'][0][metric_display_name] = 1 expected_data['days'][0][metric_display_name] = 1
else: else:
expected_data['days'][0][metric_display_name] = 10 expected_data['days'][0][metric_display_name] = 10
path = self.path_template.format(self.DEFAULT_USERNAME, urlquote(self.course_id)) path = self.path_template.format(self.DEFAULT_USERNAME, urlquote(course_id))
response = self.authenticated_get(path) response = self.authenticated_get(path)
self.assertEquals(response.status_code, 200) self.assertEquals(response.status_code, 200)
self.assertEquals( self.assertEquals(
...@@ -72,20 +72,21 @@ class EngagementTimelineTests(DemoCourseMixin, VerifyCourseIdMixin, TestCaseWith ...@@ -72,20 +72,21 @@ class EngagementTimelineTests(DemoCourseMixin, VerifyCourseIdMixin, TestCaseWith
expected_data expected_data
) )
def test_timeline(self): @ddt.data(*CourseSamples.course_ids)
def test_timeline(self, course_id):
""" """
Smoke test the learner engagement timeline. Smoke test the learner engagement timeline.
""" """
path = self.path_template.format(self.DEFAULT_USERNAME, urlquote(self.course_id)) path = self.path_template.format(self.DEFAULT_USERNAME, urlquote(course_id))
day_one = datetime.datetime(2015, 1, 1, tzinfo=pytz.utc) day_one = datetime.datetime(2015, 1, 1, tzinfo=pytz.utc)
day_two = datetime.datetime(2015, 1, 2, tzinfo=pytz.utc) day_two = datetime.datetime(2015, 1, 2, tzinfo=pytz.utc)
self.create_engagement(PROBLEM, ATTEMPTED, 'id-1', count=100, date=day_one) self.create_engagement(course_id, PROBLEM, ATTEMPTED, 'id-1', count=100, date=day_one)
self.create_engagement(PROBLEM, COMPLETED, 'id-2', count=12, date=day_one) self.create_engagement(course_id, PROBLEM, COMPLETED, 'id-2', count=12, date=day_one)
self.create_engagement(DISCUSSION, CONTRIBUTED, 'id-3', count=6, date=day_one) self.create_engagement(course_id, DISCUSSION, CONTRIBUTED, 'id-3', count=6, date=day_one)
self.create_engagement(DISCUSSION, CONTRIBUTED, 'id-4', count=10, date=day_two) self.create_engagement(course_id, DISCUSSION, CONTRIBUTED, 'id-4', count=10, date=day_two)
self.create_engagement(VIDEO, VIEWED, 'id-5', count=44, date=day_two) self.create_engagement(course_id, VIDEO, VIEWED, 'id-5', count=44, date=day_two)
self.create_engagement(PROBLEM, ATTEMPTED, 'id-6', count=8, date=day_two) self.create_engagement(course_id, PROBLEM, ATTEMPTED, 'id-6', count=8, date=day_two)
self.create_engagement(PROBLEM, ATTEMPTED, 'id-7', count=4, date=day_two) self.create_engagement(course_id, PROBLEM, ATTEMPTED, 'id-7', count=4, date=day_two)
response = self.authenticated_get(path) response = self.authenticated_get(path)
self.assertEquals(response.status_code, 200) self.assertEquals(response.status_code, 200)
expected = { expected = {
...@@ -108,12 +109,13 @@ class EngagementTimelineTests(DemoCourseMixin, VerifyCourseIdMixin, TestCaseWith ...@@ -108,12 +109,13 @@ class EngagementTimelineTests(DemoCourseMixin, VerifyCourseIdMixin, TestCaseWith
} }
self.assertEquals(response.data, expected) self.assertEquals(response.data, expected)
def test_day_gap(self): @ddt.data(*CourseSamples.course_ids)
path = self.path_template.format(self.DEFAULT_USERNAME, urlquote(self.course_id)) def test_day_gap(self, course_id):
path = self.path_template.format(self.DEFAULT_USERNAME, urlquote(course_id))
first_day = datetime.datetime(2015, 5, 26, tzinfo=pytz.utc) first_day = datetime.datetime(2015, 5, 26, tzinfo=pytz.utc)
last_day = datetime.datetime(2015, 5, 28, tzinfo=pytz.utc) last_day = datetime.datetime(2015, 5, 28, tzinfo=pytz.utc)
self.create_engagement(VIDEO, VIEWED, 'id-1', count=1, date=first_day) self.create_engagement(course_id, VIDEO, VIEWED, 'id-1', count=1, date=first_day)
self.create_engagement(PROBLEM, ATTEMPTED, entity_id='id-2', count=1, date=last_day) self.create_engagement(course_id, PROBLEM, ATTEMPTED, entity_id='id-2', count=1, date=last_day)
response = self.authenticated_get(path) response = self.authenticated_get(path)
self.assertEquals(response.status_code, 200) self.assertEquals(response.status_code, 200)
expected = { expected = {
...@@ -143,14 +145,15 @@ class EngagementTimelineTests(DemoCourseMixin, VerifyCourseIdMixin, TestCaseWith ...@@ -143,14 +145,15 @@ class EngagementTimelineTests(DemoCourseMixin, VerifyCourseIdMixin, TestCaseWith
} }
self.assertEquals(response.data, expected) self.assertEquals(response.data, expected)
def test_not_found(self): @ddt.data(*CourseSamples.course_ids)
path = self.path_template.format(self.DEFAULT_USERNAME, urlquote(self.course_id)) def test_not_found(self, course_id):
path = self.path_template.format(self.DEFAULT_USERNAME, urlquote(course_id))
response = self.authenticated_get(path) response = self.authenticated_get(path)
self.assertEquals(response.status_code, status.HTTP_404_NOT_FOUND) self.assertEquals(response.status_code, status.HTTP_404_NOT_FOUND)
expected = { expected = {
u"error_code": u"no_learner_engagement_timeline", u"error_code": u"no_learner_engagement_timeline",
u"developer_message": u"Learner {} engagement timeline not found for course {}.".format( u"developer_message": u"Learner {} engagement timeline not found for course {}.".format(
self.DEFAULT_USERNAME, self.course_id) self.DEFAULT_USERNAME, course_id)
} }
self.assertDictEqual(json.loads(response.content), expected) self.assertDictEqual(json.loads(response.content), expected)
......
boto==2.42.0 # MIT boto==2.42.0 # MIT
Django==1.9.9 # BSD License Django==1.9.9 # BSD License
django-countries==4.0 # MIT
django-model-utils==2.5.2 # BSD django-model-utils==2.5.2 # BSD
djangorestframework==3.4.6 # BSD djangorestframework==3.4.6 # BSD
django-rest-swagger==0.3.8 # BSD django-rest-swagger==0.3.8 # BSD
djangorestframework-csv==1.4.1 # BSD djangorestframework-csv==1.4.1 # BSD
django-countries==4.0 # MIT django-storages==1.4.1 # BSD
edx-django-release-util==0.1.2
elasticsearch-dsl==0.0.11 # Apache 2.0 elasticsearch-dsl==0.0.11 # Apache 2.0
ordered-set==2.0.1 # MIT ordered-set==2.0.1 # MIT
# markdown is used by swagger for rendering the api docs # markdown is used by swagger for rendering the api docs
Markdown==2.6.6 # BSD Markdown==2.6.6 # BSD
-e git+https://github.com/edx/opaque-keys.git@d45d0bd8d64c69531be69178b9505b5d38806ce0#egg=opaque-keys edx-ccx-keys==0.2.1
django-storages==1.4.1 # BSD edx-django-release-util==0.1.2
edx-opaque-keys==0.4.0
# Test dependencies go here. # Test dependencies go here.
-r base.txt -r base.txt
coverage==4.2 coverage==4.2
ddt==1.1.0 ddt==1.1.1
diff-cover >= 0.9.9 diff-cover >= 0.9.9
django-dynamic-fixture==1.9.0 django-dynamic-fixture==1.9.0
django-nose==1.4.4 django-nose==1.4.4
......
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