Example #1
0
 def setUp(self):
     super(ComPAIRXAPITestCase, self).setUp()
     self.data = SimpleAnswersTestData()
     self.user = self.data.authorized_student
     self.course = self.data.main_course
     self.assignment = self.data.assignments[0]
     self.answer = self.data.create_answer(self.assignment, self.user)
Example #2
0
    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')))
Example #3
0
    def setUp(self):
        super(ComPAIRLearningRecordTestCase, self).setUp()
        self.data = SimpleAnswersTestData()
        self.auth_data = ThirdPartyAuthTestData()
        self.lti_data = LTITestData()
        self.course = self.data.main_course
        self.assignment = self.data.assignments[0]

        self.user = self.data.create_user(SystemRole.instructor)
        self.data.enrol_user(self.user, self.data.get_course(),
                             CourseRole.instructor)

        self.global_unique_identifier = 'mock_puid_รจ_global_unique_identifier'
Example #4
0
    def setUp(self):
        super(ComPAIRXAPITestCase, self).setUp()
        self.app.config['LRS_STATEMENT_ENDPOINT'] = 'http://example.com'
        self.app.config['LRS_USERNAME'] = '******'
        self.app.config['LRS_PASSWORD'] = '******'
        self.app.config['LRS_USER_INPUT_FIELD_SIZE_LIMIT'] = 200 # 200 bytes

        self.data = SimpleAnswersTestData()
        self.user = self.data.authorized_student
        self.course = self.data.main_course
        self.assignment = self.data.assignments[0]
        self.answer = self.data.create_answer(self.assignment, self.user)
        self.sent_statement = None
        self.character_limit = int(current_app.config.get('LRS_USER_INPUT_FIELD_SIZE_LIMIT') / len("c".encode('utf-8')))
Example #5
0
 def setUp(self):
     super(ComPAIRXAPITestCase, self).setUp()
     self.data = SimpleAnswersTestData()
     self.user = self.data.authorized_student
     self.course = self.data.main_course
     self.assignment = self.data.assignments[0]
     self.answer = self.data.create_answer(self.assignment, self.user)
Example #6
0
    def setUp(self):
        super(ComPAIRLearningRecordTestCase, self).setUp()
        self.data = SimpleAnswersTestData()
        self.auth_data = ThirdPartyAuthTestData()
        self.course = self.data.main_course
        self.assignment = self.data.assignments[0]

        self.cas_user_auth = self.auth_data.create_cas_user_auth(
            SystemRole.instructor)
        self.cas_user = self.cas_user_auth.user
        self.data.enrol_user(self.cas_user, self.data.get_course(),
                             CourseRole.instructor)

        self.saml_user_auth = self.auth_data.create_saml_user_auth(
            SystemRole.instructor)
        self.saml_user = self.saml_user_auth.user
        self.data.enrol_user(self.saml_user, self.data.get_course(),
                             CourseRole.instructor)
Example #7
0
    def setUp(self):
        super(ComPAIRLearningRecordTestCase, self).setUp()
        self.data = SimpleAnswersTestData()
        self.auth_data = ThirdPartyAuthTestData()
        self.course = self.data.main_course
        self.assignment = self.data.assignments[0]

        self.cas_user_auth = self.auth_data.create_cas_user_auth(SystemRole.instructor)
        self.cas_user = self.cas_user_auth.user
        self.data.enrol_user(self.cas_user, self.data.get_course(), CourseRole.instructor)

        self.saml_user_auth = self.auth_data.create_saml_user_auth(SystemRole.instructor)
        self.saml_user = self.saml_user_auth.user
        self.data.enrol_user(self.saml_user, self.data.get_course(), CourseRole.instructor)
Example #8
0
class AccountLearningRecordTests(ComPAIRLearningRecordTestCase):
    def setUp(self):
        super(ComPAIRLearningRecordTestCase, self).setUp()
        self.data = SimpleAnswersTestData()
        self.auth_data = ThirdPartyAuthTestData()
        self.course = self.data.main_course
        self.assignment = self.data.assignments[0]

        self.cas_user_auth = self.auth_data.create_cas_user_auth(
            SystemRole.instructor)
        self.cas_user = self.cas_user_auth.user
        self.data.enrol_user(self.cas_user, self.data.get_course(),
                             CourseRole.instructor)

        self.saml_user_auth = self.auth_data.create_saml_user_auth(
            SystemRole.instructor)
        self.saml_user = self.saml_user_auth.user
        self.data.enrol_user(self.saml_user, self.data.get_course(),
                             CourseRole.instructor)

    def test_actor_accounts(self):
        for user, third_party_auth in [(self.cas_user, self.cas_user_auth),
                                       (self.saml_user, self.saml_user_auth)]:

            # test without homepage set
            # (should use compair actor account)
            self.app.config[
                'LRS_ACTOR_ACCOUNT_USE_GLOBAL_UNIQUE_IDENTIFIER'] = True
            self.app.config[
                'LRS_ACTOR_ACCOUNT_GLOBAL_UNIQUE_IDENTIFIER_HOMEPAGE'] = None
            expected_actor = self.get_compair_xapi_actor(user)

            on_assignment_modified.send(current_app._get_current_object(),
                                        event_name=on_assignment_modified.name,
                                        user=user,
                                        assignment=self.assignment)
            statements = self.get_and_clear_xapi_statement_log()
            self.assertEqual(len(statements), 1)
            self.assertEqual(statements[0]['actor'], expected_actor)

            # test with homepage set and global unique identifier not set
            # (should use compair actor account)
            self.app.config[
                'LRS_ACTOR_ACCOUNT_GLOBAL_UNIQUE_IDENTIFIER_HOMEPAGE'] = "http://third.party.homepage"
            on_assignment_modified.send(current_app._get_current_object(),
                                        event_name=on_assignment_modified.name,
                                        user=user,
                                        assignment=self.assignment)
            statements = self.get_and_clear_xapi_statement_log()
            self.assertEqual(len(statements), 1)
            self.assertEqual(statements[0]['actor'], expected_actor)
            expected_actor = self.get_compair_xapi_actor(user)

            # test with homepage set and global unique identifier set
            # (should use cas/saml actor account with overridden value used for name)
            user.global_unique_identifier = 'mock_puid_รจ_' + third_party_auth.third_party_type.value
            db.session.commit()
            expected_actor = self.get_unique_identifier_xapi_actor(
                user, "http://third.party.homepage/",
                'mock_puid_รจ_' + third_party_auth.third_party_type.value)
            on_assignment_modified.send(current_app._get_current_object(),
                                        event_name=on_assignment_modified.name,
                                        user=user,
                                        assignment=self.assignment)
            statements = self.get_and_clear_xapi_statement_log()
            self.assertEqual(len(statements), 1)
            self.assertEqual(statements[0]['actor'], expected_actor)

            # disabling LRS_ACTOR_ACCOUNT_USE_GLOBAL_UNIQUE_IDENTIFIER should skip checking global unique identifer
            self.app.config[
                'LRS_ACTOR_ACCOUNT_USE_GLOBAL_UNIQUE_IDENTIFIER'] = False
            expected_actor = self.get_compair_xapi_actor(user)
            on_assignment_modified.send(current_app._get_current_object(),
                                        event_name=on_assignment_modified.name,
                                        user=user,
                                        assignment=self.assignment)
            statements = self.get_and_clear_xapi_statement_log()
            self.assertEqual(len(statements), 1)
            self.assertEqual(statements[0]['actor'], expected_actor)
Example #9
0
class RemoteXAPITests(ComPAIRXAPITestCase):

    def setUp(self):
        super(ComPAIRXAPITestCase, self).setUp()
        self.app.config['LRS_STATEMENT_ENDPOINT'] = 'http://example.com'
        self.app.config['LRS_USERNAME'] = '******'
        self.app.config['LRS_PASSWORD'] = '******'
        self.app.config['LRS_USER_INPUT_FIELD_SIZE_LIMIT'] = 200 # 200 bytes

        self.data = SimpleAnswersTestData()
        self.user = self.data.authorized_student
        self.course = self.data.main_course
        self.assignment = self.data.assignments[0]
        self.answer = self.data.create_answer(self.assignment, self.user)
        self.sent_statement = None
        self.character_limit = int(current_app.config.get('LRS_USER_INPUT_FIELD_SIZE_LIMIT') / len("c".encode('utf-8')))

    @mock.patch('tincan.RemoteLRS.save_statement')
    def test_send_remote_statement(self, mocked_save_statement):

        def save_statement_override(statement):
            self.sent_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


        # 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.answer(self.answer, includeAttachment=True, success=True)
        )

        XAPI._send_lrs_statement(json.loads(statement.to_json(XAPI._version)))

        self._validate_and_cleanup_statement(self.sent_statement)

        self.assertEqual(self.sent_statement['actor'], self.get_compair_actor(self.user))
        self.assertEqual(self.sent_statement['verb'], {
            'id': 'http://activitystrea.ms/schema/1.0/submit',
            'display': {'en-US': 'submitted'}
        })
        self.assertEqual(self.sent_statement['object'], {
            'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid,
            'definition': {'type': 'http://id.tincanapi.com/activitytype/solution', 'name': {'en-US': 'Assignment answer'}},
            'objectType': 'Activity'
        })
        self.assertEqual(self.sent_statement['result'], {
            '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(" "))
            },
            'response': self.answer.content,
            'success': True
        })
        self.assertEqual(self.sent_statement['context'], {
            'contextActivities': {
                'grouping': [{
                    'id': 'https://localhost:8888/app/xapi/course/'+self.course.uuid,
                    'objectType': 'Activity'
                },{
                    'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid,
                    'objectType': 'Activity'
                }],
                'parent': [{
                    'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/question',
                    'objectType': 'Activity'
                }]
            }
        })

        # 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)
        # expected_result_response should be <= LRS_USER_INPUT_FIELD_SIZE_LIMIT bytes long + " [TEXT TRIMMED]..."
        expected_result_response = ("c" * self.character_limit) + " [TEXT TRIMMED]..."

        self.answer.content = content
        db.session.commit()

        statement = XAPIStatement.generate(
            user=self.user,
            verb=XAPIVerb.generate('submitted'),
            object=XAPIObject.answer(self.answer),
            context=XAPIContext.answer(self.answer),
            result=XAPIResult.answer(self.answer, includeAttachment=True, success=True)
        )

        XAPI._send_lrs_statement(json.loads(statement.to_json(XAPI._version)))

        self._validate_and_cleanup_statement(self.sent_statement)

        self.assertEqual(self.sent_statement['actor'], self.get_compair_actor(self.user))
        self.assertEqual(self.sent_statement['verb'], {
            'id': 'http://activitystrea.ms/schema/1.0/submit',
            'display': {'en-US': 'submitted'}
        })
        self.assertEqual(self.sent_statement['object'], {
            'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid,
            'definition': {'type': 'http://id.tincanapi.com/activitytype/solution', 'name': {'en-US': 'Assignment answer'}},
            'objectType': 'Activity'
        })
        self.assertEqual(self.sent_statement['result'], {
            '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(" "))
            },
            'response': expected_result_response,
            'success': True
        })
        self.assertEqual(self.sent_statement['context'], {
            'contextActivities': {
                'grouping': [{
                    'id': 'https://localhost:8888/app/xapi/course/'+self.course.uuid,
                    'objectType': 'Activity'
                },{
                    'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid,
                    'objectType': 'Activity'
                },],
                'parent': [{
                    'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/question',
                    'objectType': 'Activity'
                }]
            }
        })

        # 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._send_lrs_statement(json.loads(statement.to_json(XAPI._version)))

        self._validate_and_cleanup_statement(self.sent_statement)

        self.assertEqual(self.sent_statement['actor'], self.get_compair_actor(self.user))
        self.assertEqual(self.sent_statement['verb'], {
            'id': 'http://activitystrea.ms/schema/1.0/update',
            'display': {'en-US': 'updated'}
        })
        self.assertEqual(self.sent_statement['object'], {
            'id': 'https://localhost:8888/app/xapi/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.assertNotIn('result', self.sent_statement)
        self.assertEqual(self.sent_statement['context'], {
            'contextActivities': {
                'parent': [{
                    'id': 'https://localhost:8888/app/xapi/course/'+self.course.uuid,
                    'objectType': 'Activity'
                }]
            }
        })

        # 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)
        # expected_result_response should be <= LRS_USER_INPUT_FIELD_SIZE_LIMIT bytes long + " [TEXT TRIMMED]..."
        expected_object_name = ("a" * self.character_limit) + " [TEXT TRIMMED]..."
        expected_object_description = ("b" * self.character_limit) + " [TEXT TRIMMED]..."

        self.assignment.name = name
        self.assignment.description = description
        db.session.commit()

        statement = XAPIStatement.generate(
            user=self.user,
            verb=XAPIVerb.generate('updated'),
            object=XAPIObject.assignment(self.assignment),
            context=XAPIContext.assignment(self.assignment)
        )

        XAPI._send_lrs_statement(json.loads(statement.to_json(XAPI._version)))

        self._validate_and_cleanup_statement(self.sent_statement)

        self.assertEqual(self.sent_statement['actor'], self.get_compair_actor(self.user))
        self.assertEqual(self.sent_statement['verb'], {
            'id': 'http://activitystrea.ms/schema/1.0/update',
            'display': {'en-US': 'updated'}
        })
        self.assertEqual(self.sent_statement['object'], {
            'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid,
            'definition': {
                'type': 'http://adlnet.gov/expapi/activities/assessment',
                'name': {'en-US': expected_object_name },
                'description': {'en-US': expected_object_description }
            },
            'objectType': 'Activity'
        })
        self.assertNotIn('result', self.sent_statement)
        self.assertEqual(self.sent_statement['context'], {
            'contextActivities': {
                'parent': [{
                    'id': 'https://localhost:8888/app/xapi/course/'+self.course.uuid,
                    'objectType': 'Activity'
                }]
            }
        })
Example #10
0
class AccountLearningRecordTests(ComPAIRLearningRecordTestCase):
    def setUp(self):
        super(ComPAIRLearningRecordTestCase, self).setUp()
        self.data = SimpleAnswersTestData()
        self.auth_data = ThirdPartyAuthTestData()
        self.lti_data = LTITestData()
        self.course = self.data.main_course
        self.assignment = self.data.assignments[0]

        self.user = self.data.create_user(SystemRole.instructor)
        self.data.enrol_user(self.user, self.data.get_course(),
                             CourseRole.instructor)

        self.global_unique_identifier = 'mock_puid_รจ_global_unique_identifier'

    def test_actor_accounts(self):
        user = self.user

        # test without homepage set
        # (should use compair actor account)
        self.app.config[
            'LRS_ACTOR_ACCOUNT_USE_GLOBAL_UNIQUE_IDENTIFIER'] = True
        self.app.config[
            'LRS_ACTOR_ACCOUNT_GLOBAL_UNIQUE_IDENTIFIER_HOMEPAGE'] = None
        expected_caliper_actor = self.get_compair_caliper_actor(user)
        expected_xapi_actor = self.get_compair_xapi_actor(user)

        on_assignment_modified.send(current_app._get_current_object(),
                                    event_name=on_assignment_modified.name,
                                    user=user,
                                    assignment=self.assignment)
        events = self.get_and_clear_caliper_event_log()
        self.assertEqual(len(events), 1)
        self.assertEqual(events[0]['actor'], expected_caliper_actor)

        statements = self.get_and_clear_xapi_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0]['actor'], expected_xapi_actor)

        # test with homepage set and global unique identifier not set
        # (should use compair actor account)
        self.app.config[
            'LRS_ACTOR_ACCOUNT_GLOBAL_UNIQUE_IDENTIFIER_HOMEPAGE'] = "http://third.party.homepage"
        on_assignment_modified.send(current_app._get_current_object(),
                                    event_name=on_assignment_modified.name,
                                    user=user,
                                    assignment=self.assignment)
        events = self.get_and_clear_caliper_event_log()
        self.assertEqual(len(events), 1)
        self.assertEqual(events[0]['actor'], expected_caliper_actor)

        statements = self.get_and_clear_xapi_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0]['actor'], expected_xapi_actor)

        # test with homepage set and global unique identifier set
        user.global_unique_identifier = self.global_unique_identifier
        db.session.commit()
        expected_caliper_actor = self.get_unique_identifier_caliper_actor(
            user, "http://third.party.homepage/",
            self.global_unique_identifier)
        expected_xapi_actor = self.get_unique_identifier_xapi_actor(
            user, "http://third.party.homepage/",
            self.global_unique_identifier)
        on_assignment_modified.send(current_app._get_current_object(),
                                    event_name=on_assignment_modified.name,
                                    user=user,
                                    assignment=self.assignment)
        events = self.get_and_clear_caliper_event_log()
        self.assertEqual(len(events), 1)
        self.assertEqual(events[0]['actor'], expected_caliper_actor)

        statements = self.get_and_clear_xapi_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0]['actor'], expected_xapi_actor)

        # disabling LRS_ACTOR_ACCOUNT_USE_GLOBAL_UNIQUE_IDENTIFIER should skip checking global unique identifer
        self.app.config[
            'LRS_ACTOR_ACCOUNT_USE_GLOBAL_UNIQUE_IDENTIFIER'] = False
        expected_caliper_actor = self.get_compair_caliper_actor(user)
        expected_xapi_actor = self.get_compair_xapi_actor(user)
        on_assignment_modified.send(current_app._get_current_object(),
                                    event_name=on_assignment_modified.name,
                                    user=user,
                                    assignment=self.assignment)
        events = self.get_and_clear_caliper_event_log()
        self.assertEqual(len(events), 1)
        self.assertEqual(events[0]['actor'], expected_caliper_actor)

        statements = self.get_and_clear_xapi_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0]['actor'], expected_xapi_actor)

        # test adding third party auths & lti user links
        # NOTE: xapi doesn't really add this extra info to actor since there
        # isn't anywhere to put it
        cas_auth = self.auth_data.create_third_party_user(
            user=user, third_party_type=ThirdPartyType.cas)
        saml_auth = self.auth_data.create_third_party_user(
            user=user, third_party_type=ThirdPartyType.saml)
        lti_user = self.lti_data.create_user(
            self.lti_data.lti_consumer,
            SystemRole.instructor,
            user,
        )
        lti_user.student_number = '1234567890'
        lti_user.global_unique_identifier = self.global_unique_identifier
        lti_user.lis_person_sourcedid = 'asdfghjkl'
        db.session.commit()

        expected_caliper_actor = self.get_compair_caliper_actor(
            user,
            third_party_users=[cas_auth, saml_auth],
            lti_users=[lti_user])
        expected_xapi_actor = self.get_compair_xapi_actor(
            user,
            third_party_users=[cas_auth, saml_auth],
            lti_users=[lti_user])

        on_assignment_modified.send(current_app._get_current_object(),
                                    event_name=on_assignment_modified.name,
                                    user=user,
                                    assignment=self.assignment)
        events = self.get_and_clear_caliper_event_log()
        self.assertEqual(len(events), 1)
        print(events[0]['actor'])
        self.assertEqual(events[0]['actor'], expected_caliper_actor)

        statements = self.get_and_clear_xapi_statement_log()
        self.assertEqual(len(statements), 1)
        self.assertEqual(statements[0]['actor'], expected_xapi_actor)
Example #11
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)
Example #12
0
class AnswerXAPITests(ComPAIRXAPITestCase):
    def setUp(self):
        super(ComPAIRXAPITestCase, self).setUp()
        self.data = SimpleAnswersTestData()
        self.user = self.data.authorized_student
        self.course = self.data.main_course
        self.assignment = self.data.assignments[0]
        self.answer = self.data.create_answer(self.assignment, self.user)

    def test_on_answer_modified(self):
        for draft in [True, False]:
            self.answer.draft = draft
            db.session.commit()

            # test without tracking
            on_answer_modified.send(current_app._get_current_object(),
                                    event_name=on_answer_modified.name,
                                    user=self.user,
                                    assignment=self.assignment,
                                    answer=self.answer)

            statements = self.get_and_clear_statement_log()
            self.assertEqual(len(statements), 2)

            self.assertEqual(statements[0]['actor'],
                             self.get_compair_actor(self.user))
            if draft:
                self.assertEqual(
                    statements[0]['verb'], {
                        'id':
                        'http://xapi.learninganalytics.ubc.ca/verb/draft',
                        'display': {
                            'en-US': 'drafted'
                        }
                    })
            else:
                self.assertEqual(
                    statements[0]['verb'], {
                        'id': 'http://activitystrea.ms/schema/1.0/submit',
                        'display': {
                            'en-US': 'submitted'
                        }
                    })
            self.assertEqual(
                statements[0]['object'], {
                    'id': 'https://localhost:8888/app/xapi/answer/' +
                    self.answer.uuid,
                    'definition': {
                        'type':
                        'http://id.tincanapi.com/activitytype/solution',
                        'name': {
                            'en-US': 'Assignment answer'
                        }
                    },
                    'objectType': 'Activity'
                })
            if draft:
                self.assertEqual(
                    statements[0]['result'], {
                        '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(" "))
                        },
                        'response': self.answer.content
                    })
            else:
                self.assertEqual(
                    statements[0]['result'], {
                        '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(" "))
                        },
                        'response': self.answer.content,
                        'success': True
                    })
            self.assertEqual(
                statements[0]['context'], {
                    'contextActivities': {
                        'grouping': [
                            {
                                'id':
                                'https://localhost:8888/app/xapi/course/' +
                                self.course.uuid,
                                'objectType':
                                'Activity'
                            },
                            {
                                'id':
                                'https://localhost:8888/app/xapi/assignment/' +
                                self.assignment.uuid,
                                'objectType':
                                'Activity'
                            },
                        ],
                        'parent': [{
                            'id':
                            'https://localhost:8888/app/xapi/assignment/' +
                            self.assignment.uuid + '/question',
                            'objectType':
                            'Activity'
                        }]
                    }
                })

            self.assertEqual(statements[1]['actor'],
                             self.get_compair_actor(self.user))
            if draft:
                self.assertEqual(
                    statements[1]['verb'], {
                        'id': 'http://adlnet.gov/expapi/verbs/suspended',
                        'display': {
                            'en-US': 'suspended'
                        }
                    })
            else:
                self.assertEqual(
                    statements[1]['verb'], {
                        'id': 'http://adlnet.gov/expapi/verbs/completed',
                        'display': {
                            'en-US': 'completed'
                        }
                    })
            self.assertEqual(
                statements[1]['object'], {
                    'id':
                    'https://localhost:8888/app/xapi/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.assertEqual(statements[1]['result'], {
                'completion': not draft,
                'success': True
            })
            self.assertEqual(
                statements[1]['context'], {
                    'contextActivities': {
                        'grouping': [
                            {
                                'id':
                                'https://localhost:8888/app/xapi/course/' +
                                self.course.uuid,
                                'objectType':
                                'Activity'
                            },
                        ],
                        'parent': [{
                            'id':
                            'https://localhost:8888/app/xapi/assignment/' +
                            self.assignment.uuid,
                            'objectType':
                            'Activity'
                        }]
                    }
                })

            # test with tracking
            tracking = self.generate_tracking(with_duration=True)
            tracking_json = json.dumps({'tracking': tracking})
            with self.app.test_request_context(
                    content_type='application/json',
                    method='POST',
                    content_length=len(tracking_json),
                    data=tracking_json):
                on_answer_modified.send(current_app._get_current_object(),
                                        event_name=on_answer_modified.name,
                                        user=self.user,
                                        assignment=self.assignment,
                                        answer=self.answer)

                tracking_statements = self.get_and_clear_statement_log()
                self.assertEqual(len(statements), 2)
                self.assertEqual(statements[0]['actor'],
                                 tracking_statements[0]['actor'])
                self.assertEqual(statements[0]['verb'],
                                 tracking_statements[0]['verb'])
                self.assertEqual(statements[0]['object'],
                                 tracking_statements[0]['object'])
                self.assertEqual(statements[0]['result'],
                                 tracking_statements[0]['result'])
                self.assertEqual(
                    tracking_statements[0]['context'], {
                        'registration': tracking.get('registration'),
                        'contextActivities': {
                            'grouping': [
                                {
                                    'id':
                                    'https://localhost:8888/app/xapi/course/' +
                                    self.course.uuid,
                                    'objectType':
                                    'Activity'
                                },
                                {
                                    'id':
                                    'https://localhost:8888/app/xapi/assignment/'
                                    + self.assignment.uuid,
                                    'objectType':
                                    'Activity'
                                },
                            ],
                            'parent': [{
                                'id':
                                'https://localhost:8888/app/xapi/assignment/' +
                                self.assignment.uuid + '/question',
                                'objectType':
                                'Activity'
                            }]
                        }
                    })
                self.assertEqual(statements[1]['actor'],
                                 tracking_statements[1]['actor'])
                self.assertEqual(statements[1]['verb'],
                                 tracking_statements[1]['verb'])
                self.assertEqual(statements[1]['object'],
                                 tracking_statements[1]['object'])
                self.assertEqual(
                    tracking_statements[1]['context'], {
                        'registration': tracking.get('registration'),
                        'contextActivities': {
                            'grouping': [
                                {
                                    'id':
                                    'https://localhost:8888/app/xapi/course/' +
                                    self.course.uuid,
                                    'objectType':
                                    'Activity'
                                },
                            ],
                            'parent': [{
                                'id':
                                'https://localhost:8888/app/xapi/assignment/' +
                                self.assignment.uuid,
                                'objectType':
                                'Activity'
                            }]
                        }
                    })
                self.assertEqual(
                    tracking_statements[1]['result'], {
                        'completion': not draft,
                        'duration': tracking.get('duration'),
                        'success': True
                    })

    def test_on_answer_delete(self):
        for draft in [True, False]:
            self.answer.draft = draft
            db.session.commit()

            on_answer_delete.send(current_app._get_current_object(),
                                  event_name=on_answer_delete.name,
                                  user=self.user,
                                  answer=self.answer)

            statements = self.get_and_clear_statement_log()
            self.assertEqual(len(statements), 1)

            self.assertEqual(statements[0]['actor'],
                             self.get_compair_actor(self.user))
            self.assertEqual(
                statements[0]['verb'], {
                    'id': 'http://activitystrea.ms/schema/1.0/delete',
                    'display': {
                        'en-US': 'deleted'
                    }
                })
            self.assertEqual(
                statements[0]['object'], {
                    'id': 'https://localhost:8888/app/xapi/answer/' +
                    self.answer.uuid,
                    'definition': {
                        'type':
                        'http://id.tincanapi.com/activitytype/solution',
                        'name': {
                            'en-US': 'Assignment answer'
                        }
                    },
                    'objectType': 'Activity'
                })
            self.assertNotIn('result', statements[0])
            self.assertEqual(
                statements[0]['context'], {
                    'contextActivities': {
                        'grouping': [
                            {
                                'id':
                                'https://localhost:8888/app/xapi/course/' +
                                self.course.uuid,
                                'objectType':
                                'Activity'
                            },
                            {
                                'id':
                                'https://localhost:8888/app/xapi/assignment/' +
                                self.assignment.uuid,
                                'objectType':
                                'Activity'
                            },
                        ],
                        'parent': [{
                            'id':
                            'https://localhost:8888/app/xapi/assignment/' +
                            self.assignment.uuid + '/question',
                            'objectType':
                            'Activity'
                        }]
                    }
                })

    def test_on_answer_flag(self):
        for draft in [True, False]:
            for flagged in [True, False]:
                self.answer.draft = draft
                self.answer.flagged = flagged
                db.session.commit()

                on_answer_flag.send(current_app._get_current_object(),
                                    event_name=on_answer_flag.name,
                                    user=self.user,
                                    answer=self.answer)

                statements = self.get_and_clear_statement_log()
                self.assertEqual(len(statements), 1)

                self.assertEqual(statements[0]['actor'],
                                 self.get_compair_actor(self.user))

                if flagged:
                    self.assertEqual(
                        statements[0]['verb'], {
                            'id':
                            'http://xapi.learninganalytics.ubc.ca/verb/flag',
                            'display': {
                                'en-US': 'flagged'
                            }
                        })
                else:
                    self.assertEqual(
                        statements[0]['verb'], {
                            'id':
                            'http://xapi.learninganalytics.ubc.ca/verb/unflag',
                            'display': {
                                'en-US': 'unflagged'
                            }
                        })

                self.assertEqual(
                    statements[0]['object'], {
                        'id':
                        'https://localhost:8888/app/xapi/answer/' +
                        self.answer.uuid,
                        'definition': {
                            'type':
                            'http://id.tincanapi.com/activitytype/solution',
                            'name': {
                                'en-US': 'Assignment answer'
                            }
                        },
                        'objectType':
                        'Activity'
                    })
                self.assertNotIn('result', statements[0])
                self.assertEqual(
                    statements[0]['context'], {
                        'contextActivities': {
                            'grouping': [
                                {
                                    'id':
                                    'https://localhost:8888/app/xapi/course/' +
                                    self.course.uuid,
                                    'objectType':
                                    'Activity'
                                },
                                {
                                    'id':
                                    'https://localhost:8888/app/xapi/assignment/'
                                    + self.assignment.uuid,
                                    'objectType':
                                    'Activity'
                                },
                            ],
                            'parent': [{
                                'id':
                                'https://localhost:8888/app/xapi/assignment/' +
                                self.assignment.uuid + '/question',
                                'objectType':
                                'Activity'
                            }]
                        }
                    })
Example #13
0
class AccountLearningRecordTests(ComPAIRLearningRecordTestCase):

    def setUp(self):
        super(ComPAIRLearningRecordTestCase, self).setUp()
        self.data = SimpleAnswersTestData()
        self.auth_data = ThirdPartyAuthTestData()
        self.course = self.data.main_course
        self.assignment = self.data.assignments[0]

        self.cas_user_auth = self.auth_data.create_cas_user_auth(SystemRole.instructor)
        self.cas_user = self.cas_user_auth.user
        self.data.enrol_user(self.cas_user, self.data.get_course(), CourseRole.instructor)

        self.saml_user_auth = self.auth_data.create_saml_user_auth(SystemRole.instructor)
        self.saml_user = self.saml_user_auth.user
        self.data.enrol_user(self.saml_user, self.data.get_course(), CourseRole.instructor)

    def test_actor_accounts(self):
        for user, third_party_auth in [(self.cas_user, self.cas_user_auth), (self.saml_user, self.saml_user_auth)]:

            # test without homepage set
            # (should use compair actor account)
            self.app.config['LRS_ACTOR_ACCOUNT_USE_GLOBAL_UNIQUE_IDENTIFIER'] = True
            self.app.config['LRS_ACTOR_ACCOUNT_GLOBAL_UNIQUE_IDENTIFIER_HOMEPAGE'] = None
            expected_actor = self.get_compair_xapi_actor(user)

            on_assignment_modified.send(
                current_app._get_current_object(),
                event_name=on_assignment_modified.name,
                user=user,
                assignment=self.assignment
            )
            statements = self.get_and_clear_xapi_statement_log()
            self.assertEqual(len(statements), 1)
            self.assertEqual(statements[0]['actor'], expected_actor)

            # test with homepage set and global unique identifier not set
            # (should use compair actor account)
            self.app.config['LRS_ACTOR_ACCOUNT_GLOBAL_UNIQUE_IDENTIFIER_HOMEPAGE'] = "http://third.party.homepage"
            on_assignment_modified.send(
                current_app._get_current_object(),
                event_name=on_assignment_modified.name,
                user=user,
                assignment=self.assignment
            )
            statements = self.get_and_clear_xapi_statement_log()
            self.assertEqual(len(statements), 1)
            self.assertEqual(statements[0]['actor'], expected_actor)
            expected_actor = self.get_compair_xapi_actor(user)

            # test with homepage set and global unique identifier set
            # (should use cas/saml actor account with overridden value used for name)
            user.global_unique_identifier = 'mock_puid_รจ_'+third_party_auth.third_party_type.value
            db.session.commit()
            expected_actor = self.get_unique_identifier_xapi_actor(
                user,
                "http://third.party.homepage/",
                'mock_puid_รจ_'+third_party_auth.third_party_type.value
            )
            on_assignment_modified.send(
                current_app._get_current_object(),
                event_name=on_assignment_modified.name,
                user=user,
                assignment=self.assignment
            )
            statements = self.get_and_clear_xapi_statement_log()
            self.assertEqual(len(statements), 1)
            self.assertEqual(statements[0]['actor'], expected_actor)

            # disabling LRS_ACTOR_ACCOUNT_USE_GLOBAL_UNIQUE_IDENTIFIER should skip checking global unique identifer
            self.app.config['LRS_ACTOR_ACCOUNT_USE_GLOBAL_UNIQUE_IDENTIFIER'] = False
            expected_actor = self.get_compair_xapi_actor(user)
            on_assignment_modified.send(
                current_app._get_current_object(),
                event_name=on_assignment_modified.name,
                user=user,
                assignment=self.assignment
            )
            statements = self.get_and_clear_xapi_statement_log()
            self.assertEqual(len(statements), 1)
            self.assertEqual(statements[0]['actor'], expected_actor)
Example #14
0
    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'
        }
Example #15
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)
Example #16
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)
Example #17
0
    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'
        }
Example #18
0
class AnswerXAPITests(ComPAIRXAPITestCase):
    def setUp(self):
        super(ComPAIRXAPITestCase, self).setUp()
        self.data = SimpleAnswersTestData()
        self.user = self.data.authorized_student
        self.course = self.data.main_course
        self.assignment = self.data.assignments[0]
        self.answer = self.data.create_answer(self.assignment, self.user)

    def test_on_answer_modified(self):
        for draft in [True, False]:
            self.answer.draft = draft
            db.session.commit()

            # test without tracking
            on_answer_modified.send(
                current_app._get_current_object(),
                event_name=on_answer_modified.name,
                user=self.user,
                assignment=self.assignment,
                answer=self.answer
            )

            statements = self.get_and_clear_statement_log()
            self.assertEqual(len(statements), 2)

            self.assertEqual(statements[0]['actor'], self.get_compair_actor(self.user))
            if draft:
                self.assertEqual(statements[0]['verb'], {
                    'id': 'http://xapi.learninganalytics.ubc.ca/verb/draft',
                    'display': {'en-US': 'drafted'}
                })
            else:
                self.assertEqual(statements[0]['verb'], {
                    'id': 'http://activitystrea.ms/schema/1.0/submit',
                    'display': {'en-US': 'submitted'}
                })
            self.assertEqual(statements[0]['object'], {
                'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid,
                'definition': {'type': 'http://id.tincanapi.com/activitytype/solution', 'name': {'en-US': 'Assignment answer'}},
                'objectType': 'Activity'
            })
            if draft:
                self.assertEqual(statements[0]['result'], {
                    '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(" "))
                    },
                    'response': self.answer.content
                })
            else:
                self.assertEqual(statements[0]['result'], {
                    '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(" "))
                    },
                    'response': self.answer.content,
                    'success': True
                })
            self.assertEqual(statements[0]['context'], {
                'contextActivities': {
                    'grouping': [{
                        'id': 'https://localhost:8888/app/xapi/course/'+self.course.uuid,
                        'objectType': 'Activity'
                    },{
                        'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid,
                        'objectType': 'Activity'
                    },],
                    'parent': [{
                        'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/question',
                        'objectType': 'Activity'
                    }]
                }
            })

            self.assertEqual(statements[1]['actor'], self.get_compair_actor(self.user))
            if draft:
                self.assertEqual(statements[1]['verb'], {
                    'id': 'http://adlnet.gov/expapi/verbs/suspended',
                    'display': {'en-US': 'suspended'}
                })
            else:
                self.assertEqual(statements[1]['verb'], {
                    'id': 'http://adlnet.gov/expapi/verbs/completed',
                    'display': {'en-US': 'completed'}
                })
            self.assertEqual(statements[1]['object'], {
                'id': 'https://localhost:8888/app/xapi/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.assertEqual(statements[1]['result'], {
                'completion': not draft,
                'success': True
            })
            self.assertEqual(statements[1]['context'], {
                'contextActivities': {
                    'grouping': [{
                        'id': 'https://localhost:8888/app/xapi/course/'+self.course.uuid,
                        'objectType': 'Activity'
                    },],
                    'parent': [{
                        'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid,
                        'objectType': 'Activity'
                    }]
                }
            })

            # test with tracking
            tracking = self.generate_tracking(with_duration=True)
            tracking_json = json.dumps({ 'tracking':  tracking })
            with self.app.test_request_context(content_type='application/json', method='POST',
                    content_length=len(tracking_json), data=tracking_json):
                on_answer_modified.send(
                    current_app._get_current_object(),
                    event_name=on_answer_modified.name,
                    user=self.user,
                    assignment=self.assignment,
                    answer=self.answer
                )

                tracking_statements = self.get_and_clear_statement_log()
                self.assertEqual(len(statements), 2)
                self.assertEqual(statements[0]['actor'], tracking_statements[0]['actor'])
                self.assertEqual(statements[0]['verb'], tracking_statements[0]['verb'])
                self.assertEqual(statements[0]['object'], tracking_statements[0]['object'])
                self.assertEqual(statements[0]['result'], tracking_statements[0]['result'])
                self.assertEqual(tracking_statements[0]['context'], {
                    'registration': tracking.get('registration'),
                    'contextActivities': {
                        'grouping': [{
                            'id': 'https://localhost:8888/app/xapi/course/'+self.course.uuid,
                            'objectType': 'Activity'
                        },{
                            'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid,
                            'objectType': 'Activity'
                        },],
                        'parent': [{
                            'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/question',
                            'objectType': 'Activity'
                        }]
                    }
                })
                self.assertEqual(statements[1]['actor'], tracking_statements[1]['actor'])
                self.assertEqual(statements[1]['verb'], tracking_statements[1]['verb'])
                self.assertEqual(statements[1]['object'], tracking_statements[1]['object'])
                self.assertEqual(tracking_statements[1]['context'], {
                    'registration': tracking.get('registration'),
                    'contextActivities': {
                        'grouping': [{
                            'id': 'https://localhost:8888/app/xapi/course/'+self.course.uuid,
                            'objectType': 'Activity'
                        },],
                        'parent': [{
                            'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid,
                            'objectType': 'Activity'
                        }]
                    }
                })
                self.assertEqual(tracking_statements[1]['result'], {
                    'completion': not draft,
                    'duration': tracking.get('duration'),
                    'success': True
                })


    def test_on_answer_delete(self):
        for draft in [True, False]:
            self.answer.draft = draft
            db.session.commit()

            on_answer_delete.send(
                current_app._get_current_object(),
                event_name=on_answer_delete.name,
                user=self.user,
                answer=self.answer
            )

            statements = self.get_and_clear_statement_log()
            self.assertEqual(len(statements), 1)

            self.assertEqual(statements[0]['actor'], self.get_compair_actor(self.user))
            self.assertEqual(statements[0]['verb'], {
                'id': 'http://activitystrea.ms/schema/1.0/delete',
                'display': {'en-US': 'deleted'}
            })
            self.assertEqual(statements[0]['object'], {
                'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid,
                'definition': {'type': 'http://id.tincanapi.com/activitytype/solution', 'name': {'en-US': 'Assignment answer'}},
                'objectType': 'Activity'
            })
            self.assertNotIn('result', statements[0])
            self.assertEqual(statements[0]['context'], {
                'contextActivities': {
                    'grouping': [{
                        'id': 'https://localhost:8888/app/xapi/course/'+self.course.uuid,
                        'objectType': 'Activity'
                    },{
                        'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid,
                        'objectType': 'Activity'
                    },],
                    'parent': [{
                        'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/question',
                        'objectType': 'Activity'
                    }]
                }
            })

    def test_on_answer_flag(self):
        for draft in [True, False]:
            for flagged in [True, False]:
                self.answer.draft = draft
                self.answer.flagged = flagged
                db.session.commit()

                on_answer_flag.send(
                    current_app._get_current_object(),
                    event_name=on_answer_flag.name,
                    user=self.user,
                    answer=self.answer
                )

                statements = self.get_and_clear_statement_log()
                self.assertEqual(len(statements), 1)

                self.assertEqual(statements[0]['actor'], self.get_compair_actor(self.user))

                if flagged:
                    self.assertEqual(statements[0]['verb'], {
                        'id': 'http://xapi.learninganalytics.ubc.ca/verb/flag',
                        'display': {'en-US': 'flagged'}
                    })
                else:
                    self.assertEqual(statements[0]['verb'], {
                        'id': 'http://xapi.learninganalytics.ubc.ca/verb/unflag',
                        'display': {'en-US': 'unflagged'}
                    })

                self.assertEqual(statements[0]['object'], {
                    'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid,
                    'definition': {'type': 'http://id.tincanapi.com/activitytype/solution', 'name': {'en-US': 'Assignment answer'}},
                    'objectType': 'Activity'
                })
                self.assertNotIn('result', statements[0])
                self.assertEqual(statements[0]['context'], {
                    'contextActivities': {
                        'grouping': [{
                            'id': 'https://localhost:8888/app/xapi/course/'+self.course.uuid,
                            'objectType': 'Activity'
                        },{
                            'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid,
                            'objectType': 'Activity'
                        },],
                        'parent': [{
                            'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/question',
                            'objectType': 'Activity'
                        }]
                    }
                })