Пример #1
0
    def setUp(self):
        super(CourseOverviewAccessTestCase, self).setUp()

        today = datetime.datetime.now(pytz.UTC)
        last_week = today - datetime.timedelta(days=7)
        next_week = today + datetime.timedelta(days=7)

        self.course_default = CourseFactory.create()
        self.course_started = CourseFactory.create(start=last_week)
        self.course_not_started = CourseFactory.create(start=next_week,
                                                       days_early_for_beta=10)
        self.course_staff_only = CourseFactory.create(
            visible_to_staff_only=True)
        self.course_mobile_available = CourseFactory.create(
            mobile_available=True)
        self.course_with_pre_requisite = CourseFactory.create(
            pre_requisite_courses=[str(self.course_started.id)])
        self.course_with_pre_requisites = CourseFactory.create(
            pre_requisite_courses=[
                str(self.course_started.id),
                str(self.course_not_started.id)
            ])

        self.user_normal = UserFactory.create()
        self.user_beta_tester = BetaTesterFactory.create(
            course_key=self.course_not_started.id)
        self.user_completed_pre_requisite = UserFactory.create()
        fulfill_course_milestone(self.course_started.id,
                                 self.user_completed_pre_requisite)
        self.user_staff = UserFactory.create(is_staff=True)
        self.user_anonymous = AnonymousUserFactory.create()
Пример #2
0
    def test_instructor_tab(self):
        """
        Verify that the instructor tab appears for staff only.
        """
        def has_instructor_tab(user, course):
            """Returns true if the "Instructor" tab is shown."""
            tabs = get_course_tab_list(user, course)
            return len([tab for tab in tabs if tab.name == 'Instructor']) == 1

        assert has_instructor_tab(self.instructor, self.course)

        staff = StaffFactory(course_key=self.course.id)
        assert has_instructor_tab(staff, self.course)

        student = UserFactory.create()
        assert not has_instructor_tab(student, self.course)

        researcher = UserFactory.create()
        CourseAccessRoleFactory(
            course_id=self.course.id,
            user=researcher,
            role='data_researcher',
            org=self.course.id.org
        )
        assert has_instructor_tab(researcher, self.course)

        org_researcher = UserFactory.create()
        CourseAccessRoleFactory(
            course_id=None,
            user=org_researcher,
            role='data_researcher',
            org=self.course.id.org
        )
        assert has_instructor_tab(org_researcher, self.course)
Пример #3
0
    def setUp(self):
        freezer = freeze_time(self.now)
        freezer.start()
        self.addCleanup(freezer.stop)

        super(CourseProgressApiViewTest, self).setUp()

        self.student = UserFactory(password=USER_PASSWORD)
        self.instructor_user = InstructorFactory(course_key=self.course.id,
                                                 password=USER_PASSWORD)
        self.staff_user = UserFactory(password=USER_PASSWORD, is_staff=True)

        self.enrollment = CourseEnrollmentFactory.create(
            user=self.student, course_id=self.course.id)

        self.namespaced_url = 'navoica_api:v1:progress:detail'

        # create a configuration for django-oauth-toolkit (DOT)
        dot_app_user = UserFactory.create(password=USER_PASSWORD)
        dot_app = dot_models.Application.objects.create(
            name='test app',
            user=dot_app_user,
            client_type='confidential',
            authorization_grant_type='authorization-code',
            redirect_uris='http://localhost:8079/complete/edxorg/')
        self.dot_access_token = dot_models.AccessToken.objects.create(
            user=self.student,
            application=dot_app,
            expires=datetime.utcnow() + timedelta(weeks=1),
            scope='read write',
            token='16MGyP3OaQYHmpT1lK7Q6MMNAZsjwF')
Пример #4
0
 def setUp(self):
     super(UserRoleTestCase, self).setUp()
     self.course_key = CourseLocator('edX', 'toy', '2012_Fall')
     self.anonymous_user = AnonymousUserFactory()
     self.student = UserFactory()
     self.global_staff = UserFactory(is_staff=True)
     self.course_staff = StaffFactory(course_key=self.course_key)
     self.course_instructor = InstructorFactory(course_key=self.course_key)
Пример #5
0
    def setUp(self):
        super().setUp()

        self.initialize_course()
        self.student = UserFactory.create(username="******",
                                          email="*****@*****.**")
        self.instructor = UserFactory.create(username="******",
                                             email="*****@*****.**")
Пример #6
0
 def setUp(self):
     super(RolesTestCase, self).setUp()  # lint-amnesty, pylint: disable=super-with-arguments
     self.course_key = CourseKey.from_string('edX/toy/2012_Fall')
     self.course_loc = self.course_key.make_usage_key('course', '2012_Fall')
     self.anonymous_user = AnonymousUserFactory()
     self.student = UserFactory()
     self.global_staff = UserFactory(is_staff=True)
     self.course_staff = StaffFactory(course_key=self.course_key)
     self.course_instructor = InstructorFactory(course_key=self.course_key)
Пример #7
0
 def setUp(self):
     super().setUp()
     self.course_key = CourseKey.from_string('edX/toy/2012_Fall')
     self.course_loc = self.course_key.make_usage_key('course', '2012_Fall')
     self.anonymous_user = AnonymousUserFactory()
     self.student = UserFactory()
     self.global_staff = UserFactory(is_staff=True)
     self.course_staff = StaffFactory(course_key=self.course_key)
     self.course_instructor = InstructorFactory(course_key=self.course_key)
Пример #8
0
 def setUp(self):
     super(AccessTestCase, self).setUp()
     self.course = CourseFactory.create(org='edX', course='toy', run='test_run')
     self.anonymous_user = AnonymousUserFactory()
     self.beta_user = BetaTesterFactory(course_key=self.course.id)
     self.student = UserFactory()
     self.global_staff = UserFactory(is_staff=True)
     self.course_staff = StaffFactory(course_key=self.course.id)
     self.course_instructor = InstructorFactory(course_key=self.course.id)
     self.staff = GlobalStaffFactory()
Пример #9
0
    def setUp(self):
        freezer = freeze_time(self.now)
        freezer.start()
        self.addCleanup(freezer.stop)

        super(CertificatesListViewTest, self).setUp()

        self.student = UserFactory(password=USER_PASSWORD)
        self.instructor = InstructorFactory(course_key=self.course.id,
                                            password=USER_PASSWORD)
        self.second_instructor = InstructorFactory(
            course_key=self.second_course.id, password=USER_PASSWORD)
        self.staff_user = UserFactory(password=USER_PASSWORD, is_staff=True)

        self.enrollment = CourseEnrollmentFactory.create(
            user=self.student, course_id=self.course.id)

        self.second_enrollment = CourseEnrollmentFactory.create(
            user=self.student, course_id=self.second_course.id)

        self.certificate = GeneratedCertificateFactory(
            user=self.student,
            course_id=self.course.id,
            download_url=self.DOWNLOAD_URL,
            status=CertificateStatuses.downloadable,
            created_date=self.CREATED_DATE,
            grade=0.98,
        )

        self.second_certificate = GeneratedCertificateFactory(
            user=self.student,
            course_id=self.second_course.id,
            download_url=self.DOWNLOAD_URL,
            status=CertificateStatuses.downloadable,
            created_date=self.CREATED_DATE,
            grade=0.95,
        )

        self.namespaced_url = 'navoica_api:v1:certificates:list'

        # create a configuration for django-oauth-toolkit (DOT)
        dot_app_user = UserFactory.create(password=USER_PASSWORD)
        dot_app = dot_models.Application.objects.create(
            name='test app',
            user=dot_app_user,
            client_type='confidential',
            authorization_grant_type='authorization-code',
            redirect_uris='http://localhost:8079/complete/edxorg/')
        self.dot_access_token = dot_models.AccessToken.objects.create(
            user=self.staff_user,
            application=dot_app,
            expires=datetime.utcnow() + timedelta(weeks=1),
            scope='read write',
            token='16MGyP3OaQYHmpT1lK7Q6MMNAZsjwF')
Пример #10
0
    def test_total_credit_cart_sales_amount(self):
        """
        Test to check the total amount for all the credit card purchases.
        """
        student = UserFactory.create()
        self.client.login(username=student.username, password="******")
        student_cart = Order.get_cart_for_user(student)
        item = self.add_course_to_user_cart(student_cart, self.course.id)
        resp = self.client.post(reverse('shoppingcart.views.update_user_cart'),
                                {
                                    'ItemId': item.id,
                                    'qty': 4
                                })
        self.assertEqual(resp.status_code, 200)
        student_cart.purchase()

        self.client.login(username=self.instructor.username, password="******")
        CourseFinanceAdminRole(self.course.id).add_users(self.instructor)
        single_purchase_total = PaidCourseRegistration.get_total_amount_of_purchased_item(
            self.course.id)
        bulk_purchase_total = CourseRegCodeItem.get_total_amount_of_purchased_item(
            self.course.id)
        total_amount = single_purchase_total + bulk_purchase_total
        response = self.client.get(self.url)
        self.assertContains(
            response, '{currency}{amount}'.format(currency='$',
                                                  amount=total_amount))
Пример #11
0
    def test_courseware_page_unfulfilled_prereqs(self):
        """
        Test courseware access when a course has pre-requisite course yet to be completed
        """
        pre_requisite_course = CourseFactory.create(
            org='edX',
            course='900',
            run='test_run',
        )

        pre_requisite_courses = [six.text_type(pre_requisite_course.id)]
        course = CourseFactory.create(
            org='edX',
            course='1000',
            run='test_run',
            pre_requisite_courses=pre_requisite_courses,
        )
        set_prerequisite_courses(course.id, pre_requisite_courses)

        test_password = '******'
        user = UserFactory.create()
        user.set_password(test_password)
        user.save()
        self.login(user.email, test_password)
        CourseEnrollmentFactory(user=user, course_id=course.id)

        url = reverse('courseware', args=[six.text_type(course.id)])
        response = self.client.get(url)
        self.assertRedirects(response, reverse('dashboard'))
        self.assertEqual(response.status_code, 302)

        fulfill_course_milestone(pre_requisite_course.id, user)
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
Пример #12
0
    def test__catalog_visibility(self):
        """
        Tests the catalog visibility tri-states
        """
        user = UserFactory.create()
        course_id = CourseLocator('edX', 'test', '2012_Fall')
        staff = StaffFactory.create(course_key=course_id)

        course = Mock(id=course_id,
                      catalog_visibility=CATALOG_VISIBILITY_CATALOG_AND_ABOUT)
        assert access._has_access_course(user, 'see_in_catalog', course)
        assert access._has_access_course(user, 'see_about_page', course)
        assert access._has_access_course(staff, 'see_in_catalog', course)
        assert access._has_access_course(staff, 'see_about_page', course)

        # Now set visibility to just about page
        course = Mock(id=CourseLocator('edX', 'test', '2012_Fall'),
                      catalog_visibility=CATALOG_VISIBILITY_ABOUT)
        assert not access._has_access_course(user, 'see_in_catalog', course)
        assert access._has_access_course(user, 'see_about_page', course)
        assert access._has_access_course(staff, 'see_in_catalog', course)
        assert access._has_access_course(staff, 'see_about_page', course)

        # Now set visibility to none, which means neither in catalog nor about pages
        course = Mock(id=CourseLocator('edX', 'test', '2012_Fall'),
                      catalog_visibility=CATALOG_VISIBILITY_NONE)
        assert not access._has_access_course(user, 'see_in_catalog', course)
        assert not access._has_access_course(user, 'see_about_page', course)
        assert access._has_access_course(staff, 'see_in_catalog', course)
        assert access._has_access_course(staff, 'see_about_page', course)
Пример #13
0
 def setUp(self):
     """
     Creating pre-requisites for the test cases.
     """
     super(TestUserStateService, self).setUp()
     self.user = UserFactory.create()
     self.course = CourseFactory.create()
     chapter = ItemFactory.create(
         category='chapter',
         parent=self.course,
         display_name='Test Chapter'
     )
     sequential = ItemFactory.create(
         category='sequential',
         parent=chapter,
         display_name='Test Sequential'
     )
     vertical = ItemFactory.create(
         category='vertical',
         parent=sequential,
         display_name='Test Vertical'
     )
     self.problem = ItemFactory.create(
         category='problem',
         parent=vertical,
         display_name='Test Problem'
     )
Пример #14
0
    def test_compose_calendar_sync_email(self, is_update):
        """
        Tests that attributes of the message are being filled correctly in compose_activation_email
        """
        user = UserFactory()
        course_overview = CourseOverviewFactory()
        course_name = course_overview.display_name
        if is_update:
            calendar_sync_subject = 'Updates for Your {course} Schedule'.format(course=course_name)
            calendar_sync_headline = 'Update Your Calendar'
            calendar_sync_body = ('Your assignment due dates for {course} were recently adjusted. Update your calendar'
                                  'with your new schedule to ensure that you stay on track!').format(course=course_name)
        else:
            calendar_sync_subject = 'Stay on Track'
            calendar_sync_headline = 'Mark Your Calendar'
            calendar_sync_body = (
                'Sticking to a schedule is the best way to ensure that you successfully complete your '
                'self-paced course. This schedule of assignment due dates for {course} will help you '
                'stay on track!'.format(course=course_name))

        msg = compose_calendar_sync_email(user, course_overview, is_update)

        self.assertEqual(msg.context['calendar_sync_subject'], calendar_sync_subject)
        self.assertEqual(msg.context['calendar_sync_headline'], calendar_sync_headline)
        self.assertEqual(msg.context['calendar_sync_body'], calendar_sync_body)
        self.assertEqual(msg.recipient.username, user.username)
        self.assertEqual(msg.recipient.email_address, user.email)
Пример #15
0
    def test_access_on_course_with_pre_requisites(self):
        """
        Test course access when a course has pre-requisite course yet to be completed
        """
        user = UserFactory.create()

        pre_requisite_course = CourseFactory.create(org='test_org',
                                                    number='788',
                                                    run='test_run')

        pre_requisite_courses = [six.text_type(pre_requisite_course.id)]
        course = CourseFactory.create(
            org='test_org',
            number='786',
            run='test_run',
            pre_requisite_courses=pre_requisite_courses)
        set_prerequisite_courses(course.id, pre_requisite_courses)

        # user should not be able to load course even if enrolled
        CourseEnrollmentFactory(user=user, course_id=course.id)
        response = access._has_access_course(user, 'load', course)
        self.assertFalse(response)
        self.assertIsInstance(response, access_response.MilestoneAccessError)
        # Staff can always access course
        staff = StaffFactory.create(course_key=course.id)
        self.assertTrue(access._has_access_course(staff, 'load', course))

        # User should be able access after completing required course
        fulfill_course_milestone(pre_requisite_course.id, user)
        self.assertTrue(access._has_access_course(user, 'load', course))
Пример #16
0
    def test_group_name_case_sensitive(self):
        uppercase_course_id = "ORG/COURSE/NAME"
        lowercase_course_id = uppercase_course_id.lower()
        uppercase_course_key = CourseKey.from_string(uppercase_course_id)
        lowercase_course_key = CourseKey.from_string(lowercase_course_id)

        role = "role"

        lowercase_user = UserFactory()
        CourseRole(role, lowercase_course_key).add_users(lowercase_user)
        uppercase_user = UserFactory()
        CourseRole(role, uppercase_course_key).add_users(uppercase_user)

        self.assertTrue(CourseRole(role, lowercase_course_key).has_user(lowercase_user))
        self.assertFalse(CourseRole(role, uppercase_course_key).has_user(lowercase_user))
        self.assertFalse(CourseRole(role, lowercase_course_key).has_user(uppercase_user))
        self.assertTrue(CourseRole(role, uppercase_course_key).has_user(uppercase_user))
Пример #17
0
    def setUp(self):
        """ Setup components used by each test."""
        super().setUp()
        self.student = UserFactory()

        registration = Registration()
        registration.register(self.student)

        self.msg = compose_activation_email(self.student, registration)
Пример #18
0
    def test__has_access_course_can_enroll(self):
        yesterday = datetime.datetime.now(
            pytz.utc) - datetime.timedelta(days=1)
        tomorrow = datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1)

        # Non-staff can enroll if authenticated and specifically allowed for that course
        # even outside the open enrollment period
        user = UserFactory.create()
        course = Mock(enrollment_start=tomorrow,
                      enrollment_end=tomorrow,
                      id=CourseLocator('edX', 'test', '2012_Fall'),
                      enrollment_domain='')
        CourseEnrollmentAllowedFactory(email=user.email, course_id=course.id)
        self.assertTrue(access._has_access_course(user, 'enroll', course))

        # Staff can always enroll even outside the open enrollment period
        user = StaffFactory.create(course_key=course.id)
        self.assertTrue(access._has_access_course(user, 'enroll', course))

        # Non-staff cannot enroll if it is between the start and end dates and invitation only
        # and not specifically allowed
        course = Mock(enrollment_start=yesterday,
                      enrollment_end=tomorrow,
                      id=CourseLocator('edX', 'test', '2012_Fall'),
                      enrollment_domain='',
                      invitation_only=True)
        user = UserFactory.create()
        self.assertFalse(access._has_access_course(user, 'enroll', course))

        # Non-staff can enroll if it is between the start and end dates and not invitation only
        course = Mock(enrollment_start=yesterday,
                      enrollment_end=tomorrow,
                      id=CourseLocator('edX', 'test', '2012_Fall'),
                      enrollment_domain='',
                      invitation_only=False)
        self.assertTrue(access._has_access_course(user, 'enroll', course))

        # Non-staff cannot enroll outside the open enrollment period if not specifically allowed
        course = Mock(enrollment_start=tomorrow,
                      enrollment_end=tomorrow,
                      id=CourseLocator('edX', 'test', '2012_Fall'),
                      enrollment_domain='',
                      invitation_only=False)
        self.assertFalse(access._has_access_course(user, 'enroll', course))
Пример #19
0
    def setUp(self):
        """ Setup components used by each test."""
        super(SendActivationEmailTestCase, self).setUp()
        self.student = UserFactory()

        registration = Registration()
        registration.register(self.student)

        self.msg = compose_activation_email("http://www.example.com",
                                            self.student, registration)
Пример #20
0
 def test_spoc_gradebook_pages(self):
     for i in range(2):
         username = "******" % i
         student = UserFactory.create(username=username)
         CourseEnrollmentFactory.create(user=student,
                                        course_id=self.course.id)
     url = reverse('spoc_gradebook', kwargs={'course_id': self.course.id})
     response = self.client.get(url)
     self.assertEqual(response.status_code, 200)
     # Max number of student per page is one.  Patched setting MAX_STUDENTS_PER_PAGE_GRADE_BOOK = 1
     self.assertEqual(len(response.mako_context['students']), 1)
    def test_show_enrollment_data_for_prof_ed(self):
        # Create both "professional" (meaning professional + verification)
        # and "no-id-professional" (meaning professional without verification)
        # These should be aggregated for display purposes.
        users = [UserFactory() for _ in range(2)]
        CourseEnrollment.enroll(users[0], self.course.id, mode="professional")
        CourseEnrollment.enroll(users[1], self.course.id, mode="no-id-professional")
        response = self.client.get(self.url)

        # Check that the number of professional enrollments is two
        self.assertContains(response, '<th scope="row">Professional</th><td>2</td>')
Пример #22
0
 def setUp(self):
     super(MobileAPITestCase, self).setUp()  # lint-amnesty, pylint: disable=super-with-arguments
     self.course = CourseFactory.create(
         mobile_available=True,
         static_asset_path="needed_for_split",
         end=datetime.datetime.now(pytz.UTC),
         certificate_available_date=datetime.datetime.now(pytz.UTC))
     self.user = UserFactory.create()
     self.password = '******'
     self.username = self.user.username
     self.api_version = API_V1
     IgnoreMobileAvailableFlagConfig(enabled=False).save()
Пример #23
0
    def setUp(self):
        """
        Add a user and a course
        """
        super().setUp()
        # create and log in a staff user.
        self.user = UserFactory(is_staff=True)
        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password='******')

        # create a course via the view handler to create course
        self.course_key = self.store.make_course_key('Org_1', 'Course_1', 'Run_1')
        self._create_course_with_given_location(self.course_key)
Пример #24
0
    def test_other_user(self):
        # login and enroll as the test user
        self.login_and_enroll()
        self.logout()

        # login and enroll as another user
        other = UserFactory.create()
        self.client.login(username=other.username, password='******')
        self.enroll()
        self.logout()

        # now login and call the API as the test user
        self.login()
        self.api_response(expected_response_code=403, username=other.username)
Пример #25
0
 def setUp(self):
     super(MobileAPITestCase, self).setUp()
     self.course = CourseFactory.create(
         mobile_available=True,
         static_asset_path="needed_for_split",
         # Custom change: course end date is set to a future date to fulfill custom added feature flag
         # ALLOW_STUDENT_STATE_UPDATES_ON_CLOSED_COURSE
         end=datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=1),
         certificate_available_date=datetime.datetime.now(pytz.UTC)
     )
     self.user = UserFactory.create()
     self.password = '******'
     self.username = self.user.username
     self.api_version = API_V1
     IgnoreMobileAvailableFlagConfig(enabled=False).save()
 def test_data_download_only(self):
     """
     Verify that only the data download tab is visible for data researchers.
     """
     user = UserFactory.create()
     CourseAccessRoleFactory(course_id=self.course.id,
                             user=user,
                             role='data_researcher',
                             org=self.course.id.org)
     self.client.login(username=user.username, password="******")
     response = self.client.get(self.url)
     matches = re.findall(
         rb'<li class="nav-item"><button type="button" class="btn-link .*" data-section=".*">.*',
         response.content)
     assert len(matches) == 1
Пример #27
0
    def test_instructor_tab(self):
        """
        Verify that the instructor tab appears for staff only.
        """
        def has_instructor_tab(user, course):
            """Returns true if the "Instructor" tab is shown."""
            tabs = get_course_tab_list(user, course)
            return len([tab for tab in tabs if tab.name == 'Instructor']) == 1

        self.assertTrue(has_instructor_tab(self.instructor, self.course))

        staff = StaffFactory(course_key=self.course.id)
        self.assertTrue(has_instructor_tab(staff, self.course))

        student = UserFactory.create()
        self.assertFalse(has_instructor_tab(student, self.course))
    def build_course(self):
        """
        Build up a course tree with an html control
        """
        self.global_staff = UserFactory(is_staff=True)

        self.course = CourseFactory.create(
            org='Elasticsearch',
            course='ES101',
            run='test_run',
            display_name='Elasticsearch test course',
        )
        self.section = ItemFactory.create(
            parent=self.course,
            category='chapter',
            display_name='Test Section',
        )
        self.subsection = ItemFactory.create(
            parent=self.section,
            category='sequential',
            display_name='Test Subsection',
        )
        self.vertical = ItemFactory.create(
            parent=self.subsection,
            category='vertical',
            display_name='Test Unit',
        )
        self.html = ItemFactory.create(
            parent=self.vertical,
            category='html',
            display_name='Test Html control',
        )
        self.ghost_subsection = ItemFactory.create(
            parent=self.section,
            category='sequential',
            display_name=None,
        )
        self.ghost_vertical = ItemFactory.create(
            parent=self.ghost_subsection,
            category='vertical',
            display_name=None,
        )
        self.ghost_html = ItemFactory.create(
            parent=self.ghost_vertical,
            category='html',
            display_name='Ghost Html control',
        )
Пример #29
0
 def test_data_download(self, access_role, can_access):
     """
     Verify that the Data Download tab only shows up for certain roles
     """
     download_section = '<li class="nav-item"><button type="button" class="btn-link data_download" '\
                        'data-section="data_download">Data Download</button></li>'
     user = UserFactory.create(is_staff=access_role == 'global_staff')
     CourseAccessRoleFactory(course_id=self.course.id,
                             user=user,
                             role=access_role,
                             org=self.course.id.org)
     self.client.login(username=user.username, password="******")
     response = self.client.get(self.url)
     if can_access:
         self.assertContains(response, download_section)
     else:
         self.assertNotContains(response, download_section)
Пример #30
0
    def test_access_student_progress_ccx(self):
        """
        Assert that only a coach can see progress of student.
        """
        ccx_locator = self.make_ccx()
        student = UserFactory()

        # Enroll user
        CourseEnrollment.enroll(student, ccx_locator)

        # Test for access of a coach
        resp = self.client.get(reverse('student_progress', args=[six.text_type(ccx_locator), student.id]))
        self.assertEqual(resp.status_code, 200)

        # Assert access of a student
        self.client.login(username=student.username, password='******')
        resp = self.client.get(reverse('student_progress', args=[six.text_type(ccx_locator), self.coach.id]))
        self.assertEqual(resp.status_code, 404)