class CoursesLTIAPITests(ComPAIRAPITestCase): def setUp(self): super(CoursesLTIAPITests, self).setUp() self.data = SimpleAssignmentTestData() self.lti_data = LTITestData() def test_delete_course(self): # test unlinking of lti contexts when course deleted course = self.data.get_course() url = '/api/courses/' + course.uuid lti_consumer = self.lti_data.get_consumer() lti_context1 = self.lti_data.create_context( lti_consumer, compair_course_id=course.id) lti_context2 = self.lti_data.create_context( lti_consumer, compair_course_id=course.id) lti_resource_link1 = self.lti_data.create_resource_link( lti_consumer, lti_context=lti_context2, compair_assignment=self.data.assignments[0]) lti_resource_link2 = self.lti_data.create_resource_link( lti_consumer, lti_context=lti_context2, compair_assignment=self.data.assignments[1]) with self.login(self.data.get_authorized_instructor().username): rv = self.client.delete(url) self.assert200(rv) self.assertEqual(course.uuid, rv.json['id']) self.assertIsNone(lti_context1.compair_course_id) self.assertIsNone(lti_context2.compair_course_id) self.assertIsNone(lti_resource_link1.compair_assignment_id) self.assertIsNone(lti_resource_link2.compair_assignment_id)
class CoursesLTIAPITests(ComPAIRAPITestCase): def setUp(self): super(CoursesLTIAPITests, self).setUp() self.data = SimpleAssignmentTestData() self.lti_data = LTITestData() def test_delete_course(self): # test unlinking of lti contexts when course deleted course = self.data.get_course() url = '/api/courses/' + course.uuid lti_consumer = self.lti_data.get_consumer() lti_context1 = self.lti_data.create_context( lti_consumer, compair_course_id=course.id ) lti_context2 = self.lti_data.create_context( lti_consumer, compair_course_id=course.id ) lti_resource_link1 = self.lti_data.create_resource_link( lti_consumer, lti_context=lti_context2, compair_assignment=self.data.assignments[0] ) lti_resource_link2 = self.lti_data.create_resource_link( lti_consumer, lti_context=lti_context2, compair_assignment=self.data.assignments[1] ) with self.login(self.data.get_authorized_instructor().username): rv = self.client.delete(url) self.assert200(rv) self.assertEqual(course.uuid, rv.json['id']) self.assertIsNone(lti_context1.compair_course_id) self.assertIsNone(lti_context2.compair_course_id) self.assertIsNone(lti_resource_link1.compair_assignment_id) self.assertIsNone(lti_resource_link2.compair_assignment_id)
class AnswerLearningRecordTests(ComPAIRLearningRecordTestCase): def setUp(self): super(ComPAIRLearningRecordTestCase, self).setUp() self.data = SimpleAnswersTestData() self.lti_data = LTITestData() self.user = self.data.authorized_student self.setup_session_data(self.user) self.course = self.data.main_course self.lti_context = self.lti_data.create_context( self.lti_data.lti_consumer, compair_course_id=self.course.id, lis_course_offering_sourcedid="sis_course_id", lis_course_section_sourcedid="sis_section_id", ) self.assignment = self.data.assignments[0] self.criterion = self.assignment.criteria[0] self.answer = self.data.create_answer(self.assignment, self.user) self.expected_caliper_course = { 'academicSession': self.course.term, 'dateCreated': self.course.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.course.modified.replace(tzinfo=pytz.utc).isoformat(), 'id': "https://localhost:8888/app/course/" + self.course.uuid, 'name': self.course.name, 'type': 'CourseOffering', 'extensions': { 'ltiContexts': [{ 'context_id': self.lti_context.context_id, 'oauth_consumer_key': self.lti_data.lti_consumer.oauth_consumer_key, 'lis_course_offering_sourcedid': "sis_course_id", 'lis_course_section_sourcedid': "sis_section_id", }] } } self.expected_caliper_assignment = { 'name': self.assignment.name, 'type': 'Assessment', 'dateCreated': self.assignment.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.assignment.modified.replace(tzinfo=pytz.utc).isoformat(), 'dateToStartOn': self.assignment.answer_start.replace(tzinfo=pytz.utc).isoformat(), 'description': self.assignment.description, 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid, 'isPartOf': self.expected_caliper_course, 'items': [{ 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/question", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/comparison/question/1", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/evaluation/question/1", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/evaluation/question/2", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/comparison/question/2", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/evaluation/question/3", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/evaluation/question/4", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/comparison/question/3", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/evaluation/question/5", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/evaluation/question/6", 'type': 'AssessmentItem' }], } self.expected_caliper_assignment_question = { 'name': self.assignment.name, 'type': 'AssessmentItem', 'dateCreated': self.assignment.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.assignment.modified.replace(tzinfo=pytz.utc).isoformat(), 'dateToStartOn': self.assignment.answer_start.replace(tzinfo=pytz.utc).isoformat(), 'dateToSubmit': self.assignment.answer_end.replace(tzinfo=pytz.utc).isoformat(), 'description': self.assignment.description, 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/question", 'isPartOf': self.expected_caliper_assignment, } self.expected_caliper_answer_attempt = { 'assignable': self.expected_caliper_assignment_question, 'assignee': self.get_compair_caliper_actor(self.user), 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/question/attempt/" + self.answer.attempt_uuid, 'duration': "PT05M00S", 'startedAtTime': self.answer.attempt_started.replace(tzinfo=pytz.utc).isoformat(), 'endedAtTime': self.answer.attempt_ended.replace(tzinfo=pytz.utc).isoformat(), 'type': 'Attempt' } self.expected_caliper_answer = { 'attempt': self.expected_caliper_answer_attempt, 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/answer/" + self.answer.uuid, 'type': 'Response', 'dateCreated': self.answer.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.answer.modified.replace(tzinfo=pytz.utc).isoformat(), 'extensions': { 'characterCount': len(self.answer.content), 'content': self.answer.content, 'isDraft': False, 'wordCount': 8, 'scoreDetails': { 'algorithm': self.assignment.scoring_algorithm.value, 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0, 'criteria': { "https://localhost:8888/app/criterion/" + self.criterion.uuid: { 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0 }, } }, } } self.expected_xapi_course = { 'id': "https://localhost:8888/app/course/" + self.course.uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/course', 'name': { 'en-US': self.course.name } }, 'objectType': 'Activity' } self.expected_xapi_sis_course = { 'id': 'https://localhost:8888/course/' + self.lti_context.lis_course_offering_sourcedid, 'objectType': 'Activity' } self.expected_xapi_sis_section = { 'id': 'https://localhost:8888/course/' + self.lti_context.lis_course_offering_sourcedid + '/section/' + self.lti_context.lis_course_section_sourcedid, 'objectType': 'Activity' } self.expected_xapi_assignment = { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/assessment', 'name': { 'en-US': self.assignment.name }, 'description': { 'en-US': self.assignment.description }, }, 'objectType': 'Activity' } self.expected_xapi_assignment_question = { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/question", 'definition': { 'type': 'http://adlnet.gov/expapi/activities/question', 'name': { 'en-US': self.assignment.name }, 'description': { 'en-US': self.assignment.description }, }, 'objectType': 'Activity' } self.expected_xapi_answer_attempt = { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/question/attempt/" + self.answer.attempt_uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/attempt', 'extensions': { 'http://id.tincanapi.com/extension/attempt': { 'duration': "PT05M00S", 'startedAtTime': self.answer.attempt_started.replace( tzinfo=pytz.utc).isoformat(), 'endedAtTime': self.answer.attempt_ended.replace( tzinfo=pytz.utc).isoformat(), } } }, 'objectType': 'Activity' } self.expected_xapi_answer = { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/answer/" + self.answer.uuid, 'definition': { 'type': 'http://id.tincanapi.com/activitytype/solution', 'extensions': { 'http://id.tincanapi.com/extension/isDraft': False } }, 'objectType': 'Activity' } def test_on_answer_create(self): for draft in [True, False]: self.answer.draft = draft db.session.commit() self.expected_xapi_answer['definition']['extensions'][ 'http://id.tincanapi.com/extension/isDraft'] = draft self.expected_caliper_answer['extensions']['isDraft'] = draft self.expected_caliper_answer[ 'dateModified'] = self.answer.modified.replace( tzinfo=pytz.utc).isoformat() # test without tracking on_answer_create.send(current_app._get_current_object(), event_name=on_answer_create.name, user=self.user, answer=self.answer) events = self.get_and_clear_caliper_event_log() expected_caliper_events = [{ 'action': 'Completed', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_assignment_question, 'generated': self.expected_caliper_answer, 'session': self.get_caliper_session( self.get_compair_caliper_actor(self.user)), 'type': 'AssessmentItemEvent' }, { 'action': 'Submitted', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_assignment, 'generated': { 'assignable': self.expected_caliper_assignment, 'assignee': self.get_compair_caliper_actor(self.user), 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/attempt/" + self.answer.attempt_uuid, 'duration': "PT05M00S", 'startedAtTime': self.answer.attempt_started.replace( tzinfo=pytz.utc).isoformat(), 'endedAtTime': self.answer.attempt_ended.replace( tzinfo=pytz.utc).isoformat(), 'type': 'Attempt' }, 'session': self.get_caliper_session( self.get_compair_caliper_actor(self.user)), 'type': 'AssessmentEvent' }] self.assertEqual(len(events), len(expected_caliper_events)) for index, expected_event in enumerate(expected_caliper_events): self.assertEqual(events[index], expected_event) statements = self.get_and_clear_xapi_statement_log() expected_xapi_statements = [{ "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://adlnet.gov/expapi/verbs/completed', 'display': { 'en-US': 'completed' } }, "object": self.expected_xapi_answer, "context": { 'registration': self.answer.attempt_uuid, 'contextActivities': { 'parent': [ self.expected_xapi_assignment_question, self.expected_xapi_answer_attempt ], 'grouping': [ self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section ] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } }, "result": { 'success': True, 'duration': "PT05M00S", 'completion': not draft, 'response': self.answer.content, 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/character-count': len(self.answer.content), 'http://xapi.learninganalytics.ubc.ca/extension/word-count': len(self.answer.content.split(" ")) } } }, { "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://activitystrea.ms/schema/1.0/submit', 'display': { 'en-US': 'submitted' } }, "object": { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/attempt/" + self.answer.attempt_uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/attempt', 'extensions': { 'http://id.tincanapi.com/extension/attempt': { 'duration': "PT05M00S", 'startedAtTime': self.answer.attempt_started.replace( tzinfo=pytz.utc).isoformat(), 'endedAtTime': self.answer.attempt_ended.replace( tzinfo=pytz.utc).isoformat(), } } }, 'objectType': 'Activity' }, "context": { 'registration': self.answer.attempt_uuid, 'contextActivities': { 'parent': [self.expected_xapi_assignment], 'grouping': [ self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section ] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } }, "result": { 'success': True, 'completion': not draft } }] self.assertEqual(len(statements), len(expected_xapi_statements)) for index, expected_statement in enumerate( expected_xapi_statements): self.assertEqual(statements[index], expected_statement) def test_on_answer_modified(self): for draft in [True, False]: self.answer.draft = draft db.session.commit() self.expected_xapi_answer['definition']['extensions'][ 'http://id.tincanapi.com/extension/isDraft'] = draft self.expected_caliper_answer['extensions']['isDraft'] = draft self.expected_caliper_answer[ 'dateModified'] = self.answer.modified.replace( tzinfo=pytz.utc).isoformat() # test without tracking on_answer_modified.send(current_app._get_current_object(), event_name=on_answer_modified.name, user=self.user, answer=self.answer) events = self.get_and_clear_caliper_event_log() expected_caliper_events = [{ 'action': 'Completed', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_assignment_question, 'generated': self.expected_caliper_answer, 'session': self.get_caliper_session( self.get_compair_caliper_actor(self.user)), 'type': 'AssessmentItemEvent' }, { 'action': 'Submitted', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_assignment, 'generated': { 'assignable': self.expected_caliper_assignment, 'assignee': self.get_compair_caliper_actor(self.user), 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/attempt/" + self.answer.attempt_uuid, 'duration': "PT05M00S", 'startedAtTime': self.answer.attempt_started.replace( tzinfo=pytz.utc).isoformat(), 'endedAtTime': self.answer.attempt_ended.replace( tzinfo=pytz.utc).isoformat(), 'type': 'Attempt' }, 'session': self.get_caliper_session( self.get_compair_caliper_actor(self.user)), 'type': 'AssessmentEvent' }] self.assertEqual(len(events), len(expected_caliper_events)) for index, expected_event in enumerate(expected_caliper_events): self.assertEqual(events[index], expected_event) statements = self.get_and_clear_xapi_statement_log() expected_xapi_statements = [{ "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://adlnet.gov/expapi/verbs/completed', 'display': { 'en-US': 'completed' } }, "object": self.expected_xapi_answer, "context": { 'registration': self.answer.attempt_uuid, 'contextActivities': { 'parent': [ self.expected_xapi_assignment_question, self.expected_xapi_answer_attempt ], 'grouping': [ self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section ] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } }, "result": { 'success': True, 'duration': "PT05M00S", 'completion': not draft, 'response': self.answer.content, 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/character-count': len(self.answer.content), 'http://xapi.learninganalytics.ubc.ca/extension/word-count': len(self.answer.content.split(" ")) } } }, { "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://activitystrea.ms/schema/1.0/submit', 'display': { 'en-US': 'submitted' } }, "object": { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/attempt/" + self.answer.attempt_uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/attempt', 'extensions': { 'http://id.tincanapi.com/extension/attempt': { 'duration': "PT05M00S", 'startedAtTime': self.answer.attempt_started.replace( tzinfo=pytz.utc).isoformat(), 'endedAtTime': self.answer.attempt_ended.replace( tzinfo=pytz.utc).isoformat(), } } }, 'objectType': 'Activity' }, "context": { 'registration': self.answer.attempt_uuid, 'contextActivities': { 'parent': [self.expected_xapi_assignment], 'grouping': [ self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section ] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } }, "result": { 'success': True, 'completion': not draft } }] self.assertEqual(len(statements), len(expected_xapi_statements)) for index, expected_statement in enumerate( expected_xapi_statements): self.assertEqual(statements[index], expected_statement) def test_on_answer_delete(self): # send delete on_answer_delete.send(current_app._get_current_object(), event_name=on_answer_delete.name, user=self.user, answer=self.answer) events = self.get_and_clear_caliper_event_log() expected_caliper_event = { 'action': 'Deleted', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_answer, 'session': self.get_caliper_session(self.get_compair_caliper_actor( self.user)), 'type': 'Event' } self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) statements = self.get_and_clear_xapi_statement_log() expected_xapi_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://activitystrea.ms/schema/1.0/delete', 'display': { 'en-US': 'deleted' } }, "object": self.expected_xapi_answer, "context": { 'contextActivities': { 'parent': [ self.expected_xapi_assignment_question, self.expected_xapi_answer_attempt ], 'grouping': [ self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section ] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } }, } self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement)
class FileLearningRecordTests(ComPAIRLearningRecordTestCase): def setUp(self): super(ComPAIRLearningRecordTestCase, self).setUp() self.data = SimpleAssignmentTestData() self.lti_data = LTITestData() self.user = self.data.authorized_student self.setup_session_data(self.user) self.course = self.data.main_course self.lti_context = self.lti_data.create_context( self.lti_data.lti_consumer, compair_course_id=self.course.id, lis_course_offering_sourcedid="sis_course_id", lis_course_section_sourcedid="sis_section_id", ) self.assignment = self.data.assignments[0] self.answer = AnswerFactory(assignment=self.assignment, user=self.user) db.session.commit() self.expected_caliper_course = { 'academicSession': self.course.term, 'dateCreated': self.course.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.course.modified.replace(tzinfo=pytz.utc).isoformat(), 'id': "https://localhost:8888/app/course/" + self.course.uuid, 'name': self.course.name, 'type': 'CourseOffering', 'extensions': { 'ltiContexts': [{ 'context_id': self.lti_context.context_id, 'oauth_consumer_key': self.lti_data.lti_consumer.oauth_consumer_key, 'lis_course_offering_sourcedid': "sis_course_id", 'lis_course_section_sourcedid': "sis_section_id", }] } } self.expected_caliper_assignment = { 'name': self.assignment.name, 'type': 'Assessment', 'dateCreated': self.assignment.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.assignment.modified.replace(tzinfo=pytz.utc).isoformat(), 'dateToStartOn': self.assignment.answer_start.replace(tzinfo=pytz.utc).isoformat(), 'description': self.assignment.description, 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid, 'isPartOf': self.expected_caliper_course, 'items': [{ 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/question", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/comparison/question/1", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/evaluation/question/1", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/evaluation/question/2", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/comparison/question/2", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/evaluation/question/3", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/evaluation/question/4", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/comparison/question/3", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/evaluation/question/5", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/evaluation/question/6", 'type': 'AssessmentItem' }], } self.expected_caliper_assignment_question = { 'name': self.assignment.name, 'type': 'AssessmentItem', 'dateCreated': self.assignment.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.assignment.modified.replace(tzinfo=pytz.utc).isoformat(), 'dateToStartOn': self.assignment.answer_start.replace(tzinfo=pytz.utc).isoformat(), 'dateToSubmit': self.assignment.answer_end.replace(tzinfo=pytz.utc).isoformat(), 'description': self.assignment.description, 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/question", 'isPartOf': self.expected_caliper_assignment, } self.expected_caliper_answer_attempt = { 'assignable': self.expected_caliper_assignment_question, 'assignee': self.get_compair_caliper_actor(self.user), 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/question/attempt/" + self.answer.attempt_uuid, 'duration': "PT05M00S", 'startedAtTime': self.answer.attempt_started.replace(tzinfo=pytz.utc).isoformat(), 'endedAtTime': self.answer.attempt_ended.replace(tzinfo=pytz.utc).isoformat(), 'type': 'Attempt' } self.expected_caliper_answer = { 'attempt': self.expected_caliper_answer_attempt, 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/answer/" + self.answer.uuid, 'type': 'Response', 'dateCreated': self.answer.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.answer.modified.replace(tzinfo=pytz.utc).isoformat(), 'extensions': { 'characterCount': len(self.answer.content), 'content': self.answer.content, 'isDraft': False, 'wordCount': 8, } } self.expected_xapi_course = { 'id': "https://localhost:8888/app/course/" + self.course.uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/course', 'name': { 'en-US': self.course.name } }, 'objectType': 'Activity' } self.expected_xapi_sis_course = { 'id': 'https://localhost:8888/course/' + self.lti_context.lis_course_offering_sourcedid, 'objectType': 'Activity' } self.expected_xapi_sis_section = { 'id': 'https://localhost:8888/course/' + self.lti_context.lis_course_offering_sourcedid + '/section/' + self.lti_context.lis_course_section_sourcedid, 'objectType': 'Activity' } self.expected_xapi_assignment = { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/assessment', 'name': { 'en-US': self.assignment.name }, 'description': { 'en-US': self.assignment.description }, }, 'objectType': 'Activity' } self.expected_xapi_assignment_question = { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/question", 'definition': { 'type': 'http://adlnet.gov/expapi/activities/question', 'name': { 'en-US': self.assignment.name }, 'description': { 'en-US': self.assignment.description }, }, 'objectType': 'Activity' } self.expected_xapi_answer = { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/answer/" + self.answer.uuid, 'definition': { 'type': 'http://id.tincanapi.com/activitytype/solution', 'extensions': { 'http://id.tincanapi.com/extension/isDraft': False } }, 'objectType': 'Activity' } def test_on_get_file(self): # not report or attachment on_get_file.send(current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="none", file_name="some_file") events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 0) statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 0) # test report on_get_file.send(current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="report", file_name="some_report.csv") expected_caliper_object = { "id": 'https://localhost:8888/app/report/some_report.csv', "type": "Document", "name": "some_report.csv", "mediaType": "text/csv" } expected_caliper_event = { 'action': 'Viewed', 'actor': self.get_compair_caliper_actor(self.user), 'object': expected_caliper_object, 'session': self.get_caliper_session(self.get_compair_caliper_actor( self.user)), 'type': 'ViewEvent' } events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_object = { 'id': 'https://localhost:8888/app/report/some_report.csv', 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/file', 'name': { 'en-US': 'some_report.csv' }, 'extensions': { 'http://id.tincanapi.com/extension/mime-type': "text/csv" } }, 'objectType': 'Activity' } expected_xapi_verb = { 'id': 'http://id.tincanapi.com/verb/downloaded', 'display': { 'en-US': 'downloaded' } } expected_xapi_context = { 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_xapi_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": expected_xapi_verb, "object": expected_xapi_object, "context": expected_xapi_context } statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement) # test attachment without file record on_get_file.send(current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="attachment", file_name="some_file") events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 0) statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 0) # test attachment file record (not linked to assignments or answers) file_record = self.data.create_file(self.user) on_get_file.send(current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="attachment", file_name=file_record.name) events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 0) statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 0) # test attachment file record (assignment) self.assignment.file_id = file_record.id db.session.commit() on_get_file.send(current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="attachment", file_name=file_record.name) expected_caliper_object = { "id": 'https://localhost:8888/app/attachment/' + file_record.name, "type": "Document", "name": file_record.alias, "mediaType": 'application/pdf', "isPartOf": self.expected_caliper_assignment, "dateCreated": file_record.created.replace(tzinfo=pytz.utc).isoformat(), "dateModified": file_record.modified.replace(tzinfo=pytz.utc).isoformat() } self.expected_caliper_assignment[ 'dateModified'] = self.assignment.modified.replace( tzinfo=pytz.utc).isoformat() expected_caliper_event['object'] = expected_caliper_object expected_caliper_event['membership'] = self.get_caliper_membership( self.course, self.user, self.lti_context) events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_object = { 'id': 'https://localhost:8888/app/attachment/' + file_record.name, 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/file', 'name': { 'en-US': file_record.alias }, 'extensions': { 'http://id.tincanapi.com/extension/mime-type': 'application/pdf' } }, 'objectType': 'Activity' } expected_xapi_context = { 'contextActivities': { 'parent': [self.expected_xapi_assignment], 'grouping': [ self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section ] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_xapi_statement['object'] = expected_xapi_object expected_xapi_statement['context'] = expected_xapi_context statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement) # test attachment file record (answer) self.assignment.file_id = None self.answer.file_id = file_record.id db.session.commit() on_get_file.send(current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="attachment", file_name=file_record.name) self.expected_caliper_assignment_question[ 'dateModified'] = self.assignment.modified.replace( tzinfo=pytz.utc).isoformat() self.expected_caliper_assignment[ 'dateModified'] = self.assignment.modified.replace( tzinfo=pytz.utc).isoformat() self.expected_caliper_answer[ 'dateModified'] = self.answer.modified.replace( tzinfo=pytz.utc).isoformat() expected_caliper_object["isPartOf"] = self.expected_caliper_answer events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_context = { 'contextActivities': { 'parent': [self.expected_xapi_answer], 'grouping': [ self.expected_xapi_assignment_question, self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section ] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_xapi_statement['context'] = expected_xapi_context statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement) def test_on_attach_file(self): file_record = self.data.create_file(self.user) self.assignment.file_id = file_record.id db.session.commit() # attache to assignment on_attach_file.send( current_app._get_current_object(), event_name=on_attach_file.name, user=self.user, file=file_record, ) expected_caliper_object = { "id": 'https://localhost:8888/app/attachment/' + file_record.name, "type": "Document", "name": file_record.alias, "mediaType": 'application/pdf', "isPartOf": self.expected_caliper_assignment, "dateCreated": file_record.created.replace(tzinfo=pytz.utc).isoformat(), "dateModified": file_record.modified.replace(tzinfo=pytz.utc).isoformat() } self.expected_caliper_assignment[ 'dateModified'] = self.assignment.modified.replace( tzinfo=pytz.utc).isoformat() expected_caliper_event = { 'action': 'Attached', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': expected_caliper_object, 'session': self.get_caliper_session(self.get_compair_caliper_actor( self.user)), 'type': 'Event' } events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_verb = { 'id': 'http://activitystrea.ms/schema/1.0/attach', 'display': { 'en-US': 'attached' } } expected_xapi_object = { 'id': 'https://localhost:8888/app/attachment/' + file_record.name, 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/file', 'name': { 'en-US': file_record.alias }, 'extensions': { 'http://id.tincanapi.com/extension/mime-type': 'application/pdf' } }, 'objectType': 'Activity' } expected_xapi_context = { 'contextActivities': { 'parent': [self.expected_xapi_assignment], 'grouping': [ self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section ] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_xapi_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": expected_xapi_verb, "object": expected_xapi_object, "context": expected_xapi_context } statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement) # attach to answer self.assignment.file_id = None self.answer.file_id = file_record.id db.session.commit() on_attach_file.send( current_app._get_current_object(), event_name=on_attach_file.name, user=self.user, file=file_record, ) self.expected_caliper_assignment_question[ 'dateModified'] = self.assignment.modified.replace( tzinfo=pytz.utc).isoformat() self.expected_caliper_assignment[ 'dateModified'] = self.assignment.modified.replace( tzinfo=pytz.utc).isoformat() self.expected_caliper_answer[ 'dateModified'] = self.answer.modified.replace( tzinfo=pytz.utc).isoformat() expected_caliper_object["isPartOf"] = self.expected_caliper_answer events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_context = { 'contextActivities': { 'parent': [self.expected_xapi_answer], 'grouping': [ self.expected_xapi_assignment_question, self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section ] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_xapi_statement['context'] = expected_xapi_context statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement) def test_on_detach_file(self): file_record = self.data.create_file(self.user) db.session.commit() # attache to assignment on_detach_file.send(current_app._get_current_object(), event_name=on_detach_file.name, user=self.user, file=file_record, assignment=self.assignment) expected_caliper_object = { "id": 'https://localhost:8888/app/attachment/' + file_record.name, "type": "Document", "name": file_record.alias, "mediaType": 'application/pdf', "isPartOf": self.expected_caliper_assignment, "dateCreated": file_record.created.replace(tzinfo=pytz.utc).isoformat(), "dateModified": file_record.modified.replace(tzinfo=pytz.utc).isoformat() } expected_caliper_event = { 'action': 'Removed', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': expected_caliper_object, 'session': self.get_caliper_session(self.get_compair_caliper_actor( self.user)), 'type': 'Event' } events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_verb = { 'id': 'http://activitystrea.ms/schema/1.0/delete', 'display': { 'en-US': 'deleted' } } expected_xapi_object = { 'id': 'https://localhost:8888/app/attachment/' + file_record.name, 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/file', 'name': { 'en-US': file_record.alias }, 'extensions': { 'http://id.tincanapi.com/extension/mime-type': 'application/pdf' } }, 'objectType': 'Activity' } expected_xapi_context = { 'contextActivities': { 'parent': [self.expected_xapi_assignment], 'grouping': [ self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section ] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_xapi_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": expected_xapi_verb, "object": expected_xapi_object, "context": expected_xapi_context } statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement) # attach to answer on_detach_file.send(current_app._get_current_object(), event_name=on_detach_file.name, user=self.user, file=file_record, answer=self.answer) expected_caliper_object["isPartOf"] = self.expected_caliper_answer events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_context = { 'contextActivities': { 'parent': [self.expected_xapi_answer], 'grouping': [ self.expected_xapi_assignment_question, self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section ] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_xapi_statement['context'] = expected_xapi_context statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement)
class ComparisonLearningRecordTests(ComPAIRLearningRecordTestCase): def setUp(self): super(ComPAIRLearningRecordTestCase, self).setUp() self.data = ComparisonTestData() self.lti_data = LTITestData() self.user = self.data.authorized_student self.setup_session_data(self.user) self.course = self.data.main_course self.lti_context = self.lti_data.create_context( self.lti_data.lti_consumer, compair_course_id=self.course.id, lis_course_offering_sourcedid="sis_course_id", lis_course_section_sourcedid="sis_section_id", ) self.assignment = self.data.assignments[0] self.criterion = self.assignment.criteria[0] self.answer1 = self.data.answers[0] self.answer2 = self.data.answers[1] self.example_comparison = ComparisonFactory( assignment=self.assignment, user=self.user, answer1_id=self.answer1.id, answer2_id=self.answer2.id, winner=None, completed=False ) self.example_comparison_criterion = ComparisonCriterionFactory( comparison=self.example_comparison, criterion=self.criterion, winner=WinningAnswer.answer1, ) self.comparison = ComparisonFactory( assignment=self.assignment, user=self.user, answer1_id=self.answer1.id, answer2_id=self.answer2.id, winner=None, completed=False ) self.comparison_criterion = ComparisonCriterionFactory( comparison=self.comparison, criterion=self.criterion, winner=WinningAnswer.answer1, ) db.session.commit() self.expected_caliper_course = { 'academicSession': self.course.term, 'dateCreated': self.course.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.course.modified.replace(tzinfo=pytz.utc).isoformat(), 'id': "https://localhost:8888/app/course/"+self.course.uuid, 'name': self.course.name, 'type': 'CourseOffering', 'extensions': { 'ltiContexts': [{ 'context_id': self.lti_context.context_id, 'oauth_consumer_key': self.lti_data.lti_consumer.oauth_consumer_key, 'lis_course_offering_sourcedid': "sis_course_id", 'lis_course_section_sourcedid': "sis_section_id", }] } } self.expected_caliper_assignment = { 'name': self.assignment.name, 'type': 'Assessment', 'dateCreated': self.assignment.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.assignment.modified.replace(tzinfo=pytz.utc).isoformat(), 'dateToStartOn': self.assignment.answer_start.replace(tzinfo=pytz.utc).isoformat(), 'description': self.assignment.description, 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid, 'isPartOf': self.expected_caliper_course, 'items': [{ 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/question", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/1", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/1", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/2", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/2", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/3", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/4", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/3", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/5", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/6", 'type': 'AssessmentItem' }], } self.expected_caliper_assignment_question = { 'name': self.assignment.name, 'type': 'AssessmentItem', 'dateCreated': self.assignment.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.assignment.modified.replace(tzinfo=pytz.utc).isoformat(), 'dateToStartOn': self.assignment.answer_start.replace(tzinfo=pytz.utc).isoformat(), 'dateToSubmit': self.assignment.answer_end.replace(tzinfo=pytz.utc).isoformat(), 'description': self.assignment.description, 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/question", 'isPartOf': self.expected_caliper_assignment, } self.expected_caliper_answer1_attempt = { 'assignable': self.expected_caliper_assignment_question, 'assignee': self.get_compair_caliper_actor(self.answer1.user), 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/question/attempt/"+self.answer1.attempt_uuid, 'duration': "PT05M00S", 'startedAtTime': self.answer1.attempt_started.replace(tzinfo=pytz.utc).isoformat(), 'endedAtTime': self.answer1.attempt_ended.replace(tzinfo=pytz.utc).isoformat(), 'type': 'Attempt' } self.expected_caliper_answer1 = { 'attempt': self.expected_caliper_answer1_attempt, 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/answer/"+self.answer1.uuid, 'type': 'Response', 'dateCreated': self.answer1.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.answer1.modified.replace(tzinfo=pytz.utc).isoformat(), 'extensions': { 'characterCount': len(self.answer1.content), 'content': self.answer1.content, 'isDraft': False, 'wordCount': 8, 'scoreDetails': { 'algorithm': self.assignment.scoring_algorithm.value, 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0, 'criteria': { "https://localhost:8888/app/criterion/"+self.criterion.uuid: { 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0 }, } }, } } self.expected_caliper_answer2_attempt = { 'assignable': self.expected_caliper_assignment_question, 'assignee': self.get_compair_caliper_actor(self.answer2.user), 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/question/attempt/"+self.answer2.attempt_uuid, 'duration': "PT05M00S", 'startedAtTime': self.answer2.attempt_started.replace(tzinfo=pytz.utc).isoformat(), 'endedAtTime': self.answer2.attempt_ended.replace(tzinfo=pytz.utc).isoformat(), 'type': 'Attempt' } self.expected_caliper_answer2 = { 'attempt': self.expected_caliper_answer2_attempt, 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/answer/"+self.answer2.uuid, 'type': 'Response', 'dateCreated': self.answer2.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.answer2.modified.replace(tzinfo=pytz.utc).isoformat(), 'extensions': { 'characterCount': len(self.answer2.content), 'content': self.answer2.content, 'isDraft': False, 'wordCount': 8, 'scoreDetails': { 'algorithm': self.assignment.scoring_algorithm.value, 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0, 'criteria': { "https://localhost:8888/app/criterion/"+self.criterion.uuid: { 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0 }, } }, } } self.expected_caliper_comparison_question = { 'name': "Assignment comparison #1", 'type': 'AssessmentItem', 'dateCreated': self.assignment.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.assignment.modified.replace(tzinfo=pytz.utc).isoformat(), 'dateToStartOn': self.assignment.answer_end.replace(tzinfo=pytz.utc).isoformat(), 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/1", 'isPartOf': self.expected_caliper_assignment, } self.expected_xapi_course = { 'id': "https://localhost:8888/app/course/"+self.course.uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/course', 'name': {'en-US': self.course.name} }, 'objectType': 'Activity' } self.expected_xapi_sis_course = { 'id': 'https://localhost:8888/course/'+self.lti_context.lis_course_offering_sourcedid, 'objectType': 'Activity' } self.expected_xapi_sis_section = { 'id': 'https://localhost:8888/course/'+self.lti_context.lis_course_offering_sourcedid+'/section/'+self.lti_context.lis_course_section_sourcedid, 'objectType': 'Activity' } self.expected_xapi_assignment = { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/assessment', 'name': {'en-US': self.assignment.name}, 'description': {'en-US': self.assignment.description}, }, 'objectType': 'Activity' } self.expected_xapi_assignment_question = { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/question", 'definition': { 'type': 'http://adlnet.gov/expapi/activities/question', 'name': {'en-US': self.assignment.name}, 'description': {'en-US': self.assignment.description}, }, 'objectType': 'Activity' } self.expected_xapi_answer1_attempt = { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/question/attempt/"+self.answer1.attempt_uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/attempt', 'extensions': { 'http://id.tincanapi.com/extension/attempt': { 'duration': "PT05M00S", 'startedAtTime': self.answer1.attempt_started.replace(tzinfo=pytz.utc).isoformat(), 'endedAtTime': self.answer1.attempt_ended.replace(tzinfo=pytz.utc).isoformat(), } } }, 'objectType': 'Activity' } self.expected_xapi_answer1 = { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/answer/"+self.answer1.uuid, 'definition': { 'type': 'http://id.tincanapi.com/activitytype/solution', 'extensions': { 'http://id.tincanapi.com/extension/isDraft': False } }, 'objectType': 'Activity' } self.expected_xapi_answer2_attempt = { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/question/attempt/"+self.answer2.attempt_uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/attempt', 'extensions': { 'http://id.tincanapi.com/extension/attempt': { 'duration': "PT05M00S", 'startedAtTime': self.answer2.attempt_started.replace(tzinfo=pytz.utc).isoformat(), 'endedAtTime': self.answer2.attempt_ended.replace(tzinfo=pytz.utc).isoformat(), } } }, 'objectType': 'Activity' } self.expected_xapi_answer2 = { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/answer/"+self.answer2.uuid, 'definition': { 'type': 'http://id.tincanapi.com/activitytype/solution', 'extensions': { 'http://id.tincanapi.com/extension/isDraft': False } }, 'objectType': 'Activity' } self.expected_xapi_comparison_question = { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/1", 'definition': { 'type': 'http://adlnet.gov/expapi/activities/question', 'name': {'en-US': "Assignment comparison #1"} }, 'objectType': 'Activity' } def test_on_comparison_update(self): completed_count = 0 for (is_comparison_example, comparison) in [(True, self.example_comparison), (False, self.comparison)]: for completed in [False, True]: comparison.completed = completed comparison.winner = WinningAnswer.answer1 if completed else None db.session.commit() on_comparison_update.send( current_app._get_current_object(), event_name=on_comparison_update.name, user=self.user, assignment=self.assignment, comparison=comparison, is_comparison_example=is_comparison_example ) if completed: completed_count += 1 current_comparison = completed_count if completed else completed_count + 1 self.expected_caliper_comparison_question['name'] = "Assignment comparison #"+str(current_comparison) self.expected_caliper_comparison_question['id'] = "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/"+str(current_comparison) self.expected_xapi_comparison_question['definition']['name']['en-US'] = "Assignment comparison #{}".format(current_comparison) self.expected_xapi_comparison_question['id'] = "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/"+str(current_comparison) expected_caliper_comparison_attempt = { 'assignable': self.expected_caliper_comparison_question, 'assignee': self.get_compair_caliper_actor(self.user), 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/"+str(current_comparison)+"/attempt/"+comparison.attempt_uuid, 'duration': "PT05M00S", 'startedAtTime': comparison.attempt_started.replace(tzinfo=pytz.utc).isoformat(), 'endedAtTime': comparison.attempt_ended.replace(tzinfo=pytz.utc).isoformat(), 'type': 'Attempt' } expected_caliper_comparison = { 'attempt': expected_caliper_comparison_attempt, 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/"+comparison.uuid, 'type': 'Response', 'dateCreated': comparison.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': comparison.modified.replace(tzinfo=pytz.utc).isoformat(), 'extensions': { 'pairingAlgorithm': self.comparison.pairing_algorithm.value, 'winner': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/answer/"+self.answer1.uuid if completed else "Undecided", 'criteria': { "https://localhost:8888/app/criterion/"+self.criterion.uuid: "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/answer/"+self.answer1.uuid, }, "answers": [ "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/answer/"+self.answer1.uuid, "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/answer/"+self.answer2.uuid, ], "completed": completed } } events = self.get_and_clear_caliper_event_log() expected_caliper_events = [{ 'action': 'Completed', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_comparison_question, 'generated': expected_caliper_comparison, 'session': self.get_caliper_session(self.get_compair_caliper_actor(self.user)), 'type': 'AssessmentItemEvent' }, { 'action': 'Submitted', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_assignment, 'generated': { 'assignable': self.expected_caliper_assignment, 'assignee': self.get_compair_caliper_actor(self.user), 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/attempt/"+comparison.attempt_uuid, 'duration': "PT05M00S", 'startedAtTime': comparison.attempt_started.replace(tzinfo=pytz.utc).isoformat(), 'endedAtTime': comparison.attempt_ended.replace(tzinfo=pytz.utc).isoformat(), 'type': 'Attempt' }, 'session': self.get_caliper_session(self.get_compair_caliper_actor(self.user)), 'type': 'AssessmentEvent' }] if not is_comparison_example and completed: expected_caliper_events.append({ 'action': 'Ranked', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_answer1, 'session': self.get_caliper_session(self.get_compair_caliper_actor(self.user)), 'type': 'Event' }) expected_caliper_events.append({ 'action': 'Ranked', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_answer2, 'session': self.get_caliper_session(self.get_compair_caliper_actor(self.user)), 'type': 'Event' }) self.assertEqual(len(events), len(expected_caliper_events)) for index, expected_event in enumerate(expected_caliper_events): self.assertEqual(events[index], expected_event) expected_xapi_comparison_attempt = { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/"+str(current_comparison)+"/attempt/"+comparison.attempt_uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/attempt', 'extensions': { 'http://id.tincanapi.com/extension/attempt': { 'duration': "PT05M00S", 'startedAtTime': comparison.attempt_started.replace(tzinfo=pytz.utc).isoformat(), 'endedAtTime': comparison.attempt_ended.replace(tzinfo=pytz.utc).isoformat(), } } }, 'objectType': 'Activity' } expected_xapi_comparison = { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/"+comparison.uuid, 'definition': { 'type': 'http://id.tincanapi.com/activitytype/solution', 'name': { 'en-US': "Assignment comparison" }, 'extensions': { 'http://id.tincanapi.com/extension/completed': completed } }, 'objectType': 'Activity' } statements = self.get_and_clear_xapi_statement_log() expected_xapi_statements = [{ "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://adlnet.gov/expapi/verbs/completed', 'display': {'en-US': 'completed'} }, "object": expected_xapi_comparison, "context": { 'registration': comparison.attempt_uuid, 'contextActivities': { 'parent': [self.expected_xapi_comparison_question, self.expected_xapi_answer1, self.expected_xapi_answer2, expected_xapi_comparison_attempt], 'grouping': [self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } }, "result": { 'success': True, 'duration': "PT05M00S", 'completion': completed, 'response': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/answer/"+self.answer1.uuid if completed else "Undecided", 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/criteria': { "https://localhost:8888/app/criterion/"+self.criterion.uuid: "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/answer/"+self.answer1.uuid, } } } }, { "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://activitystrea.ms/schema/1.0/submit', 'display': {'en-US': 'submitted'} }, "object": { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/attempt/"+comparison.attempt_uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/attempt', 'extensions': { 'http://id.tincanapi.com/extension/attempt': { 'duration': "PT05M00S", 'startedAtTime': comparison.attempt_started.replace(tzinfo=pytz.utc).isoformat(), 'endedAtTime': comparison.attempt_ended.replace(tzinfo=pytz.utc).isoformat(), } } }, 'objectType': 'Activity' }, "context": { 'registration': comparison.attempt_uuid, 'contextActivities': { 'parent': [self.expected_xapi_assignment], 'grouping': [self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } }, "result": { 'success': True, 'completion': completed } }] if not is_comparison_example and completed: expected_xapi_statements.append({ "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'https://w3id.org/xapi/dod-isd/verbs/ranked', 'display': {'en-US': 'ranked'} }, "object": self.expected_xapi_answer1, "context": { 'registration': comparison.attempt_uuid, 'contextActivities': { 'parent': [self.expected_xapi_assignment_question, self.expected_xapi_answer1_attempt], 'grouping': [self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } }, "result": { 'score': {'raw': 5.0}, 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/score-details': { 'algorithm': self.assignment.scoring_algorithm.value, 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0, 'criteria': { "https://localhost:8888/app/criterion/"+self.criterion.uuid: { 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0 }, } } } } }) expected_xapi_statements.append({ "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'https://w3id.org/xapi/dod-isd/verbs/ranked', 'display': {'en-US': 'ranked'} }, "object": self.expected_xapi_answer2, "context": { 'registration': comparison.attempt_uuid, 'contextActivities': { 'parent': [self.expected_xapi_assignment_question, self.expected_xapi_answer2_attempt], 'grouping': [self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } }, "result": { 'score': {'raw': 5.0}, 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/score-details': { 'algorithm': self.assignment.scoring_algorithm.value, 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0, 'criteria': { "https://localhost:8888/app/criterion/"+self.criterion.uuid: { 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0 }, } } } } }) self.assertEqual(len(statements), len(expected_xapi_statements)) for index, expected_statement in enumerate(expected_xapi_statements): self.assertEqual(statements[index], expected_statement)
class AssignmentLearningRecordTests(ComPAIRLearningRecordTestCase): def setUp(self): super(ComPAIRLearningRecordTestCase, self).setUp() self.data = SimpleAssignmentTestData() self.lti_data = LTITestData() self.user = self.data.authorized_student self.setup_session_data(self.user) self.course = self.data.main_course self.lti_context = self.lti_data.create_context( self.lti_data.lti_consumer, compair_course_id=self.course.id, lis_course_offering_sourcedid="sis_course_id", lis_course_section_sourcedid="sis_section_id", ) self.assignment = self.data.assignments[0] self.expected_caliper_course = { 'academicSession': self.course.term, 'dateCreated': self.course.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.course.modified.replace(tzinfo=pytz.utc).isoformat(), 'id': "https://localhost:8888/app/course/"+self.course.uuid, 'name': self.course.name, 'type': 'CourseOffering', 'extensions': { 'ltiContexts': [{ 'context_id': self.lti_context.context_id, 'oauth_consumer_key': self.lti_data.lti_consumer.oauth_consumer_key, 'lis_course_offering_sourcedid': "sis_course_id", 'lis_course_section_sourcedid': "sis_section_id", }] } } self.expected_caliper_assignment = { 'name': self.assignment.name, 'type': 'Assessment', 'dateCreated': self.assignment.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.assignment.modified.replace(tzinfo=pytz.utc).isoformat(), 'dateToStartOn': self.assignment.answer_start.replace(tzinfo=pytz.utc).isoformat(), 'description': self.assignment.description, 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid, 'isPartOf': self.expected_caliper_course, 'items': [{ 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/question", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/1", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/1", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/2", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/2", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/3", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/4", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/3", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/5", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/6", 'type': 'AssessmentItem' }], } self.expected_xapi_course = { 'id': "https://localhost:8888/app/course/"+self.course.uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/course', 'name': {'en-US': self.course.name} }, 'objectType': 'Activity' } self.expected_xapi_sis_course = { 'id': 'https://localhost:8888/course/'+self.lti_context.lis_course_offering_sourcedid, 'objectType': 'Activity' } self.expected_xapi_sis_section = { 'id': 'https://localhost:8888/course/'+self.lti_context.lis_course_offering_sourcedid+'/section/'+self.lti_context.lis_course_section_sourcedid, 'objectType': 'Activity' } self.expected_xapi_assignment = { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/assessment', 'name': {'en-US': self.assignment.name}, 'description': {'en-US': self.assignment.description}, }, 'objectType': 'Activity' } def test_on_assignment_create(self): on_assignment_create.send( current_app._get_current_object(), event_name=on_assignment_create.name, user=self.user, assignment=self.assignment ) events = self.get_and_clear_caliper_event_log() expected_caliper_event = { 'action': 'Created', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_assignment, 'session': self.get_caliper_session(self.get_compair_caliper_actor(self.user)), 'type': 'Event' } self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) statements = self.get_and_clear_xapi_statement_log() expected_xapi_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://activitystrea.ms/schema/1.0/author', 'display': {'en-US': 'authored'} }, "object": self.expected_xapi_assignment, "context": { 'contextActivities': { 'parent': [self.expected_xapi_course], 'grouping': [self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } } self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement) def test_on_assignment_modified(self): on_assignment_modified.send( current_app._get_current_object(), event_name=on_assignment_modified.name, user=self.user, assignment=self.assignment ) events = self.get_and_clear_caliper_event_log() expected_caliper_event = { 'action': 'Modified', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_assignment, 'session': self.get_caliper_session(self.get_compair_caliper_actor(self.user)), 'type': 'Event' } self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) statements = self.get_and_clear_xapi_statement_log() expected_xapi_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://activitystrea.ms/schema/1.0/update', 'display': {'en-US': 'updated'} }, "object": self.expected_xapi_assignment, "context": { 'contextActivities': { 'parent': [self.expected_xapi_course], 'grouping': [self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } } self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement) def test_on_assignment_delete(self): on_assignment_delete.send( current_app._get_current_object(), event_name=on_assignment_delete.name, user=self.user, assignment=self.assignment ) events = self.get_and_clear_caliper_event_log() expected_caliper_event = { 'action': 'Deleted', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_assignment, 'session': self.get_caliper_session(self.get_compair_caliper_actor(self.user)), 'type': 'Event' } self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) statements = self.get_and_clear_xapi_statement_log() expected_xapi_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://activitystrea.ms/schema/1.0/delete', 'display': {'en-US': 'deleted'} }, "object": self.expected_xapi_assignment, "context": { 'contextActivities': { 'parent': [self.expected_xapi_course], 'grouping': [self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } } self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement)
class RemoteLearningRecordTests(ComPAIRLearningRecordTestCase): def setUp(self): super(ComPAIRLearningRecordTestCase, self).setUp() self.app.config[ 'LRS_XAPI_STATEMENT_ENDPOINT'] = 'http://example.com/xapi' self.app.config['LRS_XAPI_USERNAME'] = '******' self.app.config['LRS_XAPI_PASSWORD'] = '******' self.app.config['LRS_CALIPER_HOST'] = 'http://example.com/caliper' self.app.config['LRS_CALIPER_API_KEY'] = 'lrs_api_key' self.app.config['LRS_USER_INPUT_FIELD_SIZE_LIMIT'] = 200 # 200 bytes self.data = SimpleAnswersTestData() self.lti_data = LTITestData() self.user = self.data.authorized_student self.setup_session_data(self.user) self.course = self.data.main_course self.lti_context = self.lti_data.create_context( self.lti_data.lti_consumer, compair_course_id=self.course.id, lis_course_offering_sourcedid="sis_course_id", lis_course_section_sourcedid="sis_section_id", ) self.assignment = self.data.assignments[0] self.criterion = self.assignment.criteria[0] self.answer = self.data.create_answer(self.assignment, self.user) self.answer_comment = self.data.create_answer_comment( self.answer, self.user, AnswerCommentType.public) self.sent_xapi_statement = None self.sent_caliper_event = None self.character_limit = int( current_app.config.get('LRS_USER_INPUT_FIELD_SIZE_LIMIT') / len("c".encode('utf-8'))) @mock.patch('caliper.sensor.Sensor.send') def test_send_remote_caliper_event(self, mocked_send_event): self.app.config['XAPI_ENABLED'] = False def send_event_override(event): self.sent_caliper_event = json.loads(event.as_json()) return {} mocked_send_event.side_effect = send_event_override expected_assignment = { 'name': self.assignment.name, 'type': 'Assessment', 'dateCreated': self.assignment.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.assignment.modified.replace(tzinfo=pytz.utc).isoformat(), 'dateToStartOn': self.assignment.answer_start.replace(tzinfo=pytz.utc).isoformat(), 'description': self.assignment.description, 'id': "https://*****:*****@mock.patch('tincan.RemoteLRS.save_statement') def test_send_remote_xapi_statement(self, mocked_save_statement): self.app.config['CALIPER_ENABLED'] = False def save_statement_override(statement): self.sent_xapi_statement = json.loads( statement.to_json(XAPI._version)) return LRSResponse( success=True, request=None, response=None, data=json.dumps(["123"]), ) mocked_save_statement.side_effect = save_statement_override expected_course = { 'id': "https://localhost:8888/app/course/" + self.course.uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/course', 'name': { 'en-US': self.course.name } }, 'objectType': 'Activity' } expected_sis_course = { 'id': 'https://localhost:8888/course/' + self.lti_context.lis_course_offering_sourcedid, 'objectType': 'Activity' } expected_sis_section = { 'id': 'https://localhost:8888/course/' + self.lti_context.lis_course_offering_sourcedid + '/section/' + self.lti_context.lis_course_section_sourcedid, 'objectType': 'Activity' } expected_assignment = { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/assessment', 'name': { 'en-US': self.assignment.name }, 'description': { 'en-US': self.assignment.description }, }, 'objectType': 'Activity' } expected_assignment_question = { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/question", 'definition': { 'type': 'http://adlnet.gov/expapi/activities/question', 'name': { 'en-US': self.assignment.name }, 'description': { 'en-US': self.assignment.description }, }, 'objectType': 'Activity' } expected_attempt = { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/question/attempt/" + self.answer.attempt_uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/attempt', 'extensions': { 'http://id.tincanapi.com/extension/attempt': { 'duration': "PT05M00S", 'startedAtTime': self.answer.attempt_started.replace( tzinfo=pytz.utc).isoformat(), 'endedAtTime': self.answer.attempt_ended.replace( tzinfo=pytz.utc).isoformat(), } } }, 'objectType': 'Activity' } expected_answer = { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/answer/" + self.answer.uuid, 'definition': { 'type': 'http://id.tincanapi.com/activitytype/solution', 'extensions': { 'http://id.tincanapi.com/extension/isDraft': False } }, 'objectType': 'Activity' } expected_answer_comment = { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/answer/" + self.answer.uuid + "/comment/" + self.answer_comment.uuid, 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/comment', 'name': { 'en-US': "Assignment answer comment" }, 'extensions': { 'http://id.tincanapi.com/extension/isDraft': False, 'http://id.tincanapi.com/extension/type': self.answer_comment.comment_type.value } }, 'objectType': 'Activity' } expected_verb = { 'id': 'http://activitystrea.ms/schema/1.0/submit', 'display': { 'en-US': 'submitted' } } expected_context = { 'contextActivities': { 'parent': [expected_assignment_question, expected_attempt], 'grouping': [ expected_assignment, expected_course, expected_sis_course, expected_sis_section ] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_result = { 'success': True, 'response': self.answer.content, 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/character-count': len(self.answer.content), 'http://xapi.learninganalytics.ubc.ca/extension/word-count': len(self.answer.content.split(" ")) } } expected_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": expected_verb, "object": expected_answer, "context": expected_context, "result": expected_result } # test with answer normal content statement = XAPIStatement.generate( user=self.user, verb=XAPIVerb.generate('submitted'), object=XAPIObject.answer(self.answer), context=XAPIContext.answer(self.answer), result=XAPIResult.basic_content(self.answer.content, success=True)) XAPI._emit_to_lrs(json.loads(statement.to_json(XAPI._version))) self._validate_and_cleanup_xapi_statement(self.sent_xapi_statement) self.assertEqual(self.sent_xapi_statement, expected_statement) # test with extremely long answer content # content should be ~ LRS_USER_INPUT_FIELD_SIZE_LIMIT bytes long + 100 characters content = "c" * (self.character_limit + 100) self.answer.content = content db.session.commit() # expected_answer content should be <= LRS_USER_INPUT_FIELD_SIZE_LIMIT bytes long + " [TEXT TRIMMED]..." expected_result['response'] = ( "c" * self.character_limit) + " [TEXT TRIMMED]..." expected_result['extensions'][ 'http://xapi.learninganalytics.ubc.ca/extension/word-count'] = 1 expected_result['extensions'][ 'http://xapi.learninganalytics.ubc.ca/extension/character-count'] = len( content) statement = XAPIStatement.generate( user=self.user, verb=XAPIVerb.generate('submitted'), object=XAPIObject.answer(self.answer), context=XAPIContext.answer(self.answer), result=XAPIResult.basic_content(self.answer.content, success=True)) XAPI._emit_to_lrs(json.loads(statement.to_json(XAPI._version))) self._validate_and_cleanup_xapi_statement(self.sent_xapi_statement) self.assertEqual(self.sent_xapi_statement, expected_statement) expected_verb = { 'id': 'http://activitystrea.ms/schema/1.0/update', 'display': { 'en-US': 'updated' } } expected_result = { 'response': self.answer_comment.content, 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/character-count': len(self.answer_comment.content), 'http://xapi.learninganalytics.ubc.ca/extension/word-count': len(self.answer_comment.content.split(" ")) } } expected_context = { 'contextActivities': { 'parent': [expected_answer], 'grouping': [ expected_assignment_question, expected_assignment, expected_course, expected_sis_course, expected_sis_section ] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": expected_verb, "object": expected_answer_comment, "context": expected_context, "result": expected_result } # test with answer comment normal content statement = XAPIStatement.generate( user=self.user, verb=XAPIVerb.generate('updated'), object=XAPIObject.answer_comment(self.answer_comment), context=XAPIContext.answer_comment(self.answer_comment), result=XAPIResult.basic_content(self.answer_comment.content)) XAPI._emit_to_lrs(json.loads(statement.to_json(XAPI._version))) self._validate_and_cleanup_xapi_statement(self.sent_xapi_statement) self.assertEqual(self.sent_xapi_statement, expected_statement) # test with extremely long answer comment content # content should be ~ LRS_USER_INPUT_FIELD_SIZE_LIMIT bytes long + 100 characters content = "d" * (self.character_limit + 100) self.answer_comment.content = content db.session.commit() # expected_assignment name and description should be <= LRS_USER_INPUT_FIELD_SIZE_LIMIT bytes long + " [TEXT TRIMMED]..." expected_result['response'] = ( "d" * self.character_limit) + " [TEXT TRIMMED]..." expected_result['extensions'][ 'http://xapi.learninganalytics.ubc.ca/extension/word-count'] = 1 expected_result['extensions'][ 'http://xapi.learninganalytics.ubc.ca/extension/character-count'] = len( content) statement = XAPIStatement.generate( user=self.user, verb=XAPIVerb.generate('updated'), object=XAPIObject.answer_comment(self.answer_comment), context=XAPIContext.answer_comment(self.answer_comment), result=XAPIResult.basic_content(self.answer_comment.content)) XAPI._emit_to_lrs(json.loads(statement.to_json(XAPI._version))) self._validate_and_cleanup_xapi_statement(self.sent_xapi_statement) self.assertEqual(self.sent_xapi_statement, expected_statement) expected_context = { 'contextActivities': { 'parent': [expected_course], 'grouping': [expected_sis_course, expected_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": expected_verb, "object": expected_assignment, "context": expected_context } # test with assignment normal content statement = XAPIStatement.generate( user=self.user, verb=XAPIVerb.generate('updated'), object=XAPIObject.assignment(self.assignment), context=XAPIContext.assignment(self.assignment)) XAPI._emit_to_lrs(json.loads(statement.to_json(XAPI._version))) self._validate_and_cleanup_xapi_statement(self.sent_xapi_statement) self.assertEqual(self.sent_xapi_statement, expected_statement) # test with extremely long answer content # content should be ~ LRS_USER_INPUT_FIELD_SIZE_LIMIT bytes long + 100 characters name = "a" * (self.character_limit + 100) description = "b" * (self.character_limit + 100) self.assignment.name = name self.assignment.description = description db.session.commit() # expected_assignment name and description should be <= LRS_USER_INPUT_FIELD_SIZE_LIMIT bytes long + " [TEXT TRIMMED]..." expected_assignment['definition']['name']['en-US'] = ( "a" * self.character_limit) + " [TEXT TRIMMED]..." expected_assignment['definition']['description']['en-US'] = ( "b" * self.character_limit) + " [TEXT TRIMMED]..." statement = XAPIStatement.generate( user=self.user, verb=XAPIVerb.generate('updated'), object=XAPIObject.assignment(self.assignment), context=XAPIContext.assignment(self.assignment)) XAPI._emit_to_lrs(json.loads(statement.to_json(XAPI._version))) self._validate_and_cleanup_xapi_statement(self.sent_xapi_statement) self.assertEqual(self.sent_xapi_statement, expected_statement)
class FileLearningRecordTests(ComPAIRLearningRecordTestCase): def setUp(self): super(ComPAIRLearningRecordTestCase, self).setUp() self.data = SimpleAssignmentTestData() self.lti_data = LTITestData() self.user = self.data.authorized_student self.setup_session_data(self.user) self.course = self.data.main_course self.lti_context = self.lti_data.create_context( self.lti_data.lti_consumer, compair_course_id=self.course.id, lis_course_offering_sourcedid="sis_course_id", lis_course_section_sourcedid="sis_section_id", ) self.assignment = self.data.assignments[0] self.answer = AnswerFactory( assignment=self.assignment, user=self.user ) db.session.commit() self.expected_caliper_course = { 'academicSession': self.course.term, 'dateCreated': self.course.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.course.modified.replace(tzinfo=pytz.utc).isoformat(), 'id': "https://localhost:8888/app/course/"+self.course.uuid, 'name': self.course.name, 'type': 'CourseOffering', 'extensions': { 'ltiContexts': [{ 'context_id': self.lti_context.context_id, 'oauth_consumer_key': self.lti_data.lti_consumer.oauth_consumer_key, 'lis_course_offering_sourcedid': "sis_course_id", 'lis_course_section_sourcedid': "sis_section_id", }] } } self.expected_caliper_assignment = { 'name': self.assignment.name, 'type': 'Assessment', 'dateCreated': self.assignment.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.assignment.modified.replace(tzinfo=pytz.utc).isoformat(), 'dateToStartOn': self.assignment.answer_start.replace(tzinfo=pytz.utc).isoformat(), 'description': self.assignment.description, 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid, 'isPartOf': self.expected_caliper_course, 'items': [{ 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/question", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/1", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/1", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/2", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/2", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/3", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/4", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/3", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/5", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/6", 'type': 'AssessmentItem' }], } self.expected_caliper_assignment_question = { 'name': self.assignment.name, 'type': 'AssessmentItem', 'dateCreated': self.assignment.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.assignment.modified.replace(tzinfo=pytz.utc).isoformat(), 'dateToStartOn': self.assignment.answer_start.replace(tzinfo=pytz.utc).isoformat(), 'dateToSubmit': self.assignment.answer_end.replace(tzinfo=pytz.utc).isoformat(), 'description': self.assignment.description, 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/question", 'isPartOf': self.expected_caliper_assignment, } self.expected_caliper_answer_attempt = { 'assignable': self.expected_caliper_assignment_question, 'assignee': self.get_compair_caliper_actor(self.user), 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/question/attempt/"+self.answer.attempt_uuid, 'duration': "PT05M00S", 'startedAtTime': self.answer.attempt_started.replace(tzinfo=pytz.utc).isoformat(), 'endedAtTime': self.answer.attempt_ended.replace(tzinfo=pytz.utc).isoformat(), 'type': 'Attempt' } self.expected_caliper_answer = { 'attempt': self.expected_caliper_answer_attempt, 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/answer/"+self.answer.uuid, 'type': 'Response', 'dateCreated': self.answer.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.answer.modified.replace(tzinfo=pytz.utc).isoformat(), 'extensions': { 'characterCount': len(self.answer.content), 'content': self.answer.content, 'isDraft': False, 'wordCount': 8, } } self.expected_xapi_course = { 'id': "https://localhost:8888/app/course/"+self.course.uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/course', 'name': {'en-US': self.course.name} }, 'objectType': 'Activity' } self.expected_xapi_sis_course = { 'id': 'https://localhost:8888/course/'+self.lti_context.lis_course_offering_sourcedid, 'objectType': 'Activity' } self.expected_xapi_sis_section = { 'id': 'https://localhost:8888/course/'+self.lti_context.lis_course_offering_sourcedid+'/section/'+self.lti_context.lis_course_section_sourcedid, 'objectType': 'Activity' } self.expected_xapi_assignment = { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/assessment', 'name': {'en-US': self.assignment.name}, 'description': {'en-US': self.assignment.description}, }, 'objectType': 'Activity' } self.expected_xapi_assignment_question = { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/question", 'definition': { 'type': 'http://adlnet.gov/expapi/activities/question', 'name': {'en-US': self.assignment.name}, 'description': {'en-US': self.assignment.description}, }, 'objectType': 'Activity' } self.expected_xapi_answer = { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/answer/"+self.answer.uuid, 'definition': { 'type': 'http://id.tincanapi.com/activitytype/solution', 'extensions': { 'http://id.tincanapi.com/extension/isDraft': False } }, 'objectType': 'Activity' } def test_on_get_file(self): # not report or attachment on_get_file.send( current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="none", file_name="some_file" ) events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 0) statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 0) # test report on_get_file.send( current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="report", file_name="some_report.csv" ) expected_caliper_object = { "id": 'https://localhost:8888/app/report/some_report.csv', "type": "Document", "name": "some_report.csv", "mediaType": "text/csv" } expected_caliper_event = { 'action': 'Viewed', 'actor': self.get_compair_caliper_actor(self.user), 'object': expected_caliper_object, 'session': self.get_caliper_session(self.get_compair_caliper_actor(self.user)), 'type': 'ViewEvent' } events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_object = { 'id': 'https://localhost:8888/app/report/some_report.csv', 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/file', 'name': {'en-US': 'some_report.csv'}, 'extensions': { 'http://id.tincanapi.com/extension/mime-type': "text/csv" } }, 'objectType': 'Activity' } expected_xapi_verb = { 'id': 'http://id.tincanapi.com/verb/downloaded', 'display': {'en-US': 'downloaded'} } expected_xapi_context = { 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_xapi_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": expected_xapi_verb, "object": expected_xapi_object, "context": expected_xapi_context } statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement) # test attachment without file record on_get_file.send( current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="attachment", file_name="some_file" ) events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 0) statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 0) # test attachment file record (not linked to assignments or answers) file_record = self.data.create_file(self.user) on_get_file.send( current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="attachment", file_name=file_record.name ) events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 0) statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 0) # test attachment file record (assignment) self.assignment.file_id = file_record.id db.session.commit() on_get_file.send( current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="attachment", file_name=file_record.name ) expected_caliper_object = { "id": 'https://localhost:8888/app/attachment/'+file_record.name, "type": "Document", "name": file_record.alias, "mediaType": 'application/pdf', "isPartOf": self.expected_caliper_assignment, "dateCreated": file_record.created.replace(tzinfo=pytz.utc).isoformat(), "dateModified": file_record.modified.replace(tzinfo=pytz.utc).isoformat() } self.expected_caliper_assignment['dateModified'] = self.assignment.modified.replace(tzinfo=pytz.utc).isoformat() expected_caliper_event['object'] = expected_caliper_object expected_caliper_event['membership'] = self.get_caliper_membership(self.course, self.user, self.lti_context) events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_object = { 'id': 'https://localhost:8888/app/attachment/'+file_record.name, 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/file', 'name': {'en-US': file_record.alias}, 'extensions': { 'http://id.tincanapi.com/extension/mime-type': 'application/pdf' } }, 'objectType': 'Activity' } expected_xapi_context = { 'contextActivities': { 'parent': [self.expected_xapi_assignment], 'grouping': [self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_xapi_statement['object'] = expected_xapi_object expected_xapi_statement['context'] = expected_xapi_context statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement) # test attachment file record (answer) self.assignment.file_id = None self.answer.file_id = file_record.id db.session.commit() on_get_file.send( current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="attachment", file_name=file_record.name ) self.expected_caliper_assignment_question['dateModified'] = self.assignment.modified.replace(tzinfo=pytz.utc).isoformat() self.expected_caliper_assignment['dateModified'] = self.assignment.modified.replace(tzinfo=pytz.utc).isoformat() self.expected_caliper_answer['dateModified'] = self.answer.modified.replace(tzinfo=pytz.utc).isoformat() expected_caliper_object["isPartOf"] = self.expected_caliper_answer events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_context = { 'contextActivities': { 'parent': [self.expected_xapi_answer], 'grouping': [self.expected_xapi_assignment_question, self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_xapi_statement['context'] = expected_xapi_context statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement) def test_on_attach_file(self): file_record = self.data.create_file(self.user) self.assignment.file_id = file_record.id db.session.commit() # attache to assignment on_attach_file.send( current_app._get_current_object(), event_name=on_attach_file.name, user=self.user, file=file_record, ) expected_caliper_object = { "id": 'https://localhost:8888/app/attachment/'+file_record.name, "type": "Document", "name": file_record.alias, "mediaType": 'application/pdf', "isPartOf": self.expected_caliper_assignment, "dateCreated": file_record.created.replace(tzinfo=pytz.utc).isoformat(), "dateModified": file_record.modified.replace(tzinfo=pytz.utc).isoformat() } self.expected_caliper_assignment['dateModified'] = self.assignment.modified.replace(tzinfo=pytz.utc).isoformat() expected_caliper_event = { 'action': 'Attached', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': expected_caliper_object, 'session': self.get_caliper_session(self.get_compair_caliper_actor(self.user)), 'type': 'Event' } events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_verb = { 'id': 'http://activitystrea.ms/schema/1.0/attach', 'display': {'en-US': 'attached'} } expected_xapi_object = { 'id': 'https://localhost:8888/app/attachment/'+file_record.name, 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/file', 'name': {'en-US': file_record.alias}, 'extensions': { 'http://id.tincanapi.com/extension/mime-type': 'application/pdf' } }, 'objectType': 'Activity' } expected_xapi_context = { 'contextActivities': { 'parent': [self.expected_xapi_assignment], 'grouping': [self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_xapi_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": expected_xapi_verb, "object": expected_xapi_object, "context": expected_xapi_context } statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement) # attach to answer self.assignment.file_id = None self.answer.file_id = file_record.id db.session.commit() on_attach_file.send( current_app._get_current_object(), event_name=on_attach_file.name, user=self.user, file=file_record, ) self.expected_caliper_assignment_question['dateModified'] = self.assignment.modified.replace(tzinfo=pytz.utc).isoformat() self.expected_caliper_assignment['dateModified'] = self.assignment.modified.replace(tzinfo=pytz.utc).isoformat() self.expected_caliper_answer['dateModified'] = self.answer.modified.replace(tzinfo=pytz.utc).isoformat() expected_caliper_object["isPartOf"] = self.expected_caliper_answer events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_context = { 'contextActivities': { 'parent': [self.expected_xapi_answer], 'grouping': [self.expected_xapi_assignment_question, self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_xapi_statement['context'] = expected_xapi_context statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement) def test_on_detach_file(self): file_record = self.data.create_file(self.user) db.session.commit() # attache to assignment on_detach_file.send( current_app._get_current_object(), event_name=on_detach_file.name, user=self.user, file=file_record, assignment=self.assignment ) expected_caliper_object = { "id": 'https://localhost:8888/app/attachment/'+file_record.name, "type": "Document", "name": file_record.alias, "mediaType": 'application/pdf', "isPartOf": self.expected_caliper_assignment, "dateCreated": file_record.created.replace(tzinfo=pytz.utc).isoformat(), "dateModified": file_record.modified.replace(tzinfo=pytz.utc).isoformat() } expected_caliper_event = { 'action': 'Removed', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': expected_caliper_object, 'session': self.get_caliper_session(self.get_compair_caliper_actor(self.user)), 'type': 'Event' } events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_verb = { 'id': 'http://activitystrea.ms/schema/1.0/delete', 'display': {'en-US': 'deleted'} } expected_xapi_object = { 'id': 'https://localhost:8888/app/attachment/'+file_record.name, 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/file', 'name': {'en-US': file_record.alias}, 'extensions': { 'http://id.tincanapi.com/extension/mime-type': 'application/pdf' } }, 'objectType': 'Activity' } expected_xapi_context = { 'contextActivities': { 'parent': [self.expected_xapi_assignment], 'grouping': [self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_xapi_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": expected_xapi_verb, "object": expected_xapi_object, "context": expected_xapi_context } statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement) # attach to answer on_detach_file.send( current_app._get_current_object(), event_name=on_detach_file.name, user=self.user, file=file_record, answer=self.answer ) expected_caliper_object["isPartOf"] = self.expected_caliper_answer events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_context = { 'contextActivities': { 'parent': [self.expected_xapi_answer], 'grouping': [self.expected_xapi_assignment_question, self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_xapi_statement['context'] = expected_xapi_context statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement)
class CourseLearningRecordTests(ComPAIRLearningRecordTestCase): def setUp(self): super(ComPAIRLearningRecordTestCase, self).setUp() self.data = BasicTestData() self.lti_data = LTITestData() self.user = self.data.authorized_student self.setup_session_data(self.user) self.course = self.data.main_course self.lti_context = self.lti_data.create_context( self.lti_data.lti_consumer, compair_course_id=self.course.id, lis_course_offering_sourcedid="sis_course_id", lis_course_section_sourcedid="sis_section_id", ) self.expected_caliper_course = { 'academicSession': self.course.term, 'dateCreated': self.course.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.course.modified.replace(tzinfo=pytz.utc).isoformat(), 'id': "https://localhost:8888/app/course/" + self.course.uuid, 'name': self.course.name, 'type': 'CourseOffering', 'extensions': { 'ltiContexts': [{ 'context_id': self.lti_context.context_id, 'oauth_consumer_key': self.lti_data.lti_consumer.oauth_consumer_key, 'lis_course_offering_sourcedid': "sis_course_id", 'lis_course_section_sourcedid': "sis_section_id", }] } } self.expected_xapi_course = { 'id': "https://localhost:8888/app/course/" + self.course.uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/course', 'name': { 'en-US': self.course.name } }, 'objectType': 'Activity' } self.expected_xapi_sis_course = { 'id': 'https://localhost:8888/course/' + self.lti_context.lis_course_offering_sourcedid, 'objectType': 'Activity' } self.expected_xapi_sis_section = { 'id': 'https://localhost:8888/course/' + self.lti_context.lis_course_offering_sourcedid + '/section/' + self.lti_context.lis_course_section_sourcedid, 'objectType': 'Activity' } def test_on_course_create(self): on_course_create.send(current_app._get_current_object(), event_name=on_course_create.name, user=self.user, course=self.course) events = self.get_and_clear_caliper_event_log() expected_caliper_event = { 'action': 'Created', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_course, 'session': self.get_caliper_session(self.get_compair_caliper_actor( self.user)), 'type': 'Event' } self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) statements = self.get_and_clear_xapi_statement_log() expected_xapi_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://activitystrea.ms/schema/1.0/author', 'display': { 'en-US': 'authored' } }, "object": self.expected_xapi_course, "context": { 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } } self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement) def test_on_course_modified(self): on_course_modified.send(current_app._get_current_object(), event_name=on_course_modified.name, user=self.user, course=self.course) events = self.get_and_clear_caliper_event_log() expected_caliper_event = { 'action': 'Modified', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_course, 'session': self.get_caliper_session(self.get_compair_caliper_actor( self.user)), 'type': 'Event' } self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) statements = self.get_and_clear_xapi_statement_log() expected_xapi_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://activitystrea.ms/schema/1.0/update', 'display': { 'en-US': 'updated' } }, "object": self.expected_xapi_course, "context": { 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } } self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement) def test_on_course_delete(self): on_course_delete.send(current_app._get_current_object(), event_name=on_course_delete.name, user=self.user, course=self.course) events = self.get_and_clear_caliper_event_log() expected_caliper_event = { 'action': 'Deleted', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_course, 'session': self.get_caliper_session(self.get_compair_caliper_actor( self.user)), 'type': 'Event' } self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) statements = self.get_and_clear_xapi_statement_log() expected_xapi_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://activitystrea.ms/schema/1.0/delete', 'display': { 'en-US': 'deleted' } }, "object": self.expected_xapi_course, "context": { 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } } self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement)
class ComparisonLearningRecordTests(ComPAIRLearningRecordTestCase): def setUp(self): super(ComPAIRLearningRecordTestCase, self).setUp() self.data = ComparisonTestData() self.lti_data = LTITestData() self.user = self.data.authorized_student self.setup_session_data(self.user) self.course = self.data.main_course self.lti_context = self.lti_data.create_context( self.lti_data.lti_consumer, compair_course_id=self.course.id, lis_course_offering_sourcedid="sis_course_id", lis_course_section_sourcedid="sis_section_id", ) self.assignment = self.data.assignments[0] self.criterion = self.assignment.criteria[0] self.answer1 = self.data.answers[0] self.answer2 = self.data.answers[1] self.example_comparison = ComparisonFactory(assignment=self.assignment, user=self.user, answer1_id=self.answer1.id, answer2_id=self.answer2.id, winner=None, completed=False) self.example_comparison_criterion = ComparisonCriterionFactory( comparison=self.example_comparison, criterion=self.criterion, winner=WinningAnswer.answer1, ) self.comparison = ComparisonFactory(assignment=self.assignment, user=self.user, answer1_id=self.answer1.id, answer2_id=self.answer2.id, winner=None, completed=False) self.comparison_criterion = ComparisonCriterionFactory( comparison=self.comparison, criterion=self.criterion, winner=WinningAnswer.answer1, ) db.session.commit() self.expected_caliper_course = { 'academicSession': self.course.term, 'dateCreated': self.course.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.course.modified.replace(tzinfo=pytz.utc).isoformat(), 'id': "https://localhost:8888/app/course/" + self.course.uuid, 'name': self.course.name, 'type': 'CourseOffering', 'extensions': { 'ltiContexts': [{ 'context_id': self.lti_context.context_id, 'oauth_consumer_key': self.lti_data.lti_consumer.oauth_consumer_key, 'lis_course_offering_sourcedid': "sis_course_id", 'lis_course_section_sourcedid': "sis_section_id", }] } } self.expected_caliper_assignment = { 'name': self.assignment.name, 'type': 'Assessment', 'dateCreated': self.assignment.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.assignment.modified.replace(tzinfo=pytz.utc).isoformat(), 'dateToStartOn': self.assignment.answer_start.replace(tzinfo=pytz.utc).isoformat(), 'description': self.assignment.description, 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid, 'isPartOf': self.expected_caliper_course, 'items': [{ 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/question", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/comparison/question/1", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/evaluation/question/1", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/evaluation/question/2", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/comparison/question/2", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/evaluation/question/3", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/evaluation/question/4", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/comparison/question/3", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/evaluation/question/5", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/evaluation/question/6", 'type': 'AssessmentItem' }], } self.expected_caliper_assignment_question = { 'name': self.assignment.name, 'type': 'AssessmentItem', 'dateCreated': self.assignment.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.assignment.modified.replace(tzinfo=pytz.utc).isoformat(), 'dateToStartOn': self.assignment.answer_start.replace(tzinfo=pytz.utc).isoformat(), 'dateToSubmit': self.assignment.answer_end.replace(tzinfo=pytz.utc).isoformat(), 'description': self.assignment.description, 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/question", 'isPartOf': self.expected_caliper_assignment, } self.expected_caliper_answer1_attempt = { 'assignable': self.expected_caliper_assignment_question, 'assignee': self.get_compair_caliper_actor(self.answer1.user), 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/question/attempt/" + self.answer1.attempt_uuid, 'duration': "PT05M00S", 'startedAtTime': self.answer1.attempt_started.replace(tzinfo=pytz.utc).isoformat(), 'endedAtTime': self.answer1.attempt_ended.replace(tzinfo=pytz.utc).isoformat(), 'type': 'Attempt' } self.expected_caliper_answer1 = { 'attempt': self.expected_caliper_answer1_attempt, 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/answer/" + self.answer1.uuid, 'type': 'Response', 'dateCreated': self.answer1.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.answer1.modified.replace(tzinfo=pytz.utc).isoformat(), 'extensions': { 'characterCount': len(self.answer1.content), 'content': self.answer1.content, 'isDraft': False, 'wordCount': 8, 'scoreDetails': { 'algorithm': self.assignment.scoring_algorithm.value, 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0, 'criteria': { "https://localhost:8888/app/criterion/" + self.criterion.uuid: { 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0 }, } }, } } self.expected_caliper_answer2_attempt = { 'assignable': self.expected_caliper_assignment_question, 'assignee': self.get_compair_caliper_actor(self.answer2.user), 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/question/attempt/" + self.answer2.attempt_uuid, 'duration': "PT05M00S", 'startedAtTime': self.answer2.attempt_started.replace(tzinfo=pytz.utc).isoformat(), 'endedAtTime': self.answer2.attempt_ended.replace(tzinfo=pytz.utc).isoformat(), 'type': 'Attempt' } self.expected_caliper_answer2 = { 'attempt': self.expected_caliper_answer2_attempt, 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/answer/" + self.answer2.uuid, 'type': 'Response', 'dateCreated': self.answer2.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.answer2.modified.replace(tzinfo=pytz.utc).isoformat(), 'extensions': { 'characterCount': len(self.answer2.content), 'content': self.answer2.content, 'isDraft': False, 'wordCount': 8, 'scoreDetails': { 'algorithm': self.assignment.scoring_algorithm.value, 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0, 'criteria': { "https://localhost:8888/app/criterion/" + self.criterion.uuid: { 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0 }, } }, } } self.expected_caliper_comparison_question = { 'name': "Assignment comparison #1", 'type': 'AssessmentItem', 'dateCreated': self.assignment.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.assignment.modified.replace(tzinfo=pytz.utc).isoformat(), 'dateToStartOn': self.assignment.answer_end.replace(tzinfo=pytz.utc).isoformat(), 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/comparison/question/1", 'isPartOf': self.expected_caliper_assignment, } self.expected_xapi_course = { 'id': "https://localhost:8888/app/course/" + self.course.uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/course', 'name': { 'en-US': self.course.name } }, 'objectType': 'Activity' } self.expected_xapi_sis_course = { 'id': 'https://localhost:8888/course/' + self.lti_context.lis_course_offering_sourcedid, 'objectType': 'Activity' } self.expected_xapi_sis_section = { 'id': 'https://localhost:8888/course/' + self.lti_context.lis_course_offering_sourcedid + '/section/' + self.lti_context.lis_course_section_sourcedid, 'objectType': 'Activity' } self.expected_xapi_assignment = { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/assessment', 'name': { 'en-US': self.assignment.name }, 'description': { 'en-US': self.assignment.description }, }, 'objectType': 'Activity' } self.expected_xapi_assignment_question = { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/question", 'definition': { 'type': 'http://adlnet.gov/expapi/activities/question', 'name': { 'en-US': self.assignment.name }, 'description': { 'en-US': self.assignment.description }, }, 'objectType': 'Activity' } self.expected_xapi_answer1_attempt = { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/question/attempt/" + self.answer1.attempt_uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/attempt', 'extensions': { 'http://id.tincanapi.com/extension/attempt': { 'duration': "PT05M00S", 'startedAtTime': self.answer1.attempt_started.replace( tzinfo=pytz.utc).isoformat(), 'endedAtTime': self.answer1.attempt_ended.replace( tzinfo=pytz.utc).isoformat(), } } }, 'objectType': 'Activity' } self.expected_xapi_answer1 = { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/answer/" + self.answer1.uuid, 'definition': { 'type': 'http://id.tincanapi.com/activitytype/solution', 'extensions': { 'http://id.tincanapi.com/extension/isDraft': False } }, 'objectType': 'Activity' } self.expected_xapi_answer2_attempt = { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/question/attempt/" + self.answer2.attempt_uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/attempt', 'extensions': { 'http://id.tincanapi.com/extension/attempt': { 'duration': "PT05M00S", 'startedAtTime': self.answer2.attempt_started.replace( tzinfo=pytz.utc).isoformat(), 'endedAtTime': self.answer2.attempt_ended.replace( tzinfo=pytz.utc).isoformat(), } } }, 'objectType': 'Activity' } self.expected_xapi_answer2 = { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/answer/" + self.answer2.uuid, 'definition': { 'type': 'http://id.tincanapi.com/activitytype/solution', 'extensions': { 'http://id.tincanapi.com/extension/isDraft': False } }, 'objectType': 'Activity' } self.expected_xapi_comparison_question = { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/comparison/question/1", 'definition': { 'type': 'http://adlnet.gov/expapi/activities/question', 'name': { 'en-US': "Assignment comparison #1" } }, 'objectType': 'Activity' } def test_on_comparison_update(self): completed_count = 0 for (is_comparison_example, comparison) in [(True, self.example_comparison), (False, self.comparison)]: for completed in [False, True]: comparison.completed = completed comparison.winner = WinningAnswer.answer1 if completed else None db.session.commit() on_comparison_update.send( current_app._get_current_object(), event_name=on_comparison_update.name, user=self.user, assignment=self.assignment, comparison=comparison, is_comparison_example=is_comparison_example) if completed: completed_count += 1 current_comparison = completed_count if completed else completed_count + 1 self.expected_caliper_comparison_question[ 'name'] = "Assignment comparison #" + str( current_comparison) self.expected_caliper_comparison_question[ 'id'] = "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/comparison/question/" + str( current_comparison) self.expected_xapi_comparison_question['definition']['name'][ 'en-US'] = "Assignment comparison #{}".format( current_comparison) self.expected_xapi_comparison_question[ 'id'] = "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/comparison/question/" + str( current_comparison) expected_caliper_comparison_attempt = { 'assignable': self.expected_caliper_comparison_question, 'assignee': self.get_compair_caliper_actor(self.user), 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/comparison/question/" + str(current_comparison) + "/attempt/" + comparison.attempt_uuid, 'duration': "PT05M00S", 'startedAtTime': comparison.attempt_started.replace( tzinfo=pytz.utc).isoformat(), 'endedAtTime': comparison.attempt_ended.replace( tzinfo=pytz.utc).isoformat(), 'type': 'Attempt' } expected_caliper_comparison = { 'attempt': expected_caliper_comparison_attempt, 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/comparison/" + comparison.uuid, 'type': 'Response', 'dateCreated': comparison.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': comparison.modified.replace(tzinfo=pytz.utc).isoformat(), 'extensions': { 'pairingAlgorithm': self.comparison.pairing_algorithm.value, 'winner': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/answer/" + self.answer1.uuid if completed else "Undecided", 'criteria': { "https://localhost:8888/app/criterion/" + self.criterion.uuid: "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/answer/" + self.answer1.uuid, }, "answers": [ "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/answer/" + self.answer1.uuid, "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/answer/" + self.answer2.uuid, ], "completed": completed } } events = self.get_and_clear_caliper_event_log() expected_caliper_events = [{ 'action': 'Completed', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_comparison_question, 'generated': expected_caliper_comparison, 'session': self.get_caliper_session( self.get_compair_caliper_actor(self.user)), 'type': 'AssessmentItemEvent' }, { 'action': 'Submitted', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_assignment, 'generated': { 'assignable': self.expected_caliper_assignment, 'assignee': self.get_compair_caliper_actor(self.user), 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/attempt/" + comparison.attempt_uuid, 'duration': "PT05M00S", 'startedAtTime': comparison.attempt_started.replace( tzinfo=pytz.utc).isoformat(), 'endedAtTime': comparison.attempt_ended.replace( tzinfo=pytz.utc).isoformat(), 'type': 'Attempt' }, 'session': self.get_caliper_session( self.get_compair_caliper_actor(self.user)), 'type': 'AssessmentEvent' }] if not is_comparison_example and completed: expected_caliper_events.append({ 'action': 'Ranked', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_answer1, 'session': self.get_caliper_session( self.get_compair_caliper_actor(self.user)), 'type': 'Event' }) expected_caliper_events.append({ 'action': 'Ranked', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_answer2, 'session': self.get_caliper_session( self.get_compair_caliper_actor(self.user)), 'type': 'Event' }) self.assertEqual(len(events), len(expected_caliper_events)) for index, expected_event in enumerate( expected_caliper_events): self.assertEqual(events[index], expected_event) expected_xapi_comparison_attempt = { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/comparison/question/" + str(current_comparison) + "/attempt/" + comparison.attempt_uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/attempt', 'extensions': { 'http://id.tincanapi.com/extension/attempt': { 'duration': "PT05M00S", 'startedAtTime': comparison.attempt_started.replace( tzinfo=pytz.utc).isoformat(), 'endedAtTime': comparison.attempt_ended.replace( tzinfo=pytz.utc).isoformat(), } } }, 'objectType': 'Activity' } expected_xapi_comparison = { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/comparison/" + comparison.uuid, 'definition': { 'type': 'http://id.tincanapi.com/activitytype/solution', 'name': { 'en-US': "Assignment comparison" }, 'extensions': { 'http://id.tincanapi.com/extension/completed': completed } }, 'objectType': 'Activity' } statements = self.get_and_clear_xapi_statement_log() expected_xapi_statements = [{ "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://adlnet.gov/expapi/verbs/completed', 'display': { 'en-US': 'completed' } }, "object": expected_xapi_comparison, "context": { 'registration': comparison.attempt_uuid, 'contextActivities': { 'parent': [ self.expected_xapi_comparison_question, self.expected_xapi_answer1, self.expected_xapi_answer2, expected_xapi_comparison_attempt ], 'grouping': [ self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section ] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } }, "result": { 'success': True, 'duration': "PT05M00S", 'completion': completed, 'response': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/answer/" + self.answer1.uuid if completed else "Undecided", 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/criteria': { "https://localhost:8888/app/criterion/" + self.criterion.uuid: "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/answer/" + self.answer1.uuid, } } } }, { "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://activitystrea.ms/schema/1.0/submit', 'display': { 'en-US': 'submitted' } }, "object": { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/attempt/" + comparison.attempt_uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/attempt', 'extensions': { 'http://id.tincanapi.com/extension/attempt': { 'duration': "PT05M00S", 'startedAtTime': comparison.attempt_started.replace( tzinfo=pytz.utc).isoformat(), 'endedAtTime': comparison.attempt_ended.replace( tzinfo=pytz.utc).isoformat(), } } }, 'objectType': 'Activity' }, "context": { 'registration': comparison.attempt_uuid, 'contextActivities': { 'parent': [self.expected_xapi_assignment], 'grouping': [ self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section ] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } }, "result": { 'success': True, 'completion': completed } }] if not is_comparison_example and completed: expected_xapi_statements.append({ "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'https://w3id.org/xapi/dod-isd/verbs/ranked', 'display': { 'en-US': 'ranked' } }, "object": self.expected_xapi_answer1, "context": { 'registration': comparison.attempt_uuid, 'contextActivities': { 'parent': [ self.expected_xapi_assignment_question, self.expected_xapi_answer1_attempt ], 'grouping': [ self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section ] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } }, "result": { 'score': { 'raw': 5.0 }, 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/score-details': { 'algorithm': self.assignment.scoring_algorithm.value, 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0, 'criteria': { "https://localhost:8888/app/criterion/" + self.criterion.uuid: { 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0 }, } } } } }) expected_xapi_statements.append({ "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'https://w3id.org/xapi/dod-isd/verbs/ranked', 'display': { 'en-US': 'ranked' } }, "object": self.expected_xapi_answer2, "context": { 'registration': comparison.attempt_uuid, 'contextActivities': { 'parent': [ self.expected_xapi_assignment_question, self.expected_xapi_answer2_attempt ], 'grouping': [ self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section ] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } }, "result": { 'score': { 'raw': 5.0 }, 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/score-details': { 'algorithm': self.assignment.scoring_algorithm.value, 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0, 'criteria': { "https://localhost:8888/app/criterion/" + self.criterion.uuid: { 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0 }, } } } } }) self.assertEqual(len(statements), len(expected_xapi_statements)) for index, expected_statement in enumerate( expected_xapi_statements): self.assertEqual(statements[index], expected_statement)
class AssignmentLearningRecordTests(ComPAIRLearningRecordTestCase): def setUp(self): super(ComPAIRLearningRecordTestCase, self).setUp() self.data = SimpleAssignmentTestData() self.lti_data = LTITestData() self.user = self.data.authorized_student self.setup_session_data(self.user) self.course = self.data.main_course self.lti_context = self.lti_data.create_context( self.lti_data.lti_consumer, compair_course_id=self.course.id, lis_course_offering_sourcedid="sis_course_id", lis_course_section_sourcedid="sis_section_id", ) self.assignment = self.data.assignments[0] self.expected_caliper_course = { 'academicSession': self.course.term, 'dateCreated': self.course.created.replace( tzinfo=pytz.utc).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z', 'dateModified': self.course.modified.replace( tzinfo=pytz.utc).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z', 'id': "https://localhost:8888/app/course/" + self.course.uuid, 'name': self.course.name, 'type': 'CourseOffering', 'otherIdentifiers': [{ 'identifier': self.lti_context.context_id, 'identifierType': 'LtiContextId', 'type': 'SystemIdentifier', 'extensions': { 'lis_course_offering_sourcedid': 'sis_course_id', 'lis_course_section_sourcedid': 'sis_section_id', 'oauth_consumer_key': self.lti_data.lti_consumer.oauth_consumer_key, }, }] } self.expected_caliper_assignment = { 'name': self.assignment.name, 'type': 'Assessment', 'dateCreated': self.assignment.created.replace( tzinfo=pytz.utc).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z', 'dateModified': self.assignment.modified.replace( tzinfo=pytz.utc).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z', 'dateToStartOn': self.assignment.answer_start.replace( tzinfo=pytz.utc).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z', 'description': self.assignment.description, 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid, 'isPartOf': self.expected_caliper_course, 'items': [{ 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/question", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/comparison/question/1", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/evaluation/question/1", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/evaluation/question/2", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/comparison/question/2", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/evaluation/question/3", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/evaluation/question/4", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/comparison/question/3", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/evaluation/question/5", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/evaluation/question/6", 'type': 'AssessmentItem' }], } self.expected_xapi_course = { 'id': "https://localhost:8888/app/course/" + self.course.uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/course', 'name': { 'en-US': self.course.name } }, 'objectType': 'Activity' } self.expected_xapi_assignment = { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/assessment', 'name': { 'en-US': self.assignment.name }, 'description': { 'en-US': self.assignment.description }, }, 'objectType': 'Activity' } def test_on_assignment_create(self): on_assignment_create.send(current_app._get_current_object(), event_name=on_assignment_create.name, user=self.user, assignment=self.assignment) events = self.get_and_clear_caliper_event_log() expected_caliper_event = { 'action': 'Created', 'profile': 'ResourceManagementProfile', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_assignment, 'session': self.get_caliper_session(self.get_compair_caliper_actor( self.user)), 'type': 'ResourceManagementEvent' } self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) statements = self.get_and_clear_xapi_statement_log() expected_xapi_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://activitystrea.ms/schema/1.0/author', 'display': { 'en-US': 'authored' } }, "object": self.expected_xapi_assignment, "context": { 'contextActivities': { 'parent': [self.expected_xapi_course], 'grouping': [] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info(), 'sis_courses': [{ 'id': 'sis_course_id', 'section_ids': ['sis_section_id'] }] } } } self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement) def test_on_assignment_modified(self): on_assignment_modified.send(current_app._get_current_object(), event_name=on_assignment_modified.name, user=self.user, assignment=self.assignment) events = self.get_and_clear_caliper_event_log() expected_caliper_event = { 'action': 'Modified', 'profile': 'ResourceManagementProfile', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_assignment, 'session': self.get_caliper_session(self.get_compair_caliper_actor( self.user)), 'type': 'ResourceManagementEvent' } self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) statements = self.get_and_clear_xapi_statement_log() expected_xapi_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://activitystrea.ms/schema/1.0/update', 'display': { 'en-US': 'updated' } }, "object": self.expected_xapi_assignment, "context": { 'contextActivities': { 'parent': [self.expected_xapi_course], 'grouping': [] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info(), 'sis_courses': [{ 'id': 'sis_course_id', 'section_ids': ['sis_section_id'] }] } } } self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement) def test_on_assignment_delete(self): on_assignment_delete.send(current_app._get_current_object(), event_name=on_assignment_delete.name, user=self.user, assignment=self.assignment) events = self.get_and_clear_caliper_event_log() expected_caliper_event = { 'action': 'Archived', 'profile': 'ResourceManagementProfile', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_assignment, 'session': self.get_caliper_session(self.get_compair_caliper_actor( self.user)), 'type': 'ResourceManagementEvent' } self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) statements = self.get_and_clear_xapi_statement_log() expected_xapi_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'https://w3id.org/xapi/dod-isd/verbs/archived', 'display': { 'en-US': 'archived' } }, "object": self.expected_xapi_assignment, "context": { 'contextActivities': { 'parent': [self.expected_xapi_course], 'grouping': [] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info(), 'sis_courses': [{ 'id': 'sis_course_id', 'section_ids': ['sis_section_id'] }] } } } self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement)
class AnswerLearningRecordTests(ComPAIRLearningRecordTestCase): def setUp(self): super(ComPAIRLearningRecordTestCase, self).setUp() self.data = SimpleAnswersTestData() self.lti_data = LTITestData() self.user = self.data.authorized_student self.setup_session_data(self.user) self.course = self.data.main_course self.lti_context = self.lti_data.create_context( self.lti_data.lti_consumer, compair_course_id=self.course.id, lis_course_offering_sourcedid="sis_course_id", lis_course_section_sourcedid="sis_section_id", ) self.assignment = self.data.assignments[0] self.criterion = self.assignment.criteria[0] self.answer = self.data.create_answer(self.assignment, self.user) self.expected_caliper_course = { 'academicSession': self.course.term, 'dateCreated': self.course.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.course.modified.replace(tzinfo=pytz.utc).isoformat(), 'id': "https://localhost:8888/app/course/"+self.course.uuid, 'name': self.course.name, 'type': 'CourseOffering', 'extensions': { 'ltiContexts': [{ 'context_id': self.lti_context.context_id, 'oauth_consumer_key': self.lti_data.lti_consumer.oauth_consumer_key, 'lis_course_offering_sourcedid': "sis_course_id", 'lis_course_section_sourcedid': "sis_section_id", }] } } self.expected_caliper_assignment = { 'name': self.assignment.name, 'type': 'Assessment', 'dateCreated': self.assignment.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.assignment.modified.replace(tzinfo=pytz.utc).isoformat(), 'dateToStartOn': self.assignment.answer_start.replace(tzinfo=pytz.utc).isoformat(), 'description': self.assignment.description, 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid, 'isPartOf': self.expected_caliper_course, 'items': [{ 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/question", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/1", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/1", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/2", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/2", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/3", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/4", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/3", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/5", 'type': 'AssessmentItem' }, { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/evaluation/question/6", 'type': 'AssessmentItem' }], } self.expected_caliper_assignment_question = { 'name': self.assignment.name, 'type': 'AssessmentItem', 'dateCreated': self.assignment.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.assignment.modified.replace(tzinfo=pytz.utc).isoformat(), 'dateToStartOn': self.assignment.answer_start.replace(tzinfo=pytz.utc).isoformat(), 'dateToSubmit': self.assignment.answer_end.replace(tzinfo=pytz.utc).isoformat(), 'description': self.assignment.description, 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/question", 'isPartOf': self.expected_caliper_assignment, } self.expected_caliper_answer_attempt = { 'assignable': self.expected_caliper_assignment_question, 'assignee': self.get_compair_caliper_actor(self.user), 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/question/attempt/"+self.answer.attempt_uuid, 'duration': "PT05M00S", 'startedAtTime': self.answer.attempt_started.replace(tzinfo=pytz.utc).isoformat(), 'endedAtTime': self.answer.attempt_ended.replace(tzinfo=pytz.utc).isoformat(), 'type': 'Attempt' } self.expected_caliper_answer = { 'attempt': self.expected_caliper_answer_attempt, 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/answer/"+self.answer.uuid, 'type': 'Response', 'dateCreated': self.answer.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': self.answer.modified.replace(tzinfo=pytz.utc).isoformat(), 'extensions': { 'characterCount': len(self.answer.content), 'content': self.answer.content, 'isDraft': False, 'wordCount': 8, 'scoreDetails': { 'algorithm': self.assignment.scoring_algorithm.value, 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0, 'criteria': { "https://localhost:8888/app/criterion/"+self.criterion.uuid: { 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0 }, } }, } } self.expected_xapi_course = { 'id': "https://localhost:8888/app/course/"+self.course.uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/course', 'name': {'en-US': self.course.name} }, 'objectType': 'Activity' } self.expected_xapi_sis_course = { 'id': 'https://localhost:8888/course/'+self.lti_context.lis_course_offering_sourcedid, 'objectType': 'Activity' } self.expected_xapi_sis_section = { 'id': 'https://localhost:8888/course/'+self.lti_context.lis_course_offering_sourcedid+'/section/'+self.lti_context.lis_course_section_sourcedid, 'objectType': 'Activity' } self.expected_xapi_assignment = { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/assessment', 'name': {'en-US': self.assignment.name}, 'description': {'en-US': self.assignment.description}, }, 'objectType': 'Activity' } self.expected_xapi_assignment_question = { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/question", 'definition': { 'type': 'http://adlnet.gov/expapi/activities/question', 'name': {'en-US': self.assignment.name}, 'description': {'en-US': self.assignment.description}, }, 'objectType': 'Activity' } self.expected_xapi_answer_attempt = { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/question/attempt/"+self.answer.attempt_uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/attempt', 'extensions': { 'http://id.tincanapi.com/extension/attempt': { 'duration': "PT05M00S", 'startedAtTime': self.answer.attempt_started.replace(tzinfo=pytz.utc).isoformat(), 'endedAtTime': self.answer.attempt_ended.replace(tzinfo=pytz.utc).isoformat(), } } }, 'objectType': 'Activity' } self.expected_xapi_answer = { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/answer/"+self.answer.uuid, 'definition': { 'type': 'http://id.tincanapi.com/activitytype/solution', 'extensions': { 'http://id.tincanapi.com/extension/isDraft': False } }, 'objectType': 'Activity' } def test_on_answer_create(self): for draft in [True, False]: self.answer.draft = draft db.session.commit() self.expected_xapi_answer['definition']['extensions']['http://id.tincanapi.com/extension/isDraft'] = draft self.expected_caliper_answer['extensions']['isDraft'] = draft self.expected_caliper_answer['dateModified'] = self.answer.modified.replace(tzinfo=pytz.utc).isoformat() # test without tracking on_answer_create.send( current_app._get_current_object(), event_name=on_answer_create.name, user=self.user, answer=self.answer ) events = self.get_and_clear_caliper_event_log() expected_caliper_events = [{ 'action': 'Completed', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_assignment_question, 'generated': self.expected_caliper_answer, 'session': self.get_caliper_session(self.get_compair_caliper_actor(self.user)), 'type': 'AssessmentItemEvent' }, { 'action': 'Submitted', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_assignment, 'generated': { 'assignable': self.expected_caliper_assignment, 'assignee': self.get_compair_caliper_actor(self.user), 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/attempt/"+self.answer.attempt_uuid, 'duration': "PT05M00S", 'startedAtTime': self.answer.attempt_started.replace(tzinfo=pytz.utc).isoformat(), 'endedAtTime': self.answer.attempt_ended.replace(tzinfo=pytz.utc).isoformat(), 'type': 'Attempt' }, 'session': self.get_caliper_session(self.get_compair_caliper_actor(self.user)), 'type': 'AssessmentEvent' }] self.assertEqual(len(events), len(expected_caliper_events)) for index, expected_event in enumerate(expected_caliper_events): self.assertEqual(events[index], expected_event) statements = self.get_and_clear_xapi_statement_log() expected_xapi_statements = [{ "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://adlnet.gov/expapi/verbs/completed', 'display': {'en-US': 'completed'} }, "object": self.expected_xapi_answer, "context": { 'registration': self.answer.attempt_uuid, 'contextActivities': { 'parent': [self.expected_xapi_assignment_question, self.expected_xapi_answer_attempt], 'grouping': [self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } }, "result": { 'success': True, 'duration': "PT05M00S", 'completion': not draft, 'response': self.answer.content, 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/character-count': len(self.answer.content), 'http://xapi.learninganalytics.ubc.ca/extension/word-count': len(self.answer.content.split(" ")) } } }, { "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://activitystrea.ms/schema/1.0/submit', 'display': {'en-US': 'submitted'} }, "object": { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/attempt/"+self.answer.attempt_uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/attempt', 'extensions': { 'http://id.tincanapi.com/extension/attempt': { 'duration': "PT05M00S", 'startedAtTime': self.answer.attempt_started.replace(tzinfo=pytz.utc).isoformat(), 'endedAtTime': self.answer.attempt_ended.replace(tzinfo=pytz.utc).isoformat(), } } }, 'objectType': 'Activity' }, "context": { 'registration': self.answer.attempt_uuid, 'contextActivities': { 'parent': [self.expected_xapi_assignment], 'grouping': [self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } }, "result": { 'success': True, 'completion': not draft } }] self.assertEqual(len(statements), len(expected_xapi_statements)) for index, expected_statement in enumerate(expected_xapi_statements): self.assertEqual(statements[index], expected_statement) def test_on_answer_modified(self): for draft in [True, False]: self.answer.draft = draft db.session.commit() self.expected_xapi_answer['definition']['extensions']['http://id.tincanapi.com/extension/isDraft'] = draft self.expected_caliper_answer['extensions']['isDraft'] = draft self.expected_caliper_answer['dateModified'] = self.answer.modified.replace(tzinfo=pytz.utc).isoformat() # test without tracking on_answer_modified.send( current_app._get_current_object(), event_name=on_answer_modified.name, user=self.user, answer=self.answer ) events = self.get_and_clear_caliper_event_log() expected_caliper_events = [{ 'action': 'Completed', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_assignment_question, 'generated': self.expected_caliper_answer, 'session': self.get_caliper_session(self.get_compair_caliper_actor(self.user)), 'type': 'AssessmentItemEvent' }, { 'action': 'Submitted', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_assignment, 'generated': { 'assignable': self.expected_caliper_assignment, 'assignee': self.get_compair_caliper_actor(self.user), 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/attempt/"+self.answer.attempt_uuid, 'duration': "PT05M00S", 'startedAtTime': self.answer.attempt_started.replace(tzinfo=pytz.utc).isoformat(), 'endedAtTime': self.answer.attempt_ended.replace(tzinfo=pytz.utc).isoformat(), 'type': 'Attempt' }, 'session': self.get_caliper_session(self.get_compair_caliper_actor(self.user)), 'type': 'AssessmentEvent' }] self.assertEqual(len(events), len(expected_caliper_events)) for index, expected_event in enumerate(expected_caliper_events): self.assertEqual(events[index], expected_event) statements = self.get_and_clear_xapi_statement_log() expected_xapi_statements = [{ "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://adlnet.gov/expapi/verbs/completed', 'display': {'en-US': 'completed'} }, "object": self.expected_xapi_answer, "context": { 'registration': self.answer.attempt_uuid, 'contextActivities': { 'parent': [self.expected_xapi_assignment_question, self.expected_xapi_answer_attempt], 'grouping': [self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } }, "result": { 'success': True, 'duration': "PT05M00S", 'completion': not draft, 'response': self.answer.content, 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/character-count': len(self.answer.content), 'http://xapi.learninganalytics.ubc.ca/extension/word-count': len(self.answer.content.split(" ")) } } }, { "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://activitystrea.ms/schema/1.0/submit', 'display': {'en-US': 'submitted'} }, "object": { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/attempt/"+self.answer.attempt_uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/attempt', 'extensions': { 'http://id.tincanapi.com/extension/attempt': { 'duration': "PT05M00S", 'startedAtTime': self.answer.attempt_started.replace(tzinfo=pytz.utc).isoformat(), 'endedAtTime': self.answer.attempt_ended.replace(tzinfo=pytz.utc).isoformat(), } } }, 'objectType': 'Activity' }, "context": { 'registration': self.answer.attempt_uuid, 'contextActivities': { 'parent': [self.expected_xapi_assignment], 'grouping': [self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } }, "result": { 'success': True, 'completion': not draft } }] self.assertEqual(len(statements), len(expected_xapi_statements)) for index, expected_statement in enumerate(expected_xapi_statements): self.assertEqual(statements[index], expected_statement) def test_on_answer_delete(self): # send delete on_answer_delete.send( current_app._get_current_object(), event_name=on_answer_delete.name, user=self.user, answer=self.answer ) events = self.get_and_clear_caliper_event_log() expected_caliper_event = { 'action': 'Deleted', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_answer, 'session': self.get_caliper_session(self.get_compair_caliper_actor(self.user)), 'type': 'Event' } self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) statements = self.get_and_clear_xapi_statement_log() expected_xapi_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://activitystrea.ms/schema/1.0/delete', 'display': {'en-US': 'deleted'} }, "object": self.expected_xapi_answer, "context": { 'contextActivities': { 'parent': [self.expected_xapi_assignment_question, self.expected_xapi_answer_attempt], 'grouping': [self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } }, } self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement)