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)
def test_on_user_modified(self): # no changes provided on_user_modified.send( current_app._get_current_object(), event_name=on_user_modified.name, user=self.user ) 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/update', 'display': {'en-US': 'updated'} }) self.assertEqual(statements[0]['object'], { 'id': 'https://localhost:8888/app/#/user/'+self.user.uuid, 'definition': {'type': 'http://id.tincanapi.com/activitytype/user-profile', 'name': {'en-US': 'user profile'}}, 'objectType': 'Activity' }) self.assertNotIn('result', statements[0]) self.assertNotIn('context', statements[0]) # test with changes changes = { 'some_string': 'some_value', 'some_number': 42, 'some_dict': { 'some_string': 'some_value', 'some_number': 42 } } on_user_modified.send( current_app._get_current_object(), event_name=on_user_modified.name, user=self.user, data={'changes': changes} ) 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/update', 'display': {'en-US': 'updated'} }) self.assertEqual(statements[0]['object'], { 'id': 'https://localhost:8888/app/#/user/'+self.user.uuid, 'definition': {'type': 'http://id.tincanapi.com/activitytype/user-profile', 'name': {'en-US': 'user profile'}}, 'objectType': 'Activity' }) self.assertEqual(statements[0]['result'], { 'extensions': {'http://xapi.learninganalytics.ubc.ca/extension/fields-changed': changes} }) self.assertNotIn('context', statements[0])
def file_retrieve(file_type, file_name): file_dirs = { 'attachment': app.config['ATTACHMENT_UPLOAD_FOLDER'], 'report': app.config['REPORT_FOLDER'] } file_path = '{}/{}'.format(file_dirs[file_type], file_name) if not os.path.exists(file_path): return make_response('invalid file name', 404) # TODO: add bouncer mimetype, encoding = mimetypes.guess_type(file_name) attachment_filename = None as_attachment = False if file_type == 'attachment' and mimetype != "application/pdf": params = attachment_download_parser.parse_args() attachment_filename = params.get('name') #optionally set the download file name as_attachment = True on_get_file.send( current_app._get_current_object(), event_name=on_get_file.name, user=current_user, file_type=file_type, file_name=file_name, data={'file_path': file_path, 'mimetype': mimetype}) return send_file(file_path, mimetype=mimetype, attachment_filename=attachment_filename, as_attachment=as_attachment)
def test_on_logout(self): # not method provided on_logout.send(current_app._get_current_object(), event_name=on_logout.name, user=self.user) 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': 'https://brindlewaye.com/xAPITerms/verbs/loggedout/', 'display': { 'en-US': 'logged out' } }) self.assertEqual( statements[0]['object'], { 'id': 'https://localhost:8888/', 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/service', 'name': { 'en-US': 'ComPAIR' } }, 'objectType': 'Activity' }) self.assertNotIn('result', statements[0]) self.assertNotIn('context', statements[0])
def test_on_assignment_delete(self): on_assignment_delete.send( current_app._get_current_object(), event_name=on_assignment_delete.name, user=self.user, assignment=self.assignment ) 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/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', statements[0]) self.assertEqual(statements[0]['context'], { 'contextActivities': { 'parent': [{ 'id': 'https://localhost:8888/app/xapi/course/'+self.course.uuid, 'objectType': 'Activity' }] } })
def test_on_criterion_update(self): on_criterion_update.send( current_app._get_current_object(), event_name=on_criterion_update.name, user=self.user, criterion=self.criterion, ) 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/update", "display": {"en-US": "updated"}} ) self.assertEqual( statements[0]["object"], { "id": "https://localhost:8888/app/xapi/criterion/" + self.criterion.uuid, "definition": { "type": "http://adlnet.gov/expapi/activities/question", "name": {"en-US": self.criterion.name}, "description": {"en-US": self.criterion.description}, }, "objectType": "Activity", }, ) self.assertNotIn("result", statements[0]) self.assertNotIn("context", statements[0])
def file_retrieve(file_type, file_name): file_dirs = { 'attachment': app.config['ATTACHMENT_UPLOAD_FOLDER'], 'report': app.config['REPORT_FOLDER'] } file_path = '{}/{}'.format(file_dirs[file_type], file_name) if not os.path.exists(file_path): return make_response('invalid file name', 404) # TODO: add bouncer mimetype, encoding = mimetypes.guess_type(file_name) attachment_filename = None as_attachment = False if file_type == 'attachment' and mimetype != "application/pdf": params = attachment_download_parser.parse_args() attachment_filename = params.get( 'name') #optionally set the download file name as_attachment = True on_get_file.send(current_app._get_current_object(), event_name=on_get_file.name, user=current_user, file_type=file_type, file_name=file_name, data={ 'file_path': file_path, 'mimetype': mimetype }) return send_file(file_path, mimetype=mimetype, attachment_filename=attachment_filename, as_attachment=as_attachment)
def test_on_criterion_create(self): on_criterion_create.send( current_app._get_current_object(), event_name=on_criterion_create.name, user=self.user, criterion=self.criterion ) 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/author', 'display': {'en-US': 'authored'} }) self.assertEqual(statements[0]['object'], { 'id': 'https://localhost:8888/app/xapi/criterion/'+self.criterion.uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/question', 'name': {'en-US': self.criterion.name }, 'description': {'en-US': self.criterion.description } }, 'objectType': 'Activity' }) self.assertNotIn('result', statements[0]) self.assertNotIn('context', statements[0])
def test_on_course_modified(self): on_course_modified.send(current_app._get_current_object(), event_name=on_course_modified.name, user=self.user, course=self.course) 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/update', 'display': { 'en-US': 'updated' } }) self.assertEqual( statements[0]['object'], { 'id': 'https://localhost:8888/app/xapi/course/' + self.course.uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/course', 'name': { 'en-US': self.course.name } }, 'objectType': 'Activity' }) self.assertNotIn('result', statements[0]) self.assertNotIn('context', statements[0])
def login(): form = LoginForm() if form.validate_on_submit(): user = User.objects(username=form.username.data).first() login_user(user, remember=form.remember.data) identity_changed.send(current_app._get_current_object(), identity=Identity(user.username)) flash("You have been logged in", category="success") return redirect(url_for('stock.home')) return render_template('main/login.html', form=form)
def file_retrieve(file_type, file_name): file_dirs = { 'attachment': app.config['ATTACHMENT_UPLOAD_FOLDER'], 'report': app.config['REPORT_FOLDER'] } file_path = '{}/{}'.format(file_dirs[file_type], file_name) params = attachment_download_parser.parse_args() if file_type == 'attachment': attachment = File.get_by_file_name_or_404( file_name, joinedloads=['answers', 'assignments'] ) for answer in attachment.answers: require(READ, answer, title="Attachment Unavailable", message="Sorry, your role does not allow you to view the attachment.") for assignment in attachment.assignments: require(READ, assignment, title="Attachment Unavailable", message="Sorry, your role does not allow you to view the attachment.") # If attachment is in Kaltura, redirect the user if attachment.kaltura_media and KalturaAPI.enabled(): entry_id = attachment.kaltura_media.entry_id download_url = attachment.kaltura_media.download_url if entry_id: # Short-lived session of 60 seconds for user to start the media download kaltura_url = KalturaAPI.get_direct_access_url(entry_id, download_url, 60) return redirect(kaltura_url) if not os.path.exists(file_path): return make_response('invalid file name', 404) # TODO: add bouncer for reports mimetype, encoding = mimetypes.guess_type(file_name) attachment_filename = None as_attachment = False if file_type == 'attachment' and mimetype != "application/pdf": attachment_filename = params.get('name') #optionally set the download file name as_attachment = True on_get_file.send( current_app._get_current_object(), event_name=on_get_file.name, user=current_user, file_type=file_type, file_name=file_name, data={'file_path': file_path, 'mimetype': mimetype}) return send_file(file_path, mimetype=mimetype, attachment_filename=attachment_filename, as_attachment=as_attachment)
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' }] } })
def test_on_course_create(self): on_course_create.send(current_app._get_current_object(), event_name=on_course_create.name, user=self.user, course=self.course) events = self.get_and_clear_caliper_event_log() expected_caliper_event = { 'action': 'Created', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_course, 'session': self.get_caliper_session(self.get_compair_caliper_actor( self.user)), 'type': 'Event' } self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) statements = self.get_and_clear_xapi_statement_log() expected_xapi_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://activitystrea.ms/schema/1.0/author', 'display': { 'en-US': 'authored' } }, "object": self.expected_xapi_course, "context": { 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } } self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement)
def test_on_assignment_modified(self): on_assignment_modified.send( current_app._get_current_object(), event_name=on_assignment_modified.name, user=self.user, assignment=self.assignment ) events = self.get_and_clear_caliper_event_log() expected_caliper_event = { 'action': 'Modified', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_assignment, 'session': self.get_caliper_session(self.get_compair_caliper_actor(self.user)), 'type': 'Event' } self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) statements = self.get_and_clear_xapi_statement_log() expected_xapi_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://activitystrea.ms/schema/1.0/update', 'display': {'en-US': 'updated'} }, "object": self.expected_xapi_assignment, "context": { 'contextActivities': { 'parent': [self.expected_xapi_course], 'grouping': [self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } } self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement)
def check(): POST_USERNAME = request.form['username'] POST_PASSWORD = request.form['password'] print request.args.get('username') if models.User.query.filter_by(username=POST_USERNAME,password=POST_PASSWORD).first(): global user user = models.User.query.filter_by(username=POST_USERNAME,password=POST_PASSWORD).first() session['logged_in'] = True session['user_name'] = user.username session['user_id'] = user.id #render_template('index.html',) login_user(user) identity_changed.send(current_app._get_current_object(),identity=Identity(user.id)) global path1 path1 = os.path.join(root,'./static/users/%s/odt/' % session['user_name']) return redirect(url_for('index')) else: flash('wrong password!') return redirect(url_for('login')) return redirect(url_for('index'))
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_assignment_comment_modified(self): on_assignment_comment_modified.send( current_app._get_current_object(), event_name=on_assignment_comment_modified.name, user=self.user, assignment_comment=self.assignment_comment ) 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/update', 'display': {'en-US': 'updated'} }) self.assertEqual(statements[0]['object'], { 'id': 'https://localhost:8888/app/xapi/assignment/comment/'+self.assignment_comment.uuid, 'definition': {'type': 'http://activitystrea.ms/schema/1.0/comment', 'name': {'en-US': 'Assignment comment'}}, 'objectType': 'Activity' }) self.assertEqual(statements[0]['result'], { 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/character-count': len(self.assignment_comment.content), 'http://xapi.learninganalytics.ubc.ca/extension/word-count': len(self.assignment_comment.content.split(" ")) }, 'response': self.assignment_comment.content }) self.assertEqual(statements[0]['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' }] } })
def check(): POST_USERNAME = request.form['username'] POST_PASSWORD = request.form['password'] print request.args.get('username') if models.User.query.filter_by(username=POST_USERNAME, password=POST_PASSWORD).first(): global user user = models.User.query.filter_by(username=POST_USERNAME, password=POST_PASSWORD).first() session['logged_in'] = True session['user_name'] = user.username session['user_id'] = user.id #render_template('index.html',) login_user(user) identity_changed.send(current_app._get_current_object(), identity=Identity(user.id)) global path1 path1 = os.path.join(root, './static/users/%s/odt/' % session['user_name']) return redirect(url_for('index')) else: flash('wrong password!') return redirect(url_for('login')) return redirect(url_for('index'))
def test_on_answer_comment_create(self): # test self_evaluation_comment on_answer_comment_create.send( current_app._get_current_object(), event_name=on_answer_comment_create.name, user=self.user, answer_comment=self.self_evaluation_comment ) statements = self.get_and_clear_statement_log() self.assertEqual(len(statements), 0) # test evaluation_comment on_answer_comment_create.send( current_app._get_current_object(), event_name=on_answer_comment_create.name, user=self.user, answer_comment=self.evaluation_comment ) statements = self.get_and_clear_statement_log() self.assertEqual(len(statements), 0) # test public_comment/private_comment for comment in [self.public_comment, self.private_comment]: on_answer_comment_create.send( current_app._get_current_object(), event_name=on_answer_comment_create.name, user=self.user, answer_comment=comment ) 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://adlnet.gov/expapi/verbs/commented', 'display': {'en-US': 'commented'} }) self.assertEqual(statements[0]['object'], { 'id': 'https://localhost:8888/app/xapi/answer/comment/'+comment.uuid, 'definition': {'type': 'http://activitystrea.ms/schema/1.0/comment', 'name': {'en-US': 'Assignment answer comment'}}, 'objectType': 'Activity' }) self.assertEqual(statements[0]['result'], { 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/character-count': len(comment.content), 'http://xapi.learninganalytics.ubc.ca/extension/word-count': len(comment.content.split(" ")) }, 'response': comment.content }) 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' },{ 'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/question', 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid, 'objectType': 'Activity' }] } })
def test_on_detach_file(self): file_record = self.data.create_file(self.user) db.session.commit() # attache to assignment on_detach_file.send( current_app._get_current_object(), event_name=on_detach_file.name, user=self.user, file=file_record, assignment=self.assignment ) expected_caliper_object = { "id": 'https://localhost:8888/app/attachment/'+file_record.name, "type": "Document", "name": file_record.alias, "mediaType": 'application/pdf', "isPartOf": self.expected_caliper_assignment, "dateCreated": file_record.created.replace(tzinfo=pytz.utc).isoformat(), "dateModified": file_record.modified.replace(tzinfo=pytz.utc).isoformat() } expected_caliper_event = { 'action': 'Removed', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': expected_caliper_object, 'session': self.get_caliper_session(self.get_compair_caliper_actor(self.user)), 'type': 'Event' } events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_verb = { 'id': 'http://activitystrea.ms/schema/1.0/delete', 'display': {'en-US': 'deleted'} } expected_xapi_object = { 'id': 'https://localhost:8888/app/attachment/'+file_record.name, 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/file', 'name': {'en-US': file_record.alias}, 'extensions': { 'http://id.tincanapi.com/extension/mime-type': 'application/pdf' } }, 'objectType': 'Activity' } expected_xapi_context = { 'contextActivities': { 'parent': [self.expected_xapi_assignment], 'grouping': [self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_xapi_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": expected_xapi_verb, "object": expected_xapi_object, "context": expected_xapi_context } statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement) # attach to answer on_detach_file.send( current_app._get_current_object(), event_name=on_detach_file.name, user=self.user, file=file_record, answer=self.answer ) expected_caliper_object["isPartOf"] = self.expected_caliper_answer events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_context = { 'contextActivities': { 'parent': [self.expected_xapi_answer], 'grouping': [self.expected_xapi_assignment_question, self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_xapi_statement['context'] = expected_xapi_context statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement)
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_comment_create(self): # test self_evaluation_comment on_answer_comment_create.send( current_app._get_current_object(), event_name=on_answer_comment_create.name, user=self.user, answer_comment=self.self_evaluation_comment) statements = self.get_and_clear_statement_log() self.assertEqual(len(statements), 0) # test evaluation_comment on_answer_comment_create.send(current_app._get_current_object(), event_name=on_answer_comment_create.name, user=self.user, answer_comment=self.evaluation_comment) statements = self.get_and_clear_statement_log() self.assertEqual(len(statements), 0) # test public_comment/private_comment for comment in [self.public_comment, self.private_comment]: on_answer_comment_create.send( current_app._get_current_object(), event_name=on_answer_comment_create.name, user=self.user, answer_comment=comment) 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://adlnet.gov/expapi/verbs/commented', 'display': { 'en-US': 'commented' } }) self.assertEqual( statements[0]['object'], { 'id': 'https://localhost:8888/app/xapi/answer/comment/' + comment.uuid, 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/comment', 'name': { 'en-US': 'Assignment answer comment' } }, 'objectType': 'Activity' }) self.assertEqual( statements[0]['result'], { 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/character-count': len(comment.content), 'http://xapi.learninganalytics.ubc.ca/extension/word-count': len(comment.content.split(" ")) }, 'response': comment.content }) 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' }, { 'id': 'https://localhost:8888/app/xapi/assignment/' + self.assignment.uuid + '/question', 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/answer/' + self.answer.uuid, 'objectType': 'Activity' }] } })
def test_on_get_file(self): # not report or attachment on_get_file.send(current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="none", file_name="some_file") statements = self.get_and_clear_statement_log() self.assertEqual(len(statements), 0) # test report on_get_file.send(current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="report", file_name="some_report.csv") 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://id.tincanapi.com/verb/downloaded', 'display': { 'en-US': 'downloaded' } }) self.assertEqual( statements[0]['object'], { 'id': 'https://localhost:8888/app/report/some_report.csv', 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/file', 'name': { 'en-US': 'Report' } }, 'objectType': 'Activity' }) self.assertNotIn('result', statements[0]) self.assertNotIn('context', statements[0]) # test attachment without file record on_get_file.send(current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="attachment", file_name="some_file") statements = self.get_and_clear_statement_log() self.assertEqual(len(statements), 0) # test attachment file record (not linked to assignments or answers) file_record = self.data.create_file(self.user) on_get_file.send(current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="attachment", file_name=file_record.name) statements = self.get_and_clear_statement_log() self.assertEqual(len(statements), 0) # test attachment file record (assignment) self.assignment.file = file_record db.session.commit() on_get_file.send(current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="attachment", file_name=file_record.name) 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://id.tincanapi.com/verb/downloaded', 'display': { 'en-US': 'downloaded' } }) self.assertEqual( statements[0]['object'], { 'id': 'https://localhost:8888/app/attachment/' + file_record.name, 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/file', 'name': { 'en-US': 'Assignment attachment' } }, 'objectType': 'Activity' }) self.assertNotIn('result', statements[0]) self.assertEqual( statements[0]['context'], { 'contextActivities': { 'parent': [{ 'id': 'https://localhost:8888/app/xapi/assignment/' + self.assignment.uuid, 'objectType': 'Activity' }], 'grouping': [{ 'id': 'https://localhost:8888/app/xapi/course/' + self.data.main_course.uuid, 'objectType': 'Activity' }] } }) # test attachment file record (answer) self.assignment.file = None answer = AnswerFactory(assignment=self.assignment, user=self.user, file=file_record) db.session.commit() on_get_file.send(current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="attachment", file_name=file_record.name) 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://id.tincanapi.com/verb/downloaded', 'display': { 'en-US': 'downloaded' } }) self.assertEqual( statements[0]['object'], { 'id': 'https://localhost:8888/app/attachment/' + file_record.name, 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/file', 'name': { 'en-US': 'Assignment answer attachment' } }, 'objectType': 'Activity' }) self.assertNotIn('result', statements[0]) self.assertEqual( statements[0]['context'], { 'contextActivities': { 'parent': [{ 'id': 'https://localhost:8888/app/xapi/answer/' + answer.uuid, 'objectType': 'Activity' }], 'grouping': [{ 'id': 'https://localhost:8888/app/xapi/course/' + self.data.main_course.uuid, 'objectType': 'Activity' }, { 'id': 'https://localhost:8888/app/xapi/assignment/' + self.assignment.uuid, 'objectType': 'Activity' }, { 'id': 'https://localhost:8888/app/xapi/assignment/' + self.assignment.uuid + '/question', 'objectType': 'Activity' }] } })
def test_on_comparison_update(self): completed_count = 0 for (is_comparison_example, comparisons) in [(True, self.example_comparisons), (False, self.comparisons)]: answer_uuids = [comparisons[0].answer1_uuid, comparisons[0].answer2_uuid] answer_uuids.sort() for completed in [False, True]: for comparison in comparisons: comparison.completed = completed db.session.commit() if completed: completed_count+=1 # test without tracking on_comparison_update.send( current_app._get_current_object(), event_name=on_comparison_update.name, user=self.user, assignment=self.assignment, comparisons=comparisons, is_comparison_example=is_comparison_example ) statements = self.get_and_clear_statement_log() if completed and not is_comparison_example: self.assertEqual(len(statements), len(comparisons) + 1 + (2*len(comparisons))) else: self.assertEqual(len(statements), len(comparisons) + 1) index = 0 for comparison in comparisons: self.assertEqual(statements[index]['actor'], self.get_compair_actor(self.user)) if completed: self.assertEqual(statements[index]['verb'], { 'id': 'http://activitystrea.ms/schema/1.0/submit', 'display': {'en-US': 'submitted'} }) else: self.assertEqual(statements[index]['verb'], { 'id': 'http://xapi.learninganalytics.ubc.ca/verb/draft', 'display': {'en-US': 'drafted'} }) self.assertEqual(statements[index]['object'], { 'id': 'https://localhost:8888/app/xapi/comparison/'+comparison.uuid, 'definition': { 'type': 'http://id.tincanapi.com/activitytype/solution', 'name': {'en-US': 'Assignment criteria comparison' } }, 'objectType': 'Activity' }) if completed: self.assertEqual(statements[index]['result'], { 'response': 'https://localhost:8888/app/xapi/answer/'+comparison.winner_uuid, 'success': True }) else: self.assertEqual(statements[index]['result'], { 'response': 'https://localhost:8888/app/xapi/answer/'+comparison.winner_uuid }) self.assertEqual(statements[index]['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' },{ 'id': 'https://localhost:8888/app/xapi/answer/'+comparison.answer1_uuid, 'objectType': 'Activity' },{ 'id': 'https://localhost:8888/app/xapi/answer/'+comparison.answer2_uuid, 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/comparison?pair='+answer_uuids[0]+','+answer_uuids[1], 'objectType': 'Activity' },{ 'id': 'https://localhost:8888/app/xapi/criterion/'+comparison.criterion_uuid, 'objectType': 'Activity' }] } }) index+=1 self.assertEqual(statements[index]['actor'], self.get_compair_actor(self.user)) if completed: self.assertEqual(statements[index]['verb'], { 'id': 'http://adlnet.gov/expapi/verbs/completed', 'display': {'en-US': 'completed'} }) else: self.assertEqual(statements[index]['verb'], { 'id': 'http://adlnet.gov/expapi/verbs/suspended', 'display': {'en-US': 'suspended'} }) self.assertEqual(statements[index]['object'], { 'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/comparison?pair='+answer_uuids[0]+','+answer_uuids[1], 'definition': { 'type': 'http://adlnet.gov/expapi/activities/question', 'name': {'en-US': 'Assignment comparison #'+str(completed_count+1) }, 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/comparison': completed_count+1, 'http://xapi.learninganalytics.ubc.ca/extension/score-algorithm': PairingAlgorithm.random.value } }, 'objectType': 'Activity' }) self.assertEqual(statements[index]['result'], { 'completion': completed, 'success': True }) grouping = [{ 'id': 'https://localhost:8888/app/xapi/course/'+self.course.uuid, 'objectType': 'Activity' }] for comparison in comparisons: grouping.append({ 'id': 'https://localhost:8888/app/xapi/criterion/'+comparison.criterion_uuid, 'objectType': 'Activity' }) self.assertEqual(statements[index]['context'], { 'contextActivities': { 'grouping': grouping, 'parent': [{ 'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid, 'objectType': 'Activity' },{ 'id': 'https://localhost:8888/app/xapi/answer/'+comparisons[0].answer1_uuid, 'objectType': 'Activity' },{ 'id': 'https://localhost:8888/app/xapi/answer/'+comparisons[0].answer2_uuid, 'objectType': 'Activity' }] } }) index+=1 if completed and not is_comparison_example: for answer in [self.answer1, self.answer2]: for score in answer.scores: comparison = next(comparison for comparison in comparisons \ if comparison.criterion_id == score.criterion_id) self.assertEqual(statements[index]['actor'], self.get_compair_actor(self.user)) self.assertEqual(statements[index]['verb'], { 'id': 'http://www.tincanapi.co.uk/verbs/evaluated', 'display': {'en-US': 'evaluated'} }) self.assertEqual(statements[index]['object'], { 'id': 'https://localhost:8888/app/xapi/answer/'+answer.uuid+'?criterion='+score.criterion_uuid, 'definition': { 'type': 'http://id.tincanapi.com/activitytype/solution', 'name': {'en-US': 'Assignment answer based on criterion' } }, 'objectType': 'Activity' }) self.assertEqual(statements[index]['result'], { 'score': { 'raw': float(score.score) }, 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/score-details': { 'algorithm': score.scoring_algorithm.value, 'loses': score.loses, 'opponents': score.opponents, 'rounds': score.rounds, 'wins': score.wins, } } }) self.assertEqual(statements[index]['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' },{ 'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/question', 'objectType': 'Activity' }], 'other': [{ 'id': 'https://localhost:8888/app/xapi/comparison/'+comparison.uuid, 'objectType': 'Activity' },{ 'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/comparison?pair='+answer_uuids[0]+','+answer_uuids[1], 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/answer/'+answer.uuid, 'objectType': 'Activity' },{ 'id': 'https://localhost:8888/app/xapi/criterion/'+score.criterion_uuid, 'objectType': 'Activity' }] } }) index+=1 # 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_comparison_update.send( current_app._get_current_object(), event_name=on_comparison_update.name, user=self.user, assignment=self.assignment, comparisons=comparisons, is_comparison_example=is_comparison_example ) tracking_statements = self.get_and_clear_statement_log() self.assertEqual(len(statements), len(tracking_statements)) index = 0 for comparison in comparisons: self.assertEqual(statements[index]['actor'], tracking_statements[index]['actor']) self.assertEqual(statements[index]['verb'], tracking_statements[index]['verb']) self.assertEqual(statements[index]['object'], tracking_statements[index]['object']) self.assertEqual(statements[index]['result'], tracking_statements[index]['result']) self.assertEqual(tracking_statements[index]['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' },{ 'id': 'https://localhost:8888/app/xapi/answer/'+comparison.answer1_uuid, 'objectType': 'Activity' },{ 'id': 'https://localhost:8888/app/xapi/answer/'+comparison.answer2_uuid, 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/comparison?pair='+answer_uuids[0]+','+answer_uuids[1], 'objectType': 'Activity' },{ 'id': 'https://localhost:8888/app/xapi/criterion/'+comparison.criterion_uuid, 'objectType': 'Activity' }] } }) index+=1 self.assertEqual(statements[index]['actor'], tracking_statements[index]['actor']) self.assertEqual(statements[index]['verb'], tracking_statements[index]['verb']) self.assertEqual(statements[index]['object'], tracking_statements[index]['object']) self.assertEqual(tracking_statements[index]['result'], { 'completion': completed, 'duration': tracking.get('duration'), 'success': True }) self.assertEqual(tracking_statements[index]['context'], { 'registration': tracking.get('registration'), 'contextActivities': { 'grouping': grouping, 'parent': [{ 'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid, 'objectType': 'Activity' },{ 'id': 'https://localhost:8888/app/xapi/answer/'+comparisons[0].answer1_uuid, 'objectType': 'Activity' },{ 'id': 'https://localhost:8888/app/xapi/answer/'+comparisons[0].answer2_uuid, 'objectType': 'Activity' }] } }) index+=1 if completed and not is_comparison_example: for answer in [self.answer1, self.answer2]: for score in answer.scores: comparison = next(comparison for comparison in comparisons \ if comparison.criterion_id == score.criterion_id) self.assertEqual(statements[index]['actor'], tracking_statements[index]['actor']) self.assertEqual(statements[index]['verb'], tracking_statements[index]['verb']) self.assertEqual(statements[index]['object'], tracking_statements[index]['object']) self.assertEqual(statements[index]['result'], tracking_statements[index]['result']) self.assertEqual(tracking_statements[index]['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' },{ 'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/question', 'objectType': 'Activity' }], 'other': [{ 'id': 'https://localhost:8888/app/xapi/comparison/'+comparison.uuid, 'objectType': 'Activity' },{ 'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/comparison?pair='+answer_uuids[0]+','+answer_uuids[1], 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/answer/'+answer.uuid, 'objectType': 'Activity' },{ 'id': 'https://localhost:8888/app/xapi/criterion/'+score.criterion_uuid, 'objectType': 'Activity' }] } }) index+=1
def test_on_get_file(self): # not report or attachment on_get_file.send(current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="none", file_name="some_file") events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 0) statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 0) # test report on_get_file.send(current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="report", file_name="some_report.csv") expected_caliper_object = { "id": 'https://localhost:8888/app/report/some_report.csv', "type": "Document", "name": "some_report.csv", "mediaType": "text/csv" } expected_caliper_event = { 'action': 'Viewed', 'actor': self.get_compair_caliper_actor(self.user), 'object': expected_caliper_object, 'session': self.get_caliper_session(self.get_compair_caliper_actor( self.user)), 'type': 'ViewEvent' } events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_object = { 'id': 'https://localhost:8888/app/report/some_report.csv', 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/file', 'name': { 'en-US': 'some_report.csv' }, 'extensions': { 'http://id.tincanapi.com/extension/mime-type': "text/csv" } }, 'objectType': 'Activity' } expected_xapi_verb = { 'id': 'http://id.tincanapi.com/verb/downloaded', 'display': { 'en-US': 'downloaded' } } expected_xapi_context = { 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_xapi_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": expected_xapi_verb, "object": expected_xapi_object, "context": expected_xapi_context } statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement) # test attachment without file record on_get_file.send(current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="attachment", file_name="some_file") events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 0) statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 0) # test attachment file record (not linked to assignments or answers) file_record = self.data.create_file(self.user) on_get_file.send(current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="attachment", file_name=file_record.name) events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 0) statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 0) # test attachment file record (assignment) self.assignment.file_id = file_record.id db.session.commit() on_get_file.send(current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="attachment", file_name=file_record.name) expected_caliper_object = { "id": 'https://localhost:8888/app/attachment/' + file_record.name, "type": "Document", "name": file_record.alias, "mediaType": 'application/pdf', "isPartOf": self.expected_caliper_assignment, "dateCreated": file_record.created.replace(tzinfo=pytz.utc).isoformat(), "dateModified": file_record.modified.replace(tzinfo=pytz.utc).isoformat() } self.expected_caliper_assignment[ 'dateModified'] = self.assignment.modified.replace( tzinfo=pytz.utc).isoformat() expected_caliper_event['object'] = expected_caliper_object expected_caliper_event['membership'] = self.get_caliper_membership( self.course, self.user, self.lti_context) events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_object = { 'id': 'https://localhost:8888/app/attachment/' + file_record.name, 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/file', 'name': { 'en-US': file_record.alias }, 'extensions': { 'http://id.tincanapi.com/extension/mime-type': 'application/pdf' } }, 'objectType': 'Activity' } expected_xapi_context = { 'contextActivities': { 'parent': [self.expected_xapi_assignment], 'grouping': [ self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section ] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_xapi_statement['object'] = expected_xapi_object expected_xapi_statement['context'] = expected_xapi_context statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement) # test attachment file record (answer) self.assignment.file_id = None self.answer.file_id = file_record.id db.session.commit() on_get_file.send(current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="attachment", file_name=file_record.name) self.expected_caliper_assignment_question[ 'dateModified'] = self.assignment.modified.replace( tzinfo=pytz.utc).isoformat() self.expected_caliper_assignment[ 'dateModified'] = self.assignment.modified.replace( tzinfo=pytz.utc).isoformat() self.expected_caliper_answer[ 'dateModified'] = self.answer.modified.replace( tzinfo=pytz.utc).isoformat() expected_caliper_object["isPartOf"] = self.expected_caliper_answer events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_context = { 'contextActivities': { 'parent': [self.expected_xapi_answer], 'grouping': [ self.expected_xapi_assignment_question, self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section ] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_xapi_statement['context'] = expected_xapi_context statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement)
def test_on_assignment_comment_modified(self): on_assignment_comment_modified.send( current_app._get_current_object(), event_name=on_assignment_comment_modified.name, user=self.user, assignment_comment=self.assignment_comment) 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/update', 'display': { 'en-US': 'updated' } }) self.assertEqual( statements[0]['object'], { 'id': 'https://localhost:8888/app/xapi/assignment/comment/' + self.assignment_comment.uuid, 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/comment', 'name': { 'en-US': 'Assignment comment' } }, 'objectType': 'Activity' }) self.assertEqual( statements[0]['result'], { 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/character-count': len(self.assignment_comment.content), 'http://xapi.learninganalytics.ubc.ca/extension/word-count': len(self.assignment_comment.content.split(" ")) }, 'response': self.assignment_comment.content }) self.assertEqual( statements[0]['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' }] } })
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' }] } })
def test_on_login_with_method(self): # no login method provided on_login_with_method.send(current_app._get_current_object(), event_name=on_login_with_method.name, user=self.user) 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': 'https://brindlewaye.com/xAPITerms/verbs/loggedin/', 'display': { 'en-US': 'logged in' } }) self.assertEqual( statements[0]['object'], { 'id': 'https://localhost:8888/', 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/service', 'name': { 'en-US': 'ComPAIR' } }, 'objectType': 'Activity' }) self.assertNotIn('result', statements[0]) self.assertNotIn('context', statements[0]) # test with login method login_method = "SAML" on_login_with_method.send(current_app._get_current_object(), event_name=on_login_with_method.name, user=self.user, login_method=login_method) 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': 'https://brindlewaye.com/xAPITerms/verbs/loggedin/', 'display': { 'en-US': 'logged in' } }) self.assertEqual( statements[0]['object'], { 'id': 'https://localhost:8888/', 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/service', 'name': { 'en-US': 'ComPAIR' } }, 'objectType': 'Activity' }) self.assertNotIn('result', statements[0]) self.assertEqual( statements[0]['context'], { 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/login-method': login_method } })
def test_on_get_file(self): # not report or attachment on_get_file.send( current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="none", file_name="some_file" ) statements = self.get_and_clear_statement_log() self.assertEqual(len(statements), 0) # test report on_get_file.send( current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="report", file_name="some_report.csv" ) 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://id.tincanapi.com/verb/downloaded', 'display': {'en-US': 'downloaded'} }) self.assertEqual(statements[0]['object'], { 'id': 'https://localhost:8888/app/report/some_report.csv', 'definition': {'type': 'http://activitystrea.ms/schema/1.0/file', 'name': {'en-US': 'Report'}}, 'objectType': 'Activity' }) self.assertNotIn('result', statements[0]) self.assertNotIn('context', statements[0]) # test attachment without file record on_get_file.send( current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="attachment", file_name="some_file" ) statements = self.get_and_clear_statement_log() self.assertEqual(len(statements), 0) # test attachment file record (not linked to assignments or answers) file_record = self.data.create_file(self.user) on_get_file.send( current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="attachment", file_name=file_record.name ) statements = self.get_and_clear_statement_log() self.assertEqual(len(statements), 0) # test attachment file record (assignment) self.assignment.file = file_record db.session.commit() on_get_file.send( current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="attachment", file_name=file_record.name ) 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://id.tincanapi.com/verb/downloaded', 'display': {'en-US': 'downloaded'} }) self.assertEqual(statements[0]['object'], { 'id': 'https://localhost:8888/app/attachment/'+file_record.name, 'definition': {'type': 'http://activitystrea.ms/schema/1.0/file', 'name': {'en-US': 'Assignment attachment'}}, 'objectType': 'Activity' }) self.assertNotIn('result', statements[0]) self.assertEqual(statements[0]['context'], { 'contextActivities': { 'parent': [ {'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid, 'objectType': 'Activity'} ], 'grouping': [ {'id': 'https://localhost:8888/app/xapi/course/'+self.data.main_course.uuid, 'objectType': 'Activity'} ] } }) # test attachment file record (answer) self.assignment.file = None answer = AnswerFactory( assignment=self.assignment, user=self.user, file=file_record ) db.session.commit() on_get_file.send( current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="attachment", file_name=file_record.name ) 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://id.tincanapi.com/verb/downloaded', 'display': {'en-US': 'downloaded'} }) self.assertEqual(statements[0]['object'], { 'id': 'https://localhost:8888/app/attachment/'+file_record.name, 'definition': {'type': 'http://activitystrea.ms/schema/1.0/file', 'name': {'en-US': 'Assignment answer attachment'}}, 'objectType': 'Activity' }) self.assertNotIn('result', statements[0]) self.assertEqual(statements[0]['context'], { 'contextActivities': { 'parent': [ {'id': 'https://localhost:8888/app/xapi/answer/'+answer.uuid, 'objectType': 'Activity'} ], 'grouping': [ {'id': 'https://localhost:8888/app/xapi/course/'+self.data.main_course.uuid, 'objectType': 'Activity'}, {'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid, 'objectType': 'Activity'}, {'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/question', 'objectType': 'Activity'} ] } })
def logout(): logout_user() identity_changed.send(current_app._get_current_object(), identity=AnonymousIdentity()) return redirect(url_for('main.login'))
def test_on_answer_comment_delete(self): # test self_evaluation_comment on_answer_comment_delete.send( current_app._get_current_object(), event_name=on_answer_comment_delete.name, user=self.user, answer_comment=self.self_evaluation_comment ) 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/comment/'+self.self_evaluation_comment.uuid, 'definition': {'type': 'http://activitystrea.ms/schema/1.0/review', 'name': {'en-US': 'Assignment self-evaluation review'}}, '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' },{ 'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid, 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/self-evaluation', 'objectType': 'Activity' }] } }) # test evaluation_comment on_answer_comment_delete.send( current_app._get_current_object(), event_name=on_answer_comment_delete.name, user=self.user, answer_comment=self.evaluation_comment ) 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/comment/'+self.evaluation_comment.uuid, 'definition': {'type': 'http://activitystrea.ms/schema/1.0/comment', 'name': {'en-US': 'Assignment answer evaluation comment'}}, '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' },{ 'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/question', 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid, 'objectType': 'Activity' }] } }) # test public_comment/private_comment for comment in [self.public_comment, self.private_comment]: on_answer_comment_delete.send( current_app._get_current_object(), event_name=on_answer_comment_delete.name, user=self.user, answer_comment=comment ) 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/comment/'+comment.uuid, 'definition': {'type': 'http://activitystrea.ms/schema/1.0/comment', 'name': {'en-US': 'Assignment answer comment'}}, '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' },{ 'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/question', 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid, 'objectType': 'Activity' }] } })
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_comparison_update(self): completed_count = 0 for (is_comparison_example, comparison) in [(True, self.example_comparison), (False, self.comparison)]: for completed in [False, True]: comparison.completed = completed comparison.winner = WinningAnswer.answer1 if completed else None db.session.commit() on_comparison_update.send( current_app._get_current_object(), event_name=on_comparison_update.name, user=self.user, assignment=self.assignment, comparison=comparison, is_comparison_example=is_comparison_example ) if completed: completed_count += 1 current_comparison = completed_count if completed else completed_count + 1 self.expected_caliper_comparison_question['name'] = "Assignment comparison #"+str(current_comparison) self.expected_caliper_comparison_question['id'] = "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/"+str(current_comparison) self.expected_xapi_comparison_question['definition']['name']['en-US'] = "Assignment comparison #{}".format(current_comparison) self.expected_xapi_comparison_question['id'] = "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/"+str(current_comparison) expected_caliper_comparison_attempt = { 'assignable': self.expected_caliper_comparison_question, 'assignee': self.get_compair_caliper_actor(self.user), 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/"+str(current_comparison)+"/attempt/"+comparison.attempt_uuid, 'duration': "PT05M00S", 'startedAtTime': comparison.attempt_started.replace(tzinfo=pytz.utc).isoformat(), 'endedAtTime': comparison.attempt_ended.replace(tzinfo=pytz.utc).isoformat(), 'type': 'Attempt' } expected_caliper_comparison = { 'attempt': expected_caliper_comparison_attempt, 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/"+comparison.uuid, 'type': 'Response', 'dateCreated': comparison.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': comparison.modified.replace(tzinfo=pytz.utc).isoformat(), 'extensions': { 'pairingAlgorithm': self.comparison.pairing_algorithm.value, 'winner': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/answer/"+self.answer1.uuid if completed else "Undecided", 'criteria': { "https://localhost:8888/app/criterion/"+self.criterion.uuid: "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/answer/"+self.answer1.uuid, }, "answers": [ "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/answer/"+self.answer1.uuid, "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/answer/"+self.answer2.uuid, ], "completed": completed } } events = self.get_and_clear_caliper_event_log() expected_caliper_events = [{ 'action': 'Completed', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_comparison_question, 'generated': expected_caliper_comparison, 'session': self.get_caliper_session(self.get_compair_caliper_actor(self.user)), 'type': 'AssessmentItemEvent' }, { 'action': 'Submitted', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_assignment, 'generated': { 'assignable': self.expected_caliper_assignment, 'assignee': self.get_compair_caliper_actor(self.user), 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/attempt/"+comparison.attempt_uuid, 'duration': "PT05M00S", 'startedAtTime': comparison.attempt_started.replace(tzinfo=pytz.utc).isoformat(), 'endedAtTime': comparison.attempt_ended.replace(tzinfo=pytz.utc).isoformat(), 'type': 'Attempt' }, 'session': self.get_caliper_session(self.get_compair_caliper_actor(self.user)), 'type': 'AssessmentEvent' }] if not is_comparison_example and completed: expected_caliper_events.append({ 'action': 'Ranked', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_answer1, 'session': self.get_caliper_session(self.get_compair_caliper_actor(self.user)), 'type': 'Event' }) expected_caliper_events.append({ 'action': 'Ranked', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_answer2, 'session': self.get_caliper_session(self.get_compair_caliper_actor(self.user)), 'type': 'Event' }) self.assertEqual(len(events), len(expected_caliper_events)) for index, expected_event in enumerate(expected_caliper_events): self.assertEqual(events[index], expected_event) expected_xapi_comparison_attempt = { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/question/"+str(current_comparison)+"/attempt/"+comparison.attempt_uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/attempt', 'extensions': { 'http://id.tincanapi.com/extension/attempt': { 'duration': "PT05M00S", 'startedAtTime': comparison.attempt_started.replace(tzinfo=pytz.utc).isoformat(), 'endedAtTime': comparison.attempt_ended.replace(tzinfo=pytz.utc).isoformat(), } } }, 'objectType': 'Activity' } expected_xapi_comparison = { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/comparison/"+comparison.uuid, 'definition': { 'type': 'http://id.tincanapi.com/activitytype/solution', 'name': { 'en-US': "Assignment comparison" }, 'extensions': { 'http://id.tincanapi.com/extension/completed': completed } }, 'objectType': 'Activity' } statements = self.get_and_clear_xapi_statement_log() expected_xapi_statements = [{ "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://adlnet.gov/expapi/verbs/completed', 'display': {'en-US': 'completed'} }, "object": expected_xapi_comparison, "context": { 'registration': comparison.attempt_uuid, 'contextActivities': { 'parent': [self.expected_xapi_comparison_question, self.expected_xapi_answer1, self.expected_xapi_answer2, expected_xapi_comparison_attempt], 'grouping': [self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } }, "result": { 'success': True, 'duration': "PT05M00S", 'completion': completed, 'response': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/answer/"+self.answer1.uuid if completed else "Undecided", 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/criteria': { "https://localhost:8888/app/criterion/"+self.criterion.uuid: "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/answer/"+self.answer1.uuid, } } } }, { "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://activitystrea.ms/schema/1.0/submit', 'display': {'en-US': 'submitted'} }, "object": { 'id': "https://localhost:8888/app/course/"+self.course.uuid+"/assignment/"+self.assignment.uuid+"/attempt/"+comparison.attempt_uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/attempt', 'extensions': { 'http://id.tincanapi.com/extension/attempt': { 'duration': "PT05M00S", 'startedAtTime': comparison.attempt_started.replace(tzinfo=pytz.utc).isoformat(), 'endedAtTime': comparison.attempt_ended.replace(tzinfo=pytz.utc).isoformat(), } } }, 'objectType': 'Activity' }, "context": { 'registration': comparison.attempt_uuid, 'contextActivities': { 'parent': [self.expected_xapi_assignment], 'grouping': [self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } }, "result": { 'success': True, 'completion': completed } }] if not is_comparison_example and completed: expected_xapi_statements.append({ "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'https://w3id.org/xapi/dod-isd/verbs/ranked', 'display': {'en-US': 'ranked'} }, "object": self.expected_xapi_answer1, "context": { 'registration': comparison.attempt_uuid, 'contextActivities': { 'parent': [self.expected_xapi_assignment_question, self.expected_xapi_answer1_attempt], 'grouping': [self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } }, "result": { 'score': {'raw': 5.0}, 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/score-details': { 'algorithm': self.assignment.scoring_algorithm.value, 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0, 'criteria': { "https://localhost:8888/app/criterion/"+self.criterion.uuid: { 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0 }, } } } } }) expected_xapi_statements.append({ "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'https://w3id.org/xapi/dod-isd/verbs/ranked', 'display': {'en-US': 'ranked'} }, "object": self.expected_xapi_answer2, "context": { 'registration': comparison.attempt_uuid, 'contextActivities': { 'parent': [self.expected_xapi_assignment_question, self.expected_xapi_answer2_attempt], 'grouping': [self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } }, "result": { 'score': {'raw': 5.0}, 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/score-details': { 'algorithm': self.assignment.scoring_algorithm.value, 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0, 'criteria': { "https://localhost:8888/app/criterion/"+self.criterion.uuid: { 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0 }, } } } } }) self.assertEqual(len(statements), len(expected_xapi_statements)) for index, expected_statement in enumerate(expected_xapi_statements): self.assertEqual(statements[index], expected_statement)
def test_on_comparison_update(self): completed_count = 0 for (is_comparison_example, comparison) in [(True, self.example_comparison), (False, self.comparison)]: for completed in [False, True]: comparison.completed = completed comparison.winner = WinningAnswer.answer1 if completed else None db.session.commit() if completed: completed_count += 1 # test without tracking on_comparison_update.send( current_app._get_current_object(), event_name=on_comparison_update.name, user=self.user, assignment=self.assignment, comparison=comparison, is_comparison_example=is_comparison_example) statements = self.get_and_clear_statement_log() if completed and not is_comparison_example: self.assertEqual( len(statements), len(self.example_comparison_criteria) + 1 + 1 + (2 * (len(self.example_comparison_criteria) + 1))) else: self.assertEqual( len(statements), len(self.example_comparison_criteria) + 1 + 1) index = 0 self.assertEqual(statements[index]['actor'], self.get_compair_actor(self.user)) if completed: self.assertEqual( statements[index]['verb'], { 'id': 'http://activitystrea.ms/schema/1.0/submit', 'display': { 'en-US': 'submitted' } }) else: self.assertEqual( statements[index]['verb'], { 'id': 'http://xapi.learninganalytics.ubc.ca/verb/draft', 'display': { 'en-US': 'drafted' } }) self.assertEqual( statements[index]['object'], { 'id': 'https://localhost:8888/app/xapi/comparison/' + comparison.uuid, 'definition': { 'type': 'http://id.tincanapi.com/activitytype/solution', 'name': { 'en-US': 'Assignment comparison' } }, 'objectType': 'Activity' }) if completed: self.assertEqual( statements[index]['result'], { 'response': 'https://localhost:8888/app/xapi/answer/' + comparison.answer1_uuid, 'success': True }) else: self.assertEqual(statements[index]['result'], {'response': 'Undecided'}) self.assertEqual( statements[index]['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' }, { 'id': 'https://localhost:8888/app/xapi/answer/' + comparison.answer1_uuid, 'objectType': 'Activity' }, { 'id': 'https://localhost:8888/app/xapi/answer/' + comparison.answer2_uuid, 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/comparison/' + comparison.uuid + '/question', 'objectType': 'Activity' }] } }) index += 1 for comparison_criterion in comparison.comparison_criteria: self.assertEqual(statements[index]['actor'], self.get_compair_actor(self.user)) if completed: self.assertEqual( statements[index]['verb'], { 'id': 'http://activitystrea.ms/schema/1.0/submit', 'display': { 'en-US': 'submitted' } }) else: self.assertEqual( statements[index]['verb'], { 'id': 'http://xapi.learninganalytics.ubc.ca/verb/draft', 'display': { 'en-US': 'drafted' } }) self.assertEqual( statements[index]['object'], { 'id': 'https://localhost:8888/app/xapi/comparison/criterion/' + comparison_criterion.uuid, 'definition': { 'type': 'http://id.tincanapi.com/activitytype/solution', 'name': { 'en-US': 'Assignment criterion comparison' } }, 'objectType': 'Activity' }) if completed: self.assertEqual( statements[index]['result'], { 'response': 'https://localhost:8888/app/xapi/answer/' + comparison_criterion.answer1_uuid, 'success': True }) else: self.assertEqual( statements[index]['result'], { 'response': 'https://localhost:8888/app/xapi/answer/' + comparison_criterion.answer1_uuid }) self.assertEqual( statements[index]['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' }, { 'id': 'https://localhost:8888/app/xapi/answer/' + comparison.answer1_uuid, 'objectType': 'Activity' }, { 'id': 'https://localhost:8888/app/xapi/answer/' + comparison.answer2_uuid, 'objectType': 'Activity' }, { 'id': 'https://localhost:8888/app/xapi/comparison/' + comparison.uuid + '/question', 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/comparison/' + comparison.uuid, 'objectType': 'Activity' }, { 'id': 'https://localhost:8888/app/xapi/criterion/' + comparison_criterion.criterion_uuid, 'objectType': 'Activity' }] } }) index += 1 self.assertEqual(statements[index]['actor'], self.get_compair_actor(self.user)) if completed: self.assertEqual( statements[index]['verb'], { 'id': 'http://adlnet.gov/expapi/verbs/completed', 'display': { 'en-US': 'completed' } }) else: self.assertEqual( statements[index]['verb'], { 'id': 'http://adlnet.gov/expapi/verbs/suspended', 'display': { 'en-US': 'suspended' } }) self.assertEqual( statements[index]['object'], { 'id': 'https://localhost:8888/app/xapi/comparison/' + comparison.uuid + '/question', 'definition': { 'type': 'http://adlnet.gov/expapi/activities/question', 'name': { 'en-US': 'Assignment comparison #' + str(completed_count + 1) }, 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/comparison': completed_count + 1, 'http://xapi.learninganalytics.ubc.ca/extension/score-algorithm': PairingAlgorithm.adaptive_min_delta.value } }, 'objectType': 'Activity' }) self.assertEqual(statements[index]['result'], { 'completion': completed, 'success': True }) grouping = [{ 'id': 'https://localhost:8888/app/xapi/course/' + self.course.uuid, 'objectType': 'Activity' }] for comparison_criterion in comparison.comparison_criteria: grouping.append({ 'id': 'https://localhost:8888/app/xapi/criterion/' + comparison_criterion.criterion_uuid, 'objectType': 'Activity' }) self.assertEqual( statements[index]['context'], { 'contextActivities': { 'grouping': grouping, 'parent': [{ 'id': 'https://localhost:8888/app/xapi/assignment/' + self.assignment.uuid, 'objectType': 'Activity' }, { 'id': 'https://localhost:8888/app/xapi/answer/' + comparison.answer1_uuid, 'objectType': 'Activity' }, { 'id': 'https://localhost:8888/app/xapi/answer/' + comparison.answer2_uuid, 'objectType': 'Activity' }] } }) index += 1 if completed and not is_comparison_example: for answer in [self.answer1, self.answer2]: score = answer.score self.assertEqual(statements[index]['actor'], self.get_compair_actor(self.user)) self.assertEqual( statements[index]['verb'], { 'id': 'http://www.tincanapi.co.uk/verbs/evaluated', 'display': { 'en-US': 'evaluated' } }) self.assertEqual( statements[index]['object'], { 'id': 'https://localhost:8888/app/xapi/answer/' + answer.uuid, 'definition': { 'type': 'http://id.tincanapi.com/activitytype/solution', 'name': { 'en-US': 'Assignment answer' } }, 'objectType': 'Activity' }) self.assertEqual( statements[index]['result'], { 'score': { 'raw': float(score.score) }, 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/score-details': { 'algorithm': score.scoring_algorithm.value, 'loses': score.loses, 'opponents': score.opponents, 'rounds': score.rounds, 'wins': score.wins, } } }) self.assertEqual( statements[index]['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' }], 'other': [{ 'id': 'https://localhost:8888/app/xapi/comparison/' + comparison.uuid, 'objectType': 'Activity' }, { 'id': 'https://localhost:8888/app/xapi/comparison/' + comparison.uuid + '/question', 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/assignment/' + self.assignment.uuid + '/question', 'objectType': 'Activity' }] } }) index += 1 for score in answer.criteria_scores: comparison_criterion = next(comparison_criterion for comparison_criterion in comparison.comparison_criteria \ if comparison_criterion.criterion_id == score.criterion_id) self.assertEqual(statements[index]['actor'], self.get_compair_actor(self.user)) self.assertEqual( statements[index]['verb'], { 'id': 'http://www.tincanapi.co.uk/verbs/evaluated', 'display': { 'en-US': 'evaluated' } }) self.assertEqual( statements[index]['object'], { 'id': 'https://localhost:8888/app/xapi/answer/' + answer.uuid + '?criterion=' + score.criterion_uuid, 'definition': { 'type': 'http://id.tincanapi.com/activitytype/solution', 'name': { 'en-US': 'Assignment answer based on criterion' } }, 'objectType': 'Activity' }) self.assertEqual( statements[index]['result'], { 'score': { 'raw': float(score.score) }, 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/score-details': { 'algorithm': score.scoring_algorithm.value, 'loses': score.loses, 'opponents': score.opponents, 'rounds': score.rounds, 'wins': score.wins, } } }) self.assertEqual( statements[index]['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' }, { 'id': 'https://localhost:8888/app/xapi/assignment/' + self.assignment.uuid + '/question', 'objectType': 'Activity' }], 'other': [{ 'id': 'https://localhost:8888/app/xapi/comparison/criterion/' + comparison_criterion.uuid, 'objectType': 'Activity' }, { 'id': 'https://localhost:8888/app/xapi/comparison/' + comparison.uuid, 'objectType': 'Activity' }, { 'id': 'https://localhost:8888/app/xapi/comparison/' + comparison.uuid + '/question', 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/answer/' + answer.uuid, 'objectType': 'Activity' }, { 'id': 'https://localhost:8888/app/xapi/criterion/' + score.criterion_uuid, 'objectType': 'Activity' }] } }) index += 1 # 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_comparison_update.send( current_app._get_current_object(), event_name=on_comparison_update.name, user=self.user, assignment=self.assignment, comparison=comparison, is_comparison_example=is_comparison_example) tracking_statements = self.get_and_clear_statement_log() self.assertEqual(len(statements), len(tracking_statements)) index = 0 self.assertEqual(statements[index]['actor'], tracking_statements[index]['actor']) self.assertEqual(statements[index]['verb'], tracking_statements[index]['verb']) self.assertEqual(statements[index]['object'], tracking_statements[index]['object']) self.assertEqual(statements[index]['result'], tracking_statements[index]['result']) self.assertEqual( tracking_statements[index]['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' }, { 'id': 'https://localhost:8888/app/xapi/answer/' + comparison.answer1_uuid, 'objectType': 'Activity' }, { 'id': 'https://localhost:8888/app/xapi/answer/' + comparison.answer2_uuid, 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/comparison/' + comparison.uuid + '/question', 'objectType': 'Activity' }] } }) index += 1 for comparison_criterion in comparison.comparison_criteria: self.assertEqual(statements[index]['actor'], tracking_statements[index]['actor']) self.assertEqual(statements[index]['verb'], tracking_statements[index]['verb']) self.assertEqual(statements[index]['object'], tracking_statements[index]['object']) self.assertEqual(statements[index]['result'], tracking_statements[index]['result']) self.assertEqual( tracking_statements[index]['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' }, { 'id': 'https://localhost:8888/app/xapi/answer/' + comparison.answer1_uuid, 'objectType': 'Activity' }, { 'id': 'https://localhost:8888/app/xapi/answer/' + comparison.answer2_uuid, 'objectType': 'Activity' }, { 'id': 'https://localhost:8888/app/xapi/comparison/' + comparison.uuid + '/question', 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/comparison/' + comparison.uuid, 'objectType': 'Activity' }, { 'id': 'https://localhost:8888/app/xapi/criterion/' + comparison_criterion.criterion_uuid, 'objectType': 'Activity' }] } }) index += 1 self.assertEqual(statements[index]['actor'], tracking_statements[index]['actor']) self.assertEqual(statements[index]['verb'], tracking_statements[index]['verb']) self.assertEqual(statements[index]['object'], tracking_statements[index]['object']) self.assertEqual( tracking_statements[index]['result'], { 'completion': completed, 'duration': tracking.get('duration'), 'success': True }) self.assertEqual( tracking_statements[index]['context'], { 'registration': tracking.get('registration'), 'contextActivities': { 'grouping': grouping, 'parent': [{ 'id': 'https://localhost:8888/app/xapi/assignment/' + self.assignment.uuid, 'objectType': 'Activity' }, { 'id': 'https://localhost:8888/app/xapi/answer/' + comparison.answer1_uuid, 'objectType': 'Activity' }, { 'id': 'https://localhost:8888/app/xapi/answer/' + comparison.answer2_uuid, 'objectType': 'Activity' }] } }) index += 1 if completed and not is_comparison_example: for answer in [self.answer1, self.answer2]: self.assertEqual( statements[index]['actor'], tracking_statements[index]['actor']) self.assertEqual( statements[index]['verb'], tracking_statements[index]['verb']) self.assertEqual( statements[index]['object'], tracking_statements[index]['object']) self.assertEqual( statements[index]['result'], tracking_statements[index]['result']) self.assertEqual( tracking_statements[index]['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' }], 'other': [{ 'id': 'https://localhost:8888/app/xapi/comparison/' + comparison.uuid, 'objectType': 'Activity' }, { 'id': 'https://localhost:8888/app/xapi/comparison/' + comparison.uuid + '/question', 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/assignment/' + self.assignment.uuid + '/question', 'objectType': 'Activity' }] } }) index += 1 for score in answer.criteria_scores: comparison_criterion = next(comparison_criterion for comparison_criterion in comparison.comparison_criteria \ if comparison_criterion.criterion_id == score.criterion_id) self.assertEqual( statements[index]['actor'], tracking_statements[index]['actor']) self.assertEqual( statements[index]['verb'], tracking_statements[index]['verb']) self.assertEqual( statements[index]['object'], tracking_statements[index]['object']) self.assertEqual( statements[index]['result'], tracking_statements[index]['result']) self.assertEqual( tracking_statements[index]['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' }, { 'id': 'https://localhost:8888/app/xapi/assignment/' + self.assignment.uuid + '/question', 'objectType': 'Activity' }], 'other': [{ 'id': 'https://localhost:8888/app/xapi/comparison/criterion/' + comparison_criterion.uuid, 'objectType': 'Activity' }, { 'id': 'https://localhost:8888/app/xapi/comparison/' + comparison.uuid, 'objectType': 'Activity' }, { 'id': 'https://localhost:8888/app/xapi/comparison/' + comparison.uuid + '/question', 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/answer/' + answer.uuid, 'objectType': 'Activity' }, { 'id': 'https://localhost:8888/app/xapi/criterion/' + score.criterion_uuid, 'objectType': 'Activity' }] } }) index += 1
def test_on_detach_file(self): file_record = self.data.create_file(self.user) db.session.commit() # attache to assignment on_detach_file.send(current_app._get_current_object(), event_name=on_detach_file.name, user=self.user, file=file_record, assignment=self.assignment) expected_caliper_object = { "id": 'https://localhost:8888/app/attachment/' + file_record.name, "type": "Document", "name": file_record.alias, "mediaType": 'application/pdf', "isPartOf": self.expected_caliper_assignment, "dateCreated": file_record.created.replace(tzinfo=pytz.utc).isoformat(), "dateModified": file_record.modified.replace(tzinfo=pytz.utc).isoformat() } expected_caliper_event = { 'action': 'Removed', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': expected_caliper_object, 'session': self.get_caliper_session(self.get_compair_caliper_actor( self.user)), 'type': 'Event' } events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_verb = { 'id': 'http://activitystrea.ms/schema/1.0/delete', 'display': { 'en-US': 'deleted' } } expected_xapi_object = { 'id': 'https://localhost:8888/app/attachment/' + file_record.name, 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/file', 'name': { 'en-US': file_record.alias }, 'extensions': { 'http://id.tincanapi.com/extension/mime-type': 'application/pdf' } }, 'objectType': 'Activity' } expected_xapi_context = { 'contextActivities': { 'parent': [self.expected_xapi_assignment], 'grouping': [ self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section ] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_xapi_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": expected_xapi_verb, "object": expected_xapi_object, "context": expected_xapi_context } statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement) # attach to answer on_detach_file.send(current_app._get_current_object(), event_name=on_detach_file.name, user=self.user, file=file_record, answer=self.answer) expected_caliper_object["isPartOf"] = self.expected_caliper_answer events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_context = { 'contextActivities': { 'parent': [self.expected_xapi_answer], 'grouping': [ self.expected_xapi_assignment_question, self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section ] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_xapi_statement['context'] = expected_xapi_context statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement)
def file_retrieve(file_type, file_name): file_dirs = { 'attachment': app.config['ATTACHMENT_UPLOAD_FOLDER'], 'report': app.config['REPORT_FOLDER'] } file_path = '{}/{}'.format(file_dirs[file_type], file_name) params = attachment_download_parser.parse_args() if file_type == 'attachment': attachment = File.get_by_file_name_or_404( file_name, joinedloads=['answers', 'assignments']) for answer in attachment.answers: require( READ, answer, title="Attachment Unavailable", message= "Sorry, your role does not allow you to view the attachment." ) for assignment in attachment.assignments: require( READ, assignment, title="Attachment Unavailable", message= "Sorry, your role does not allow you to view the attachment." ) # If attachment is in Kaltura, redirect the user if attachment.kaltura_media and KalturaAPI.enabled(): entry_id = attachment.kaltura_media.entry_id download_url = attachment.kaltura_media.download_url if entry_id: # Short-lived session of 60 seconds for user to start the media download kaltura_url = KalturaAPI.get_direct_access_url( entry_id, download_url, 60) return redirect(kaltura_url) if not os.path.exists(file_path): return make_response('invalid file name', 404) # TODO: add bouncer for reports mimetype, encoding = mimetypes.guess_type(file_name) attachment_filename = None as_attachment = False if file_type == 'attachment' and mimetype != "application/pdf": attachment_filename = params.get( 'name') #optionally set the download file name as_attachment = True on_get_file.send(current_app._get_current_object(), event_name=on_get_file.name, user=current_user, file_type=file_type, file_name=file_name, data={ 'file_path': file_path, 'mimetype': mimetype }) return send_file(file_path, mimetype=mimetype, attachment_filename=attachment_filename, as_attachment=as_attachment)
def test_on_comparison_update(self): completed_count = 0 for (is_comparison_example, comparison) in [(True, self.example_comparison), (False, self.comparison)]: for completed in [False, True]: comparison.completed = completed comparison.winner = WinningAnswer.answer1 if completed else None db.session.commit() on_comparison_update.send( current_app._get_current_object(), event_name=on_comparison_update.name, user=self.user, assignment=self.assignment, comparison=comparison, is_comparison_example=is_comparison_example) if completed: completed_count += 1 current_comparison = completed_count if completed else completed_count + 1 self.expected_caliper_comparison_question[ 'name'] = "Assignment comparison #" + str( current_comparison) self.expected_caliper_comparison_question[ 'id'] = "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/comparison/question/" + str( current_comparison) self.expected_xapi_comparison_question['definition']['name'][ 'en-US'] = "Assignment comparison #{}".format( current_comparison) self.expected_xapi_comparison_question[ 'id'] = "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/comparison/question/" + str( current_comparison) expected_caliper_comparison_attempt = { 'assignable': self.expected_caliper_comparison_question, 'assignee': self.get_compair_caliper_actor(self.user), 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/comparison/question/" + str(current_comparison) + "/attempt/" + comparison.attempt_uuid, 'duration': "PT05M00S", 'startedAtTime': comparison.attempt_started.replace( tzinfo=pytz.utc).isoformat(), 'endedAtTime': comparison.attempt_ended.replace( tzinfo=pytz.utc).isoformat(), 'type': 'Attempt' } expected_caliper_comparison = { 'attempt': expected_caliper_comparison_attempt, 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/comparison/" + comparison.uuid, 'type': 'Response', 'dateCreated': comparison.created.replace(tzinfo=pytz.utc).isoformat(), 'dateModified': comparison.modified.replace(tzinfo=pytz.utc).isoformat(), 'extensions': { 'pairingAlgorithm': self.comparison.pairing_algorithm.value, 'winner': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/answer/" + self.answer1.uuid if completed else "Undecided", 'criteria': { "https://localhost:8888/app/criterion/" + self.criterion.uuid: "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/answer/" + self.answer1.uuid, }, "answers": [ "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/answer/" + self.answer1.uuid, "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/answer/" + self.answer2.uuid, ], "completed": completed } } events = self.get_and_clear_caliper_event_log() expected_caliper_events = [{ 'action': 'Completed', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_comparison_question, 'generated': expected_caliper_comparison, 'session': self.get_caliper_session( self.get_compair_caliper_actor(self.user)), 'type': 'AssessmentItemEvent' }, { 'action': 'Submitted', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_assignment, 'generated': { 'assignable': self.expected_caliper_assignment, 'assignee': self.get_compair_caliper_actor(self.user), 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/attempt/" + comparison.attempt_uuid, 'duration': "PT05M00S", 'startedAtTime': comparison.attempt_started.replace( tzinfo=pytz.utc).isoformat(), 'endedAtTime': comparison.attempt_ended.replace( tzinfo=pytz.utc).isoformat(), 'type': 'Attempt' }, 'session': self.get_caliper_session( self.get_compair_caliper_actor(self.user)), 'type': 'AssessmentEvent' }] if not is_comparison_example and completed: expected_caliper_events.append({ 'action': 'Ranked', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_answer1, 'session': self.get_caliper_session( self.get_compair_caliper_actor(self.user)), 'type': 'Event' }) expected_caliper_events.append({ 'action': 'Ranked', 'actor': self.get_compair_caliper_actor(self.user), 'membership': self.get_caliper_membership(self.course, self.user, self.lti_context), 'object': self.expected_caliper_answer2, 'session': self.get_caliper_session( self.get_compair_caliper_actor(self.user)), 'type': 'Event' }) self.assertEqual(len(events), len(expected_caliper_events)) for index, expected_event in enumerate( expected_caliper_events): self.assertEqual(events[index], expected_event) expected_xapi_comparison_attempt = { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/comparison/question/" + str(current_comparison) + "/attempt/" + comparison.attempt_uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/attempt', 'extensions': { 'http://id.tincanapi.com/extension/attempt': { 'duration': "PT05M00S", 'startedAtTime': comparison.attempt_started.replace( tzinfo=pytz.utc).isoformat(), 'endedAtTime': comparison.attempt_ended.replace( tzinfo=pytz.utc).isoformat(), } } }, 'objectType': 'Activity' } expected_xapi_comparison = { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/comparison/" + comparison.uuid, 'definition': { 'type': 'http://id.tincanapi.com/activitytype/solution', 'name': { 'en-US': "Assignment comparison" }, 'extensions': { 'http://id.tincanapi.com/extension/completed': completed } }, 'objectType': 'Activity' } statements = self.get_and_clear_xapi_statement_log() expected_xapi_statements = [{ "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://adlnet.gov/expapi/verbs/completed', 'display': { 'en-US': 'completed' } }, "object": expected_xapi_comparison, "context": { 'registration': comparison.attempt_uuid, 'contextActivities': { 'parent': [ self.expected_xapi_comparison_question, self.expected_xapi_answer1, self.expected_xapi_answer2, expected_xapi_comparison_attempt ], 'grouping': [ self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section ] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } }, "result": { 'success': True, 'duration': "PT05M00S", 'completion': completed, 'response': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/answer/" + self.answer1.uuid if completed else "Undecided", 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/criteria': { "https://localhost:8888/app/criterion/" + self.criterion.uuid: "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/answer/" + self.answer1.uuid, } } } }, { "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'http://activitystrea.ms/schema/1.0/submit', 'display': { 'en-US': 'submitted' } }, "object": { 'id': "https://localhost:8888/app/course/" + self.course.uuid + "/assignment/" + self.assignment.uuid + "/attempt/" + comparison.attempt_uuid, 'definition': { 'type': 'http://adlnet.gov/expapi/activities/attempt', 'extensions': { 'http://id.tincanapi.com/extension/attempt': { 'duration': "PT05M00S", 'startedAtTime': comparison.attempt_started.replace( tzinfo=pytz.utc).isoformat(), 'endedAtTime': comparison.attempt_ended.replace( tzinfo=pytz.utc).isoformat(), } } }, 'objectType': 'Activity' }, "context": { 'registration': comparison.attempt_uuid, 'contextActivities': { 'parent': [self.expected_xapi_assignment], 'grouping': [ self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section ] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } }, "result": { 'success': True, 'completion': completed } }] if not is_comparison_example and completed: expected_xapi_statements.append({ "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'https://w3id.org/xapi/dod-isd/verbs/ranked', 'display': { 'en-US': 'ranked' } }, "object": self.expected_xapi_answer1, "context": { 'registration': comparison.attempt_uuid, 'contextActivities': { 'parent': [ self.expected_xapi_assignment_question, self.expected_xapi_answer1_attempt ], 'grouping': [ self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section ] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } }, "result": { 'score': { 'raw': 5.0 }, 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/score-details': { 'algorithm': self.assignment.scoring_algorithm.value, 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0, 'criteria': { "https://localhost:8888/app/criterion/" + self.criterion.uuid: { 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0 }, } } } } }) expected_xapi_statements.append({ "actor": self.get_compair_xapi_actor(self.user), "verb": { 'id': 'https://w3id.org/xapi/dod-isd/verbs/ranked', 'display': { 'en-US': 'ranked' } }, "object": self.expected_xapi_answer2, "context": { 'registration': comparison.attempt_uuid, 'contextActivities': { 'parent': [ self.expected_xapi_assignment_question, self.expected_xapi_answer2_attempt ], 'grouping': [ self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section ] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } }, "result": { 'score': { 'raw': 5.0 }, 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/score-details': { 'algorithm': self.assignment.scoring_algorithm.value, 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0, 'criteria': { "https://localhost:8888/app/criterion/" + self.criterion.uuid: { 'loses': 0, 'opponents': 0, 'rounds': 0, 'score': 5, 'wins': 0 }, } } } } }) self.assertEqual(len(statements), len(expected_xapi_statements)) for index, expected_statement in enumerate( expected_xapi_statements): self.assertEqual(statements[index], expected_statement)
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)
def test_on_answer_comment_modified(self): for draft in [True, False]: self.self_evaluation_comment.draft = draft db.session.commit() # test self_evaluation_comment without tracking on_answer_comment_modified.send( current_app._get_current_object(), event_name=on_answer_comment_modified.name, user=self.user, answer_comment=self.self_evaluation_comment) 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/comment/' + self.self_evaluation_comment.uuid, 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/review', 'name': { 'en-US': 'Assignment self-evaluation review' } }, 'objectType': 'Activity' }) if draft: self.assertEqual( statements[0]['result'], { 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/character-count': len(self.self_evaluation_comment.content), 'http://xapi.learninganalytics.ubc.ca/extension/word-count': len(self.self_evaluation_comment.content.split( " ")) }, 'response': self.self_evaluation_comment.content }) else: self.assertEqual( statements[0]['result'], { 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/character-count': len(self.self_evaluation_comment.content), 'http://xapi.learninganalytics.ubc.ca/extension/word-count': len(self.self_evaluation_comment.content.split( " ")) }, 'response': self.self_evaluation_comment.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' }, { 'id': 'https://localhost:8888/app/xapi/answer/' + self.answer.uuid, 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/assignment/' + self.assignment.uuid + '/self-evaluation', '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 + '/self-evaluation', 'definition': { 'type': 'http://adlnet.gov/expapi/activities/question', 'name': { 'en-US': 'Assignment self-evaluation' } }, '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' }, { 'id': 'https://localhost:8888/app/xapi/answer/' + self.answer.uuid, 'objectType': 'Activity' }] } }) # test self_evaluation_comment 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_comment_modified.send( current_app._get_current_object(), event_name=on_answer_comment_modified.name, user=self.user, answer_comment=self.self_evaluation_comment) 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' }, { 'id': 'https://localhost:8888/app/xapi/answer/' + self.answer.uuid, 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/assignment/' + self.assignment.uuid + '/self-evaluation', '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' }, { 'id': 'https://localhost:8888/app/xapi/answer/' + self.answer.uuid, 'objectType': 'Activity' }] } }) self.assertEqual( tracking_statements[1]['result'], { 'completion': not draft, 'duration': tracking.get('duration'), 'success': True }) for draft in [True, False]: self.evaluation_comment.draft = draft db.session.commit() # test evaluation_comment on_answer_comment_modified.send( current_app._get_current_object(), event_name=on_answer_comment_modified.name, user=self.user, answer_comment=self.evaluation_comment) statements = self.get_and_clear_statement_log() self.assertEqual(len(statements), 1) 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://adlnet.gov/expapi/verbs/commented', 'display': { 'en-US': 'commented' } }) self.assertEqual( statements[0]['object'], { 'id': 'https://localhost:8888/app/xapi/answer/comment/' + self.evaluation_comment.uuid, 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/comment', 'name': { 'en-US': 'Assignment answer evaluation comment' } }, 'objectType': 'Activity' }) self.assertEqual( statements[0]['result'], { 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/character-count': len(self.evaluation_comment.content), 'http://xapi.learninganalytics.ubc.ca/extension/word-count': len(self.evaluation_comment.content.split(" ")) }, 'response': self.evaluation_comment.content }) 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' }, { 'id': 'https://localhost:8888/app/xapi/assignment/' + self.assignment.uuid + '/question', 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/answer/' + self.answer.uuid, 'objectType': 'Activity' }] } }) # test evaluation_comment 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_comment_modified.send( current_app._get_current_object(), event_name=on_answer_comment_modified.name, user=self.user, answer_comment=self.evaluation_comment) tracking_statements = self.get_and_clear_statement_log() self.assertEqual(len(statements), 1) 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' }, { 'id': 'https://localhost:8888/app/xapi/assignment/' + self.assignment.uuid + '/question', 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/answer/' + self.answer.uuid, 'objectType': 'Activity' }] } }) for comment in [self.public_comment, self.private_comment]: on_answer_comment_modified.send( current_app._get_current_object(), event_name=on_answer_comment_modified.name, user=self.user, answer_comment=comment) 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/update', 'display': { 'en-US': 'updated' } }) self.assertEqual( statements[0]['object'], { 'id': 'https://localhost:8888/app/xapi/answer/comment/' + comment.uuid, 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/comment', 'name': { 'en-US': 'Assignment answer comment' } }, 'objectType': 'Activity' }) self.assertEqual( statements[0]['result'], { 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/character-count': len(comment.content), 'http://xapi.learninganalytics.ubc.ca/extension/word-count': len(comment.content.split(" ")) }, 'response': comment.content }) 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' }, { 'id': 'https://localhost:8888/app/xapi/assignment/' + self.assignment.uuid + '/question', 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/answer/' + self.answer.uuid, 'objectType': 'Activity' }] } })
def test_on_user_modified(self): # no changes provided on_user_modified.send(current_app._get_current_object(), event_name=on_user_modified.name, user=self.user) expected_caliper_event = { 'action': 'Modified', 'actor': self.get_compair_caliper_actor(self.user), 'object': self.get_compair_caliper_actor(self.user), 'session': self.get_caliper_session(self.get_compair_caliper_actor( self.user)), 'type': 'Event' } events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_verb = { 'id': 'http://activitystrea.ms/schema/1.0/update', 'display': { 'en-US': 'updated' } } expected_xapi_context = { 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_xapi_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": expected_xapi_verb, "object": self.get_compair_xapi_actor(self.user), "context": expected_xapi_context } statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement) # test with changes changes = { 'some_string': 'some_value', 'some_number': 42, 'some_dict': { 'some_string': 'some_value', 'some_number': 42 } } on_user_modified.send(current_app._get_current_object(), event_name=on_user_modified.name, user=self.user, data={'changes': changes}) expected_caliper_event['extensions'] = {"changes": changes} events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_statement['result'] = { 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/changes': changes } } statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement)
def test_on_answer_comment_delete(self): # test self_evaluation_comment on_answer_comment_delete.send( current_app._get_current_object(), event_name=on_answer_comment_delete.name, user=self.user, answer_comment=self.self_evaluation_comment) 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/comment/' + self.self_evaluation_comment.uuid, 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/review', 'name': { 'en-US': 'Assignment self-evaluation review' } }, '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' }, { 'id': 'https://localhost:8888/app/xapi/answer/' + self.answer.uuid, 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/assignment/' + self.assignment.uuid + '/self-evaluation', 'objectType': 'Activity' }] } }) # test evaluation_comment on_answer_comment_delete.send(current_app._get_current_object(), event_name=on_answer_comment_delete.name, user=self.user, answer_comment=self.evaluation_comment) 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/comment/' + self.evaluation_comment.uuid, 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/comment', 'name': { 'en-US': 'Assignment answer evaluation comment' } }, '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' }, { 'id': 'https://localhost:8888/app/xapi/assignment/' + self.assignment.uuid + '/question', 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/answer/' + self.answer.uuid, 'objectType': 'Activity' }] } }) # test public_comment/private_comment for comment in [self.public_comment, self.private_comment]: on_answer_comment_delete.send( current_app._get_current_object(), event_name=on_answer_comment_delete.name, user=self.user, answer_comment=comment) 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/comment/' + comment.uuid, 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/comment', 'name': { 'en-US': 'Assignment answer comment' } }, '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' }, { 'id': 'https://localhost:8888/app/xapi/assignment/' + self.assignment.uuid + '/question', 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/answer/' + self.answer.uuid, 'objectType': 'Activity' }] } })
def test_on_answer_comment_modified(self): for draft in [True, False]: self.self_evaluation_comment.draft = draft db.session.commit() # test self_evaluation_comment without tracking on_answer_comment_modified.send( current_app._get_current_object(), event_name=on_answer_comment_modified.name, user=self.user, answer_comment=self.self_evaluation_comment ) 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/comment/'+self.self_evaluation_comment.uuid, 'definition': {'type': 'http://activitystrea.ms/schema/1.0/review', 'name': {'en-US': 'Assignment self-evaluation review'}}, 'objectType': 'Activity' }) if draft: self.assertEqual(statements[0]['result'], { 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/character-count': len(self.self_evaluation_comment.content), 'http://xapi.learninganalytics.ubc.ca/extension/word-count': len(self.self_evaluation_comment.content.split(" ")) }, 'response': self.self_evaluation_comment.content }) else: self.assertEqual(statements[0]['result'], { 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/character-count': len(self.self_evaluation_comment.content), 'http://xapi.learninganalytics.ubc.ca/extension/word-count': len(self.self_evaluation_comment.content.split(" ")) }, 'response': self.self_evaluation_comment.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' },{ 'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid, 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/self-evaluation', '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+'/self-evaluation', 'definition': {'type': 'http://adlnet.gov/expapi/activities/question', 'name': {'en-US': 'Assignment self-evaluation'}}, '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' },{ 'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid, 'objectType': 'Activity' }] } }) # test self_evaluation_comment 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_comment_modified.send( current_app._get_current_object(), event_name=on_answer_comment_modified.name, user=self.user, answer_comment=self.self_evaluation_comment ) 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' },{ 'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid, 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/self-evaluation', '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' },{ 'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid, 'objectType': 'Activity' }] } }) self.assertEqual(tracking_statements[1]['result'], { 'completion': not draft, 'duration': tracking.get('duration'), 'success': True }) for draft in [True, False]: self.evaluation_comment.draft = draft db.session.commit() # test evaluation_comment on_answer_comment_modified.send( current_app._get_current_object(), event_name=on_answer_comment_modified.name, user=self.user, answer_comment=self.evaluation_comment ) statements = self.get_and_clear_statement_log() self.assertEqual(len(statements), 1) 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://adlnet.gov/expapi/verbs/commented', 'display': {'en-US': 'commented'} }) self.assertEqual(statements[0]['object'], { 'id': 'https://localhost:8888/app/xapi/answer/comment/'+self.evaluation_comment.uuid, 'definition': {'type': 'http://activitystrea.ms/schema/1.0/comment', 'name': {'en-US': 'Assignment answer evaluation comment'}}, 'objectType': 'Activity' }) self.assertEqual(statements[0]['result'], { 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/character-count': len(self.evaluation_comment.content), 'http://xapi.learninganalytics.ubc.ca/extension/word-count': len(self.evaluation_comment.content.split(" ")) }, 'response': self.evaluation_comment.content }) 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' },{ 'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/question', 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid, 'objectType': 'Activity' }] } }) # test evaluation_comment 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_comment_modified.send( current_app._get_current_object(), event_name=on_answer_comment_modified.name, user=self.user, answer_comment=self.evaluation_comment ) tracking_statements = self.get_and_clear_statement_log() self.assertEqual(len(statements), 1) 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' },{ 'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/question', 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid, 'objectType': 'Activity' }] } }) for comment in [self.public_comment, self.private_comment]: on_answer_comment_modified.send( current_app._get_current_object(), event_name=on_answer_comment_modified.name, user=self.user, answer_comment=comment ) 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/update', 'display': {'en-US': 'updated'} }) self.assertEqual(statements[0]['object'], { 'id': 'https://localhost:8888/app/xapi/answer/comment/'+comment.uuid, 'definition': {'type': 'http://activitystrea.ms/schema/1.0/comment', 'name': {'en-US': 'Assignment answer comment'}}, 'objectType': 'Activity' }) self.assertEqual(statements[0]['result'], { 'extensions': { 'http://xapi.learninganalytics.ubc.ca/extension/character-count': len(comment.content), 'http://xapi.learninganalytics.ubc.ca/extension/word-count': len(comment.content.split(" ")) }, 'response': comment.content }) 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' },{ 'id': 'https://localhost:8888/app/xapi/assignment/'+self.assignment.uuid+'/question', 'objectType': 'Activity' }], 'parent': [{ 'id': 'https://localhost:8888/app/xapi/answer/'+self.answer.uuid, 'objectType': 'Activity' }] } })
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)
def test_on_get_file(self): # not report or attachment on_get_file.send( current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="none", file_name="some_file" ) events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 0) statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 0) # test report on_get_file.send( current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="report", file_name="some_report.csv" ) expected_caliper_object = { "id": 'https://localhost:8888/app/report/some_report.csv', "type": "Document", "name": "some_report.csv", "mediaType": "text/csv" } expected_caliper_event = { 'action': 'Viewed', 'actor': self.get_compair_caliper_actor(self.user), 'object': expected_caliper_object, 'session': self.get_caliper_session(self.get_compair_caliper_actor(self.user)), 'type': 'ViewEvent' } events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_object = { 'id': 'https://localhost:8888/app/report/some_report.csv', 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/file', 'name': {'en-US': 'some_report.csv'}, 'extensions': { 'http://id.tincanapi.com/extension/mime-type': "text/csv" } }, 'objectType': 'Activity' } expected_xapi_verb = { 'id': 'http://id.tincanapi.com/verb/downloaded', 'display': {'en-US': 'downloaded'} } expected_xapi_context = { 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_xapi_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": expected_xapi_verb, "object": expected_xapi_object, "context": expected_xapi_context } statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement) # test attachment without file record on_get_file.send( current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="attachment", file_name="some_file" ) events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 0) statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 0) # test attachment file record (not linked to assignments or answers) file_record = self.data.create_file(self.user) on_get_file.send( current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="attachment", file_name=file_record.name ) events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 0) statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 0) # test attachment file record (assignment) self.assignment.file_id = file_record.id db.session.commit() on_get_file.send( current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="attachment", file_name=file_record.name ) expected_caliper_object = { "id": 'https://localhost:8888/app/attachment/'+file_record.name, "type": "Document", "name": file_record.alias, "mediaType": 'application/pdf', "isPartOf": self.expected_caliper_assignment, "dateCreated": file_record.created.replace(tzinfo=pytz.utc).isoformat(), "dateModified": file_record.modified.replace(tzinfo=pytz.utc).isoformat() } self.expected_caliper_assignment['dateModified'] = self.assignment.modified.replace(tzinfo=pytz.utc).isoformat() expected_caliper_event['object'] = expected_caliper_object expected_caliper_event['membership'] = self.get_caliper_membership(self.course, self.user, self.lti_context) events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_object = { 'id': 'https://localhost:8888/app/attachment/'+file_record.name, 'definition': { 'type': 'http://activitystrea.ms/schema/1.0/file', 'name': {'en-US': file_record.alias}, 'extensions': { 'http://id.tincanapi.com/extension/mime-type': 'application/pdf' } }, 'objectType': 'Activity' } expected_xapi_context = { 'contextActivities': { 'parent': [self.expected_xapi_assignment], 'grouping': [self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_xapi_statement['object'] = expected_xapi_object expected_xapi_statement['context'] = expected_xapi_context statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement) # test attachment file record (answer) self.assignment.file_id = None self.answer.file_id = file_record.id db.session.commit() on_get_file.send( current_app._get_current_object(), event_name=on_get_file.name, user=self.user, file_type="attachment", file_name=file_record.name ) self.expected_caliper_assignment_question['dateModified'] = self.assignment.modified.replace(tzinfo=pytz.utc).isoformat() self.expected_caliper_assignment['dateModified'] = self.assignment.modified.replace(tzinfo=pytz.utc).isoformat() self.expected_caliper_answer['dateModified'] = self.answer.modified.replace(tzinfo=pytz.utc).isoformat() expected_caliper_object["isPartOf"] = self.expected_caliper_answer events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_context = { 'contextActivities': { 'parent': [self.expected_xapi_answer], 'grouping': [self.expected_xapi_assignment_question, self.expected_xapi_assignment, self.expected_xapi_course, self.expected_xapi_sis_course, self.expected_xapi_sis_section] }, 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_xapi_statement['context'] = expected_xapi_context statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement)
def test_on_user_modified(self): # no changes provided on_user_modified.send( current_app._get_current_object(), event_name=on_user_modified.name, user=self.user ) expected_caliper_event = { 'action': 'Modified', 'actor': self.get_compair_caliper_actor(self.user), 'object': self.get_compair_caliper_actor(self.user), 'session': self.get_caliper_session(self.get_compair_caliper_actor(self.user)), 'type': 'Event' } events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_verb = { 'id': 'http://activitystrea.ms/schema/1.0/update', 'display': {'en-US': 'updated'} } expected_xapi_context = { 'extensions': { 'http://id.tincanapi.com/extension/browser-info': {}, 'http://id.tincanapi.com/extension/session-info': self.get_xapi_session_info() } } expected_xapi_statement = { "actor": self.get_compair_xapi_actor(self.user), "verb": expected_xapi_verb, "object": self.get_compair_xapi_actor(self.user), "context": expected_xapi_context } statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_statement) # test with changes changes = { 'some_string': 'some_value', 'some_number': 42, 'some_dict': { 'some_string': 'some_value', 'some_number': 42 } } on_user_modified.send( current_app._get_current_object(), event_name=on_user_modified.name, user=self.user, data={'changes': changes} ) expected_caliper_event['extensions'] = { "changes": changes } events = self.get_and_clear_caliper_event_log() self.assertEqual(len(events), 1) self.assertEqual(events[0], expected_caliper_event) expected_xapi_statement['result'] = { 'extensions': {'http://xapi.learninganalytics.ubc.ca/extension/changes': changes} } statements = self.get_and_clear_xapi_statement_log() self.assertEqual(len(statements), 1) self.assertEqual(statements[0], expected_xapi_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)