diff --git a/common/test/acceptance/tests/helpers.py b/common/test/acceptance/tests/helpers.py index fcf3eefe8f..60cad0222e 100644 --- a/common/test/acceptance/tests/helpers.py +++ b/common/test/acceptance/tests/helpers.py @@ -277,8 +277,7 @@ class EventsTestMixin(object): def setUp(self): super(EventsTestMixin, self).setUp() self.event_collection = MongoClient()["test"]["events"] - self.event_collection.drop() - self.start_time = datetime.now() + self.reset_event_tracking() def assert_event_emitted_num_times(self, event_name, event_time, event_user_id, num_times_emitted): """ @@ -298,6 +297,43 @@ class EventsTestMixin(object): ).count(), num_times_emitted ) + def reset_event_tracking(self): + """ + Resets all event tracking so that previously captured events are removed. + """ + self.event_collection.drop() + self.start_time = datetime.now() + + def get_matching_events(self, event_type): + """ + Returns a cursor for the matching browser events. + """ + return self.event_collection.find({ + "event_type": event_type, + "time": {"$gt": self.start_time}, + }) + + def verify_events_of_type(self, event_type, expected_events): + """Verify that the expected events of a given type were logged. + + Args: + event_type (str): The type of event to be verified. + expected_events (list): A list of dicts representing the events that should + have been fired. + """ + EmptyPromise( + lambda: self.get_matching_events(event_type).count() >= len(expected_events), + "Waiting for the minimum number of events of type {type} to have been recorded".format(type=event_type) + ).fulfill() + + # Verify that the correct events were fired + cursor = self.get_matching_events(event_type) + actual_events = [] + for i in range(0, cursor.count()): + raw_event = cursor.next() + actual_events.append(json.loads(raw_event["event"])) + self.assertEqual(expected_events, actual_events) + class UniqueCourseTest(WebAppTest): """ diff --git a/common/test/acceptance/tests/lms/test_account_settings.py b/common/test/acceptance/tests/lms/test_account_settings.py index 5d6e8a894f..d2f1d7265c 100644 --- a/common/test/acceptance/tests/lms/test_account_settings.py +++ b/common/test/acceptance/tests/lms/test_account_settings.py @@ -10,13 +10,14 @@ from ...pages.lms.account_settings import AccountSettingsPage from ...pages.lms.auto_auth import AutoAuthPage from ...pages.lms.dashboard import DashboardPage +from ..helpers import EventsTestMixin -class AccountSettingsPageTest(WebAppTest): + +class AccountSettingsTestMixin(EventsTestMixin, WebAppTest): """ - Tests that verify behaviour of the Account Settings page. + Mixin with helper methods to test the account settings page. """ - SUCCESS_MESSAGE = 'Your changes have been saved.' USERNAME = "test" PASSWORD = "testpass" EMAIL = "test@example.com" @@ -25,17 +26,26 @@ class AccountSettingsPageTest(WebAppTest): """ Initialize account and pages. """ - super(AccountSettingsPageTest, self).setUp() + super(AccountSettingsTestMixin, self).setUp() - AutoAuthPage(self.browser, username=self.USERNAME, password=self.PASSWORD, email=self.EMAIL).visit() + self.user_id = AutoAuthPage( + self.browser, username=self.USERNAME, password=self.PASSWORD, email=self.EMAIL + ).visit().get_user_id() - self.account_settings_page = AccountSettingsPage(self.browser) - self.account_settings_page.visit() +class DashboardMenuTest(AccountSettingsTestMixin, WebAppTest): + """ + Tests that the dashboard menu works correctly with the account settings page. + """ def test_link_on_dashboard_works(self): """ - Scenario: Verify that account settings link is present in dashboard - page and we can use it to navigate to the page. + Scenario: Verify that the "Account Settings" link works from the dashboard. + + + Given that I am a registered user + And I visit my dashboard + And I click on "Account Settings" in the top drop down + Then I should see my account settings page """ dashboard_page = DashboardPage(self.browser) dashboard_page.visit() @@ -43,6 +53,41 @@ class AccountSettingsPageTest(WebAppTest): self.assertIn('Account Settings', dashboard_page.username_dropdown_link_text) dashboard_page.click_account_settings_link() + +class AccountSettingsPageTest(AccountSettingsTestMixin, WebAppTest): + """ + Tests that verify behaviour of the Account Settings page. + """ + SUCCESS_MESSAGE = 'Your changes have been saved.' + + def setUp(self): + """ + Initialize account and pages. + """ + super(AccountSettingsPageTest, self).setUp() + + # Visit the account settings page for the current user. + self.account_settings_page = AccountSettingsPage(self.browser) + self.account_settings_page.visit() + + def test_page_view_event(self): + """ + Scenario: An event should be recorded when the "Account Settings" + page is viewed. + + Given that I am a registered user + And I visit my account settings page + Then a page view analytics event should be recorded + """ + self.verify_events_of_type( + u"edx.user.settings.viewed", + [{ + u"user_id": int(self.user_id), + u"page": u"account", + u"visibility": None, + }] + ) + def test_all_sections_and_fields_are_present(self): """ Scenario: Verify that all sections and fields are present on the page. @@ -245,7 +290,7 @@ class AccountSettingsPageTest(WebAppTest): [u'Pakistan', u''], ) - def test_prefered_language_field(self): + def test_preferred_language_field(self): """ Test behaviour of "Preferred Language" field. """ diff --git a/common/test/acceptance/tests/lms/test_learner_profile.py b/common/test/acceptance/tests/lms/test_learner_profile.py index 6e6a9dd837..d9cc96f3bd 100644 --- a/common/test/acceptance/tests/lms/test_learner_profile.py +++ b/common/test/acceptance/tests/lms/test_learner_profile.py @@ -2,28 +2,23 @@ """ End-to-end tests for Student's Profile Page. """ +from datetime import datetime +from bok_choy.web_app_test import WebAppTest +from ...pages.common.logout import LogoutPage from ...pages.lms.account_settings import AccountSettingsPage from ...pages.lms.auto_auth import AutoAuthPage from ...pages.lms.learner_profile import LearnerProfilePage from ...pages.lms.dashboard import DashboardPage -from bok_choy.web_app_test import WebAppTest +from ..helpers import EventsTestMixin -class LearnerProfilePageTest(WebAppTest): +class LearnerProfileTestMixin(EventsTestMixin): """ - Tests that verify Student's Profile Page. + Mixin with helper methods for testing learner profile pages. """ - USER_1_NAME = 'user1' - USER_1_EMAIL = 'user1@edx.org' - USER_2_NAME = 'user2' - USER_2_EMAIL = 'user2@edx.org' - - MY_USER = 1 - OTHER_USER = 2 - PRIVACY_PUBLIC = 'all_users' PRIVACY_PRIVATE = 'private' @@ -32,39 +27,16 @@ class LearnerProfilePageTest(WebAppTest): PUBLIC_PROFILE_EDITABLE_FIELDS = ['country', 'language_proficiencies', 'bio'] - def setUp(self): + def log_in_as_unique_user(self): """ - Initialize pages. + Create a unique user and return the account's username and id. """ - super(LearnerProfilePageTest, self).setUp() + username = "test_{uuid}".format(uuid=self.unique_id[0:6]) + auto_auth_page = AutoAuthPage(self.browser, username=username).visit() + user_id = auto_auth_page.get_user_id() + return username, user_id - self.account_settings_page = AccountSettingsPage(self.browser) - self.dashboard_page = DashboardPage(self.browser) - - self.my_auto_auth_page = AutoAuthPage(self.browser, username=self.USER_1_NAME, email=self.USER_1_EMAIL).visit() - self.my_profile_page = LearnerProfilePage(self.browser, self.USER_1_NAME) - - self.other_auto_auth_page = AutoAuthPage( - self.browser, - username=self.USER_2_NAME, - email=self.USER_2_EMAIL - ).visit() - - self.other_profile_page = LearnerProfilePage(self.browser, self.USER_2_NAME) - - self.set_birth_year(self.MY_USER, birth_year='1990') - self.set_birth_year(self.OTHER_USER, birth_year='1990') - - def authenticate_as_user(self, user): - """ - Auto authenticate a user. - """ - if user == self.MY_USER: - self.my_auto_auth_page.visit() - elif user == self.OTHER_USER: - self.other_auto_auth_page.visit() - - def set_pubilc_profile_fields_data(self, profile_page): + def set_public_profile_fields_data(self, profile_page): """ Fill in the public profile fields of a user. """ @@ -72,56 +44,72 @@ class LearnerProfilePageTest(WebAppTest): profile_page.value_for_dropdown_field('country', 'United Kingdom') profile_page.value_for_textarea_field('bio', 'Nothing Special') - def visit_my_profile_page(self, user, privacy=None, authenticate=True): + def visit_profile_page(self, username, privacy=None): """ - Visits a users profile page. + Visits a user's profile page. """ - if authenticate: - self.authenticate_as_user(user) + profile_page = LearnerProfilePage(self.browser, username) - self.my_profile_page.visit() - self.my_profile_page.wait_for_page() - - if user is self.MY_USER and privacy is not None: - self.my_profile_page.privacy = privacy + # Change the privacy if requested by loading the page and + # changing the drop down + if privacy is not None: + profile_page.visit() + profile_page.wait_for_page() + profile_page.privacy = privacy if privacy == self.PRIVACY_PUBLIC: - self.set_pubilc_profile_fields_data(self.my_profile_page) + self.set_public_profile_fields_data(profile_page) - def visit_other_profile_page(self, user, privacy=None): + # Reset event tracking so that the tests only see events from + # loading the profile page. + self.reset_event_tracking() + + # Load the page + profile_page.visit() + profile_page.wait_for_page() + + return profile_page + + def set_birth_year(self, birth_year): """ - Visits a users profile page. + Set birth year for the current user to the specified value. """ - self.authenticate_as_user(user) - self.other_profile_page.visit() - self.other_profile_page.wait_for_page() + account_settings_page = AccountSettingsPage(self.browser) + account_settings_page.visit() + account_settings_page.wait_for_page() + self.assertEqual( + account_settings_page.value_for_dropdown_field('year_of_birth', str(birth_year)), + str(birth_year) + ) - if user is self.OTHER_USER and privacy is not None: - self.other_profile_page.visit() - self.other_profile_page.wait_for_page() - self.other_profile_page.privacy = privacy - - if privacy == self.PRIVACY_PUBLIC: - self.set_pubilc_profile_fields_data(self.other_profile_page) - - def set_birth_year(self, user, birth_year): + def verify_profile_page_view_event(self, profile_user_id, visibility=None): """ - Set birth year for `user` to the specified value. + Verifies that the correct view event was captured for the profile page. """ - self.authenticate_as_user(user) - self.account_settings_page.visit() - self.account_settings_page.wait_for_page() - self.assertEqual(self.account_settings_page.value_for_dropdown_field('year_of_birth', birth_year), birth_year) + self.verify_events_of_type( + u"edx.user.settings.viewed", + [{ + u"user_id": int(profile_user_id), + u"page": u"profile", + u"visibility": unicode(visibility), + }] + ) - def verify_profile_forced_private_message(self, birth_year, message=None): + +class OwnLearnerProfilePageTest(LearnerProfileTestMixin, WebAppTest): + """ + Tests that verify a student's own profile page. + """ + + def verify_profile_forced_private_message(self, username, birth_year, message=None): """ Verify age limit messages for a user. """ - self.set_birth_year(self.MY_USER, birth_year=birth_year) - self.visit_my_profile_page(self.MY_USER, authenticate=False) - self.assertTrue(self.my_profile_page.privacy_field_visible) - self.assertEqual(self.my_profile_page.age_limit_message_present, message is not None) - self.assertIn(message, self.my_profile_page.profile_forced_private_message) + self.set_birth_year(birth_year=birth_year if birth_year is not None else "") + profile_page = self.visit_profile_page(username) + self.assertTrue(profile_page.privacy_field_visible) + self.assertEqual(profile_page.age_limit_message_present, message is not None) + self.assertIn(message, profile_page.profile_forced_private_message) def test_dashboard_learner_profile_link(self): """ @@ -134,11 +122,14 @@ class LearnerProfilePageTest(WebAppTest): When I click on My Profile link. Then I will be navigated to My Profile page. """ - self.dashboard_page.visit() - self.dashboard_page.click_username_dropdown() - self.assertTrue('My Profile' in self.dashboard_page.username_dropdown_link_text) - self.dashboard_page.click_my_profile_link() - self.my_profile_page.wait_for_page() + username, user_id = self.log_in_as_unique_user() + dashboard_page = DashboardPage(self.browser) + dashboard_page.visit() + dashboard_page.click_username_dropdown() + self.assertTrue('My Profile' in dashboard_page.username_dropdown_link_text) + dashboard_page.click_my_profile_link() + my_profile_page = LearnerProfilePage(self.browser, username) + my_profile_page.wait_for_page() def test_fields_on_my_private_profile(self): """ @@ -151,10 +142,13 @@ class LearnerProfilePageTest(WebAppTest): Then I should see the profile visibility selector dropdown. Then I see some of the profile fields are shown. """ - self.visit_my_profile_page(self.MY_USER, privacy=self.PRIVACY_PRIVATE) + username, user_id = self.log_in_as_unique_user() + profile_page = self.visit_profile_page(username, privacy=self.PRIVACY_PRIVATE) - self.assertTrue(self.my_profile_page.privacy_field_visible) - self.assertEqual(self.my_profile_page.visible_fields, self.PRIVATE_PROFILE_FIELDS) + self.assertTrue(profile_page.privacy_field_visible) + self.assertEqual(profile_page.visible_fields, self.PRIVATE_PROFILE_FIELDS) + + self.verify_profile_page_view_event(user_id, visibility=self.PRIVACY_PRIVATE) def test_fields_on_my_public_profile(self): """ @@ -168,81 +162,43 @@ class LearnerProfilePageTest(WebAppTest): Then I see all the profile fields are shown. And `location`, `language` and `about me` fields are editable. """ - self.visit_my_profile_page(self.MY_USER, privacy=self.PRIVACY_PUBLIC) + username, user_id = self.log_in_as_unique_user() + profile_page = self.visit_profile_page(username, privacy=self.PRIVACY_PUBLIC) - self.assertTrue(self.my_profile_page.privacy_field_visible) - self.assertEqual(self.my_profile_page.visible_fields, self.PUBLIC_PROFILE_FIELDS) + self.assertTrue(profile_page.privacy_field_visible) + self.assertEqual(profile_page.visible_fields, self.PUBLIC_PROFILE_FIELDS) - self.assertEqual(self.my_profile_page.editable_fields, self.PUBLIC_PROFILE_EDITABLE_FIELDS) + self.assertEqual(profile_page.editable_fields, self.PUBLIC_PROFILE_EDITABLE_FIELDS) - def test_fields_on_others_private_profile(self): - """ - Scenario: Verify that desired fields are shown when looking at her own private profile. + self.verify_profile_page_view_event(user_id, visibility=self.PRIVACY_PUBLIC) - Given that I am a registered user. - And I visit others private profile page. - Then I shouldn't see the profile visibility selector dropdown. - Then I see some of the profile fields are shown. - """ - self.visit_other_profile_page(self.OTHER_USER, privacy=self.PRIVACY_PRIVATE) - self.visit_other_profile_page(self.MY_USER) - - self.assertFalse(self.other_profile_page.privacy_field_visible) - self.assertEqual(self.other_profile_page.visible_fields, self.PRIVATE_PROFILE_FIELDS) - - def test_fields_on_others_public_profile(self): - """ - Scenario: Verify that desired fields are shown when looking at her own public profile. - - Given that I am a registered user. - And I visit others public profile page. - Then I shouldn't see the profile visibility selector dropdown. - Then all the profile fields are shown. - Then I shouldn't see the profile visibility selector dropdown. - Also `location`, `language` and `about me` fields are not editable. - """ - self.visit_other_profile_page(self.OTHER_USER, privacy=self.PRIVACY_PUBLIC) - self.visit_other_profile_page(self.MY_USER) - - self.other_profile_page.wait_for_public_fields() - self.assertFalse(self.other_profile_page.privacy_field_visible) - - fields_to_check = self.PUBLIC_PROFILE_FIELDS - self.assertEqual(self.other_profile_page.visible_fields, fields_to_check) - - self.assertEqual(self.my_profile_page.editable_fields, []) - - def _test_dropdown_field(self, field_id, new_value, displayed_value, mode): + def _test_dropdown_field(self, profile_page, field_id, new_value, displayed_value, mode): """ Test behaviour of a dropdown field. """ - self.visit_my_profile_page(self.MY_USER, privacy=self.PRIVACY_PUBLIC) - - self.my_profile_page.value_for_dropdown_field(field_id, new_value) - self.assertEqual(self.my_profile_page.get_non_editable_mode_value(field_id), displayed_value) - self.assertTrue(self.my_profile_page.mode_for_field(field_id), mode) + profile_page.value_for_dropdown_field(field_id, new_value) + self.assertEqual(profile_page.get_non_editable_mode_value(field_id), displayed_value) + self.assertTrue(profile_page.mode_for_field(field_id), mode) self.browser.refresh() - self.my_profile_page.wait_for_page() + profile_page.wait_for_page() - self.assertEqual(self.my_profile_page.get_non_editable_mode_value(field_id), displayed_value) - self.assertTrue(self.my_profile_page.mode_for_field(field_id), mode) + self.assertEqual(profile_page.get_non_editable_mode_value(field_id), displayed_value) + self.assertTrue(profile_page.mode_for_field(field_id), mode) - def _test_textarea_field(self, field_id, new_value, displayed_value, mode): + def _test_textarea_field(self, profile_page, field_id, new_value, displayed_value, mode): """ Test behaviour of a textarea field. """ - self.visit_my_profile_page(self.MY_USER, privacy=self.PRIVACY_PUBLIC, ) - - self.my_profile_page.value_for_textarea_field(field_id, new_value) - self.assertEqual(self.my_profile_page.get_non_editable_mode_value(field_id), displayed_value) - self.assertTrue(self.my_profile_page.mode_for_field(field_id), mode) + profile_page.value_for_textarea_field(field_id, new_value) + self.assertEqual(profile_page.get_non_editable_mode_value(field_id), displayed_value) + self.assertTrue(profile_page.mode_for_field(field_id), mode) self.browser.refresh() - self.my_profile_page.wait_for_page() + profile_page.wait_for_page() - self.assertEqual(self.my_profile_page.get_non_editable_mode_value(field_id), displayed_value) - self.assertTrue(self.my_profile_page.mode_for_field(field_id), mode) + self.assertEqual(profile_page.get_non_editable_mode_value(field_id), displayed_value) + self.assertTrue(profile_page.mode_for_field(field_id), mode) def test_country_field(self): """ @@ -259,12 +215,14 @@ class LearnerProfilePageTest(WebAppTest): Then `country` field mode should be `edit` And `country` field icon should be visible. """ - self._test_dropdown_field('country', 'Pakistan', 'Pakistan', 'display') + username, user_id = self.log_in_as_unique_user() + profile_page = self.visit_profile_page(username, privacy=self.PRIVACY_PUBLIC) + self._test_dropdown_field(profile_page, 'country', 'Pakistan', 'Pakistan', 'display') - self.my_profile_page.make_field_editable('country') - self.assertTrue(self.my_profile_page.mode_for_field('country'), 'edit') + profile_page.make_field_editable('country') + self.assertTrue(profile_page.mode_for_field('country'), 'edit') - self.assertTrue(self.my_profile_page.field_icon_present('country')) + self.assertTrue(profile_page.field_icon_present('country')) def test_language_field(self): """ @@ -285,13 +243,15 @@ class LearnerProfilePageTest(WebAppTest): Then `language` field mode should be `edit` And `language` field icon should be visible. """ - self._test_dropdown_field('language_proficiencies', 'Urdu', 'Urdu', 'display') - self._test_dropdown_field('language_proficiencies', '', 'Add language', 'placeholder') + username, user_id = self.log_in_as_unique_user() + profile_page = self.visit_profile_page(username, privacy=self.PRIVACY_PUBLIC) + self._test_dropdown_field(profile_page, 'language_proficiencies', 'Urdu', 'Urdu', 'display') + self._test_dropdown_field(profile_page, 'language_proficiencies', '', 'Add language', 'placeholder') - self.my_profile_page.make_field_editable('language_proficiencies') - self.assertTrue(self.my_profile_page.mode_for_field('language_proficiencies'), 'edit') + profile_page.make_field_editable('language_proficiencies') + self.assertTrue(profile_page.mode_for_field('language_proficiencies'), 'edit') - self.assertTrue(self.my_profile_page.field_icon_present('language_proficiencies')) + self.assertTrue(profile_page.field_icon_present('language_proficiencies')) def test_about_me_field(self): """ @@ -320,11 +280,13 @@ class LearnerProfilePageTest(WebAppTest): "why you're taking courses on edX, or what you hope to learn." ) - self._test_textarea_field('bio', 'Eat Sleep Code', 'Eat Sleep Code', 'display') - self._test_textarea_field('bio', '', placeholder_value, 'placeholder') + username, user_id = self.log_in_as_unique_user() + profile_page = self.visit_profile_page(username, privacy=self.PRIVACY_PUBLIC) + self._test_textarea_field(profile_page, 'bio', 'Eat Sleep Code', 'Eat Sleep Code', 'display') + self._test_textarea_field(profile_page, 'bio', '', placeholder_value, 'placeholder') - self.my_profile_page.make_field_editable('bio') - self.assertTrue(self.my_profile_page.mode_for_field('bio'), 'edit') + profile_page.make_field_editable('bio') + self.assertTrue(profile_page.mode_for_field('bio'), 'edit') def test_birth_year_not_set(self): """ @@ -332,11 +294,13 @@ class LearnerProfilePageTest(WebAppTest): Given that I am a registered user. And birth year is not set for the user. - And i visit my profile page. - Then i should see message `Your profile is disabled because you haven't filled in your Year of Birth.` + And I visit my profile page. + Then I should see a message that the profile is private until the year of birth is set. """ + username, user_id = self.log_in_as_unique_user() message = "You must specify your birth year before you can share your full profile." - self.verify_profile_forced_private_message('', message=message) + self.verify_profile_forced_private_message(username, birth_year=None, message=message) + self.verify_profile_page_view_event(user_id, visibility=self.PRIVACY_PRIVATE) def test_user_is_under_age(self): """ @@ -344,7 +308,72 @@ class LearnerProfilePageTest(WebAppTest): Given that I am a registered user. And birth year is set so that age is less than 13. - And i visit my profile page. - Then i should see message `You must be over 13 to share a full profile.` + And I visit my profile page. + Then I should see a message that the profile is private as I am under thirteen. """ - self.verify_profile_forced_private_message('2010', message='You must be over 13 to share a full profile.') + username, user_id = self.log_in_as_unique_user() + under_age_birth_year = datetime.now().year - 10 + self.verify_profile_forced_private_message( + username, + birth_year=under_age_birth_year, + message='You must be over 13 to share a full profile.' + ) + self.verify_profile_page_view_event(user_id, visibility=self.PRIVACY_PRIVATE) + + +class DifferentUserLearnerProfilePageTest(LearnerProfileTestMixin, WebAppTest): + """ + Tests that verify viewing the profile page of a different user. + """ + def test_different_user_private_profile(self): + """ + Scenario: Verify that desired fields are shown when looking at a different user's private profile. + + Given that I am a registered user. + And I visit a different user's private profile page. + Then I shouldn't see the profile visibility selector dropdown. + Then I see some of the profile fields are shown. + """ + different_username, different_user_id = self._initialize_different_user(privacy=self.PRIVACY_PRIVATE) + self.log_in_as_unique_user() + profile_page = self.visit_profile_page(different_username) + + self.assertFalse(profile_page.privacy_field_visible) + self.assertEqual(profile_page.visible_fields, self.PRIVATE_PROFILE_FIELDS) + self.verify_profile_page_view_event(different_user_id, visibility=self.PRIVACY_PRIVATE) + + def test_different_user_public_profile(self): + """ + Scenario: Verify that desired fields are shown when looking at a different user's public profile. + + Given that I am a registered user. + And I visit a different user's public profile page. + Then I shouldn't see the profile visibility selector dropdown. + Then all the profile fields are shown. + Then I shouldn't see the profile visibility selector dropdown. + Also `location`, `language` and `about me` fields are not editable. + """ + different_username, different_user_id = self._initialize_different_user(privacy=self.PRIVACY_PUBLIC) + self.log_in_as_unique_user() + profile_page = self.visit_profile_page(different_username) + profile_page.wait_for_public_fields() + self.assertFalse(profile_page.privacy_field_visible) + self.assertEqual(profile_page.visible_fields, self.PUBLIC_PROFILE_FIELDS) + self.assertEqual(profile_page.editable_fields, []) + self.verify_profile_page_view_event(different_user_id, visibility=self.PRIVACY_PUBLIC) + + def _initialize_different_user(self, privacy=None): + """ + Initialize the profile page for a different test user + """ + username, user_id = self.log_in_as_unique_user() + + # Set the privacy for the new user + if privacy is None: + privacy = self.PRIVACY_PUBLIC + self.visit_profile_page(username, privacy=privacy) + + # Log the user out + LogoutPage(self.browser).visit() + + return username, user_id diff --git a/lms/djangoapps/student_profile/test/test_views.py b/lms/djangoapps/student_profile/test/test_views.py index e09e2c0501..12d57026bb 100644 --- a/lms/djangoapps/student_profile/test/test_views.py +++ b/lms/djangoapps/student_profile/test/test_views.py @@ -67,3 +67,11 @@ class LearnerProfileViewTest(UrlResetMixin, TestCase): for attribute in self.CONTEXT_DATA: self.assertIn(attribute, response.content) + + def test_undefined_profile_page(self): + """ + Verify that a 404 is returned for a non-existent profile page. + """ + profile_path = reverse('learner_profile', kwargs={'username': "no_such_user"}) + response = self.client.get(path=profile_path) + self.assertEqual(404, response.status_code) diff --git a/lms/djangoapps/student_profile/views.py b/lms/djangoapps/student_profile/views.py index cd476583b9..e0fa845dca 100644 --- a/lms/djangoapps/student_profile/views.py +++ b/lms/djangoapps/student_profile/views.py @@ -1,20 +1,22 @@ """ Views for a student's profile information. """ from django.conf import settings +from django.core.exceptions import ObjectDoesNotExist from django_countries import countries from django.core.urlresolvers import reverse from django.contrib.auth.decorators import login_required +from django.http import HttpResponse from django.views.decorators.http import require_http_methods from edxmako.shortcuts import render_to_response +from student.models import User @login_required @require_http_methods(['GET']) def learner_profile(request, username): - """ - Render the students profile page. + """Render the profile page for the specified username. Args: request (HttpRequest) @@ -23,20 +25,23 @@ def learner_profile(request, username): Returns: HttpResponse: 200 if the page was sent successfully HttpResponse: 302 if not logged in (redirect to login page) + HttpResponse: 404 if the specified username does not exist HttpResponse: 405 if using an unsupported HTTP method Example usage: GET /account/profile """ - return render_to_response( - 'student_profile/learner_profile.html', - learner_profile_context(request.user.username, username, request.user.is_staff) - ) + try: + return render_to_response( + 'student_profile/learner_profile.html', + learner_profile_context(request.user.username, username, request.user.is_staff) + ) + except ObjectDoesNotExist: + return HttpResponse(status=404) def learner_profile_context(logged_in_username, profile_username, user_is_staff): - """ - Context for the learner profile page. + """Context for the learner profile page. Args: logged_in_username (str): Username of user logged In user. @@ -45,7 +50,11 @@ def learner_profile_context(logged_in_username, profile_username, user_is_staff) Returns: dict + + Raises: + ObjectDoesNotExist: the specified profile_username does not exist. """ + profile_user = User.objects.get(username=profile_username) language_options = [language for language in settings.ALL_LANGUAGES] country_options = [ @@ -57,6 +66,7 @@ def learner_profile_context(logged_in_username, profile_username, user_is_staff) context = { 'data': { + 'profile_user_id': profile_user.id, 'default_public_account_fields': settings.ACCOUNT_VISIBILITY_CONFIGURATION['public_fields'], 'accounts_api_url': reverse("accounts_api", kwargs={'username': profile_username}), 'preferences_api_url': reverse('preferences_api', kwargs={'username': profile_username}), diff --git a/lms/envs/bok_choy.auth.json b/lms/envs/bok_choy.auth.json index 4820812192..cd47547ed2 100644 --- a/lms/envs/bok_choy.auth.json +++ b/lms/envs/bok_choy.auth.json @@ -49,6 +49,15 @@ ], "port": 27017 }, + "TRACKING_BACKENDS": { + "mongo": { + "ENGINE": "track.backends.mongodb.MongoBackend", + "OPTIONS": { + "database": "test", + "collection": "events" + } + } + }, "EVENT_TRACKING_BACKENDS": { "mongo": { "ENGINE": "eventtracking.backends.mongodb.MongoBackend", diff --git a/lms/static/js/spec/student_profile/learner_profile_view_spec.js b/lms/static/js/spec/student_profile/learner_profile_view_spec.js index e7f9c7b0fb..36d2f44569 100644 --- a/lms/static/js/spec/student_profile/learner_profile_view_spec.js +++ b/lms/static/js/spec/student_profile/learner_profile_view_spec.js @@ -93,8 +93,8 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j return new LearnerProfileView( { el: $('.wrapper-profile'), - own_profile: ownProfile, - has_preferences_access: true, + ownProfile: ownProfile, + hasPreferencesAccess: true, accountSettingsModel: accountSettingsModel, preferencesModel: accountPreferencesModel, accountPrivacyFieldView: accountPrivacyFieldView, diff --git a/lms/static/js/student_account/views/account_settings_factory.js b/lms/static/js/student_account/views/account_settings_factory.js index ebe73ec316..89d5f2c304 100644 --- a/lms/static/js/student_account/views/account_settings_factory.js +++ b/lms/static/js/student_account/views/account_settings_factory.js @@ -1,16 +1,16 @@ ;(function (define, undefined) { 'use strict'; define([ - 'gettext', 'jquery', 'underscore', 'backbone', + 'gettext', 'jquery', 'underscore', 'backbone', 'logger', 'js/views/fields', 'js/student_account/models/user_account_model', 'js/student_account/models/user_preferences_model', 'js/student_account/views/account_settings_fields', 'js/student_account/views/account_settings_view' - ], function (gettext, $, _, Backbone, FieldViews, UserAccountModel, UserPreferencesModel, + ], function (gettext, $, _, Backbone, Logger, FieldViews, UserAccountModel, UserPreferencesModel, AccountSettingsFieldViews, AccountSettingsView) { - return function (fieldsData, authData, userAccountsApiUrl, userPreferencesApiUrl) { + return function (fieldsData, authData, userAccountsApiUrl, userPreferencesApiUrl, accountUserId) { var accountSettingsElement = $('.wrapper-account-settings'); @@ -29,7 +29,7 @@ model: userAccountModel, title: gettext('Username'), valueAttribute: 'username', - helpMessage: 'The name that identifies you on the edX site. You cannot change your username.' + helpMessage: gettext('The name that identifies you on the edX site. You cannot change your username.') }) }, { @@ -55,7 +55,7 @@ valueAttribute: 'password', emailAttribute: 'email', linkTitle: gettext('Reset Password'), - linkHref: fieldsData['password']['url'], + linkHref: fieldsData.password.url, helpMessage: gettext('When you click "Reset Password", a message will be sent to your email address. Click the link in the message to reset your password.') }) }, @@ -67,7 +67,7 @@ required: true, refreshPageOnSave: true, helpMessage: gettext('The language used for the edX site. The site is currently available in a limited number of languages.'), - options: fieldsData['language']['options'] + options: fieldsData.language.options }) } ] @@ -80,7 +80,7 @@ model: userAccountModel, title: gettext('Education Completed'), valueAttribute: 'level_of_education', - options: fieldsData['level_of_education']['options'] + options: fieldsData.level_of_education.options }) }, { @@ -88,7 +88,7 @@ model: userAccountModel, title: gettext('Gender'), valueAttribute: 'gender', - options: fieldsData['gender']['options'] + options: fieldsData.gender.options }) }, { @@ -96,7 +96,7 @@ model: userAccountModel, title: gettext('Year of Birth'), valueAttribute: 'year_of_birth', - options: fieldsData['year_of_birth']['options'] + options: fieldsData.year_of_birth.options }) }, { @@ -104,7 +104,7 @@ model: userAccountModel, title: gettext('Country or Region'), valueAttribute: 'country', - options: fieldsData['country']['options'] + options: fieldsData.country.options }) }, { @@ -112,7 +112,7 @@ model: userAccountModel, title: gettext('Preferred Language'), valueAttribute: 'language_proficiencies', - options: fieldsData['preferred_language']['options'] + options: fieldsData.preferred_language.options }) } ] @@ -125,20 +125,22 @@ fields: _.map(authData.providers, function(provider) { return { 'view': new AccountSettingsFieldViews.AuthFieldView({ - title: provider['name'], - valueAttribute: 'auth-' + provider['name'].toLowerCase(), + title: provider.name, + valueAttribute: 'auth-' + provider.name.toLowerCase(), helpMessage: '', - connected: provider['connected'], - connectUrl: provider['connect_url'], - disconnectUrl: provider['disconnect_url'] + connected: provider.connected, + connectUrl: provider.connect_url, + disconnectUrl: provider.disconnect_url }) - } + }; }) }; sectionsData.push(accountsSectionData); } var accountSettingsView = new AccountSettingsView({ + model: userAccountModel, + accountUserId: accountUserId, el: accountSettingsElement, sectionsData: sectionsData }); @@ -149,14 +151,25 @@ accountSettingsView.showLoadingError(); }; + var showAccountFields = function () { + // Record that the account settings page was viewed. + Logger.log('edx.user.settings.viewed', { + page: "account", + visibility: null, + user_id: accountUserId + }); + + // Render the fields + accountSettingsView.renderFields(); + }; + userAccountModel.fetch({ success: function () { + // Fetch the user preferences model userPreferencesModel.fetch({ - success: function () { - accountSettingsView.renderFields(); - }, + success: showAccountFields, error: showLoadingError - }) + }); }, error: showLoadingError }); @@ -167,5 +180,5 @@ accountSettingsView: accountSettingsView }; }; - }) + }); }).call(this, define || RequireJS.define); diff --git a/lms/static/js/student_account/views/account_settings_view.js b/lms/static/js/student_account/views/account_settings_view.js index 747d0d7a3f..511baaf68a 100644 --- a/lms/static/js/student_account/views/account_settings_view.js +++ b/lms/static/js/student_account/views/account_settings_view.js @@ -23,7 +23,7 @@ var view = this; _.each(this.$('.account-settings-section-body'), function (sectionEl, index) { - _.each(view.options.sectionsData[index].fields, function (field, index) { + _.each(view.options.sectionsData[index].fields, function (field) { $(sectionEl).append(field.view.render().el); }); }); @@ -37,5 +37,5 @@ }); return AccountSettingsView; - }) + }); }).call(this, define || RequireJS.define); diff --git a/lms/static/js/student_profile/views/learner_profile_factory.js b/lms/static/js/student_profile/views/learner_profile_factory.js index f524c6d4ff..72663fed71 100644 --- a/lms/static/js/student_profile/views/learner_profile_factory.js +++ b/lms/static/js/student_profile/views/learner_profile_factory.js @@ -1,16 +1,14 @@ ;(function (define, undefined) { 'use strict'; define([ - 'gettext', 'jquery', 'underscore', 'backbone', + 'gettext', 'jquery', 'underscore', 'backbone', 'logger', 'js/student_account/models/user_account_model', 'js/student_account/models/user_preferences_model', 'js/views/fields', 'js/student_profile/views/learner_profile_fields', 'js/student_profile/views/learner_profile_view', 'js/student_account/views/account_settings_fields' - - - ], function (gettext, $, _, Backbone, AccountSettingsModel, AccountPreferencesModel, FieldsView, + ], function (gettext, $, _, Backbone, Logger, AccountSettingsModel, AccountPreferencesModel, FieldsView, LearnerProfileFieldsView, LearnerProfileView, AccountSettingsFieldViews) { return function (options) { @@ -18,14 +16,14 @@ var learnerProfileElement = $('.wrapper-profile'); var accountPreferencesModel = new AccountPreferencesModel(); - accountPreferencesModel.url = options['preferences_api_url']; + accountPreferencesModel.url = options.preferences_api_url; var accountSettingsModel = new AccountSettingsModel({ - 'default_public_account_fields': options['default_public_account_fields'] + 'default_public_account_fields': options.default_public_account_fields }); - accountSettingsModel.url = options['accounts_api_url']; + accountSettingsModel.url = options.accounts_api_url; - var editable = options['own_profile'] ? 'toggle' : 'never'; + var editable = options.own_profile ? 'toggle' : 'never'; var accountPrivacyFieldView = new LearnerProfileFieldsView.AccountPrivacyFieldView({ model: accountPreferencesModel, @@ -39,7 +37,7 @@ ['all_users', gettext('Full Profile')] ], helpMessage: '', - accountSettingsPageUrl: options['account_settings_page_url'] + accountSettingsPageUrl: options.account_settings_page_url }); var usernameFieldView = new FieldsView.ReadonlyFieldView({ @@ -58,7 +56,7 @@ iconName: 'fa-map-marker', placeholderValue: gettext('Add country'), valueAttribute: "country", - options: options['country_options'], + options: options.country_options, helpMessage: '' }), new AccountSettingsFieldViews.LanguageProficienciesFieldView({ @@ -69,7 +67,7 @@ iconName: 'fa-comment', placeholderValue: gettext('Add language'), valueAttribute: "language_proficiencies", - options: options['language_options'], + options: options.language_options, helpMessage: '' }) ]; @@ -88,8 +86,8 @@ var learnerProfileView = new LearnerProfileView({ el: learnerProfileElement, - own_profile: options['own_profile'], - has_preferences_access: options['has_preferences_access'], + ownProfile: options.own_profile, + has_preferences_access: options.has_preferences_access, accountSettingsModel: accountSettingsModel, preferencesModel: accountPreferencesModel, accountPrivacyFieldView: accountPrivacyFieldView, @@ -102,20 +100,37 @@ learnerProfileView.showLoadingError(); }; - var renderLearnerProfileView = function() { + var getProfileVisibility = function() { + if (options.has_preferences_access) { + return accountPreferencesModel.get('account_privacy'); + } else { + return accountSettingsModel.get('profile_is_public') ? 'all_users' : 'private'; + } + }; + + var showLearnerProfileView = function() { + // Record that the profile page was viewed + Logger.log('edx.user.settings.viewed', { + page: "profile", + visibility: getProfileVisibility(), + user_id: options.profile_user_id + }); + + // Render the view for the first time learnerProfileView.render(); }; accountSettingsModel.fetch({ success: function () { - if (options['has_preferences_access']) { + // Fetch the preferences model if the user has access + if (options.has_preferences_access) { accountPreferencesModel.fetch({ - success: renderLearnerProfileView, + success: showLearnerProfileView, error: showLoadingError }); } else { - renderLearnerProfileView(); + showLearnerProfileView(); } }, error: showLoadingError @@ -127,5 +142,5 @@ learnerProfileView: learnerProfileView }; }; - }) + }); }).call(this, define || RequireJS.define); diff --git a/lms/static/js/student_profile/views/learner_profile_view.js b/lms/static/js/student_profile/views/learner_profile_view.js index 2c93f25f56..e5215d496a 100644 --- a/lms/static/js/student_profile/views/learner_profile_view.js +++ b/lms/static/js/student_profile/views/learner_profile_view.js @@ -14,7 +14,7 @@ showFullProfile: function () { var isAboveMinimumAge = this.options.accountSettingsModel.isAboveMinimumAge(); - if (this.options.own_profile) { + if (this.options.ownProfile) { return isAboveMinimumAge && this.options.preferencesModel.get('account_privacy') === 'all_users'; } else { return this.options.accountSettingsModel.get('profile_is_public'); @@ -25,7 +25,7 @@ this.$el.html(this.template({ username: this.options.accountSettingsModel.get('username'), profilePhoto: 'http://www.teachthought.com/wp-content/uploads/2012/07/edX-120x120.jpg', - ownProfile: this.options.own_profile, + ownProfile: this.options.ownProfile, showFullProfile: this.showFullProfile() })); this.renderFields(); @@ -35,11 +35,12 @@ renderFields: function() { var view = this; - if (this.options.own_profile) { - var fieldView = this.options.accountPrivacyFieldView; - fieldView.profileIsPrivate = (!this.options.accountSettingsModel.get('year_of_birth')); - fieldView.requiresParentalConsent = (this.options.accountSettingsModel.get('requires_parental_consent')); - fieldView.isAboveMinimumAge = this.options.accountSettingsModel.isAboveMinimumAge(); + if (this.options.ownProfile) { + var fieldView = this.options.accountPrivacyFieldView, + settings = this.options.accountSettingsModel; + fieldView.profileIsPrivate = !settings.get('year_of_birth'); + fieldView.requiresParentalConsent = settings.get('requires_parental_consent'); + fieldView.isAboveMinimumAge = settings.isAboveMinimumAge(); fieldView.undelegateEvents(); this.$('.wrapper-profile-field-account-privacy').append(fieldView.render().el); fieldView.delegateEvents(); @@ -48,13 +49,13 @@ this.$('.profile-section-one-fields').append(this.options.usernameFieldView.render().el); if (this.showFullProfile()) { - _.each(this.options.sectionOneFieldViews, function (fieldView, index) { + _.each(this.options.sectionOneFieldViews, function (fieldView) { fieldView.undelegateEvents(); view.$('.profile-section-one-fields').append(fieldView.render().el); fieldView.delegateEvents(); }); - _.each(this.options.sectionTwoFieldViews, function (fieldView, index) { + _.each(this.options.sectionTwoFieldViews, function (fieldView) { fieldView.undelegateEvents(); view.$('.profile-section-two-fields').append(fieldView.render().el); fieldView.delegateEvents(); @@ -69,5 +70,5 @@ }); return LearnerProfileView; - }) + }); }).call(this, define || RequireJS.define); diff --git a/lms/templates/student_account/account_settings.html b/lms/templates/student_account/account_settings.html index 9eb5f207de..052d3580f4 100644 --- a/lms/templates/student_account/account_settings.html +++ b/lms/templates/student_account/account_settings.html @@ -41,7 +41,7 @@ var authData = ${ json.dumps(auth) }; setupAccountSettingsSection( - fieldsData, authData, '${user_accounts_api_url}', '${user_preferences_api_url}' + fieldsData, authData, '${user_accounts_api_url}', '${user_preferences_api_url}', ${user.id} ); }); }).call(this, require || RequireJS.require);