예제 #1
0
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)
예제 #2
0
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)
예제 #3
0
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)
예제 #4
0
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)
예제 #5
0
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)
예제 #6
0
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)
예제 #7
0
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)
예제 #8
0
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)
예제 #9
0
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)
예제 #10
0
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)
예제 #11
0
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)
예제 #12
0
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)