Exemplo n.º 1
0
    def test_unpublished_sessions_for_entitlement_when_enrolled(self, mock_get_edx_api_data):
        """
        Test unpublished course runs are part of visible session entitlements when the user
        is enrolled.
        """
        catalog_course_run = CourseRunFactory.create(status=COURSE_UNPUBLISHED)
        catalog_course = CourseFactory(course_runs=[catalog_course_run])
        mock_get_edx_api_data.return_value = catalog_course
        course_key = CourseKey.from_string(catalog_course_run.get('key'))
        course_overview = CourseOverviewFactory.create(id=course_key, start=self.tomorrow)
        CourseModeFactory.create(
            mode_slug=CourseMode.VERIFIED,
            min_price=100,
            course_id=course_overview.id,
            expiration_datetime=now() - timedelta(days=1)
        )
        course_enrollment = CourseEnrollmentFactory(
            user=self.user, course_id=unicode(course_overview.id), mode=CourseMode.VERIFIED
        )
        entitlement = CourseEntitlementFactory(
            user=self.user, enrollment_course_run=course_enrollment, mode=CourseMode.VERIFIED
        )

        session_entitlements = get_visible_sessions_for_entitlement(entitlement)
        self.assertEqual(session_entitlements, [catalog_course_run])
Exemplo n.º 2
0
    def test_get_course_runs_by_course(self, mock_get_edx_api_data):
        """
        Test retrievals of run from a Course.
        """
        catalog_course_runs = CourseRunFactory.create_batch(10)
        catalog_course = CourseFactory(course_runs=catalog_course_runs)
        mock_get_edx_api_data.return_value = catalog_course

        data = get_course_runs_for_course(course_uuid=str(catalog_course['uuid']))
        self.assertTrue(mock_get_edx_api_data.called)
        self.assertEqual(data, catalog_course_runs)
Exemplo n.º 3
0
    def test_get_course_runs(self, mock_get_edx_api_data):
        """
        Test retrieval of course runs.
        """
        catalog_course_runs = CourseRunFactory.create_batch(10)
        mock_get_edx_api_data.return_value = catalog_course_runs

        data = get_course_runs()
        self.assertTrue(mock_get_edx_api_data.called)
        self.assert_contract(mock_get_edx_api_data.call_args)
        self.assertEqual(data, catalog_course_runs)
Exemplo n.º 4
0
    def test_get_course_owners_by_course(self, mock_get_edx_api_data):
        """
        Test retrieval of course runs.
        """
        catalog_course_runs = CourseRunFactory.create_batch(10)
        catalog_course = CourseFactory(course_runs=catalog_course_runs)
        mock_get_edx_api_data.return_value = catalog_course

        data = get_owners_for_course(course_uuid=str(catalog_course['uuid']))
        assert mock_get_edx_api_data.called
        assert data == catalog_course['owners']
Exemplo n.º 5
0
    def setUpClass(cls):
        super(TestProgramListing, cls).setUpClass()

        cls.course = ModuleStoreCourseFactory()
        course_run = CourseRunFactory(key=six.text_type(cls.course.id))
        course = CourseFactory(course_runs=[course_run])

        cls.first_program = ProgramFactory(courses=[course])
        cls.second_program = ProgramFactory(courses=[course])

        cls.data = sorted([cls.first_program, cls.second_program], key=cls.program_sort_key)
    def setUpClass(cls):
        super().setUpClass()

        cls.course = ModuleStoreCourseFactory()
        course_run = CourseRunFactory(key=str(cls.course.id))  # lint-amnesty, pylint: disable=no-member
        course = CourseFactory(course_runs=[course_run])

        cls.first_program = ProgramFactory(courses=[course])
        cls.second_program = ProgramFactory(courses=[course])

        cls.data = sorted([cls.first_program, cls.second_program], key=cls.program_sort_key)
Exemplo n.º 7
0
    def test_get_course_owners_by_course(self, mock_get_edx_api_data):
        """
        Test retrieval of course runs.
        """
        catalog_course_runs = CourseRunFactory.create_batch(10)
        catalog_course = CourseFactory(course_runs=catalog_course_runs)
        mock_get_edx_api_data.return_value = catalog_course

        data = get_owners_for_course(course_uuid=str(catalog_course['uuid']))
        self.assertTrue(mock_get_edx_api_data.called)
        self.assertEqual(data, catalog_course['owners'])
Exemplo n.º 8
0
    def test_get_course_runs(self, mock_get_edx_api_data):
        """
        Test retrieval of course runs.
        """
        catalog_course_runs = CourseRunFactory.create_batch(10)
        mock_get_edx_api_data.return_value = catalog_course_runs

        data = get_course_runs()
        self.assertTrue(mock_get_edx_api_data.called)
        self.assert_contract(mock_get_edx_api_data.call_args)
        self.assertEqual(data, catalog_course_runs)
Exemplo n.º 9
0
    def setUp(self):
        super(TestProgramDataExtender, self).setUp()

        self.course = ModuleStoreCourseFactory()
        self.course.start = datetime.datetime.now(utc) - datetime.timedelta(days=1)
        self.course.end = datetime.datetime.now(utc) + datetime.timedelta(days=1)
        self.course = self.update_course(self.course, self.user.id)

        self.course_run = CourseRunFactory(key=unicode(self.course.id))
        self.catalog_course = CourseFactory(course_runs=[self.course_run])
        self.program = ProgramFactory(courses=[self.catalog_course])
Exemplo n.º 10
0
 def test_program_uuid_args(self, mock_get_programs, mock_task):
     course_1_id = 'course-v1:edX+Test+1'
     course_2_id = 'course-v1:edX+Test+2'
     program = ProgramFactory(
         courses=[
             CourseFactory(
                 course_runs=[
                     CourseRunFactory(key=course_1_id),
                     CourseRunFactory(key=course_2_id)
                 ]
             )
         ],
         curricula=[],
     )
     self.expected_options['program_uuids'] = [program['uuid']]
     mock_get_programs.return_value = [program]
     call_command(Command(), '--program_uuids', program['uuid'])
     assert mock_task.called
     assert mock_task.call_args[0][0] == self.expected_options
     assert mock_task.call_args[0][1].sort() == [course_1_id, course_2_id].sort()
Exemplo n.º 11
0
    def setUp(self):
        super(RelatedProgramsTests, self).setUp()

        self.url = reverse('dashboard')

        self.create_programs_config()
        self.client.login(username=self.user.username, password=self.password)

        course_run = CourseRunFactory(key=unicode(self.course.id))  # pylint: disable=no-member
        course = CatalogCourseFactory(course_runs=[course_run])
        self.programs = [ProgramFactory(courses=[course]) for __ in range(2)]
Exemplo n.º 12
0
    def test_multiple_published_course_runs(self):
        """
        Learner should not be eligible for one click purchase if:
            - program has a course with more than one published course run
        """
        course_run_1 = CourseRunFactory(
            key=str(ModuleStoreCourseFactory().id),
            status='published'
        )
        course_run_2 = CourseRunFactory(
            key=str(ModuleStoreCourseFactory().id),
            status='published'
        )
        course = CourseFactory(course_runs=[course_run_1, course_run_2])
        program = ProgramFactory(
            courses=[
                CourseFactory(course_runs=[
                    CourseRunFactory(
                        key=str(ModuleStoreCourseFactory().id),
                        status='published'
                    )
                ]),
                course,
                CourseFactory(course_runs=[
                    CourseRunFactory(
                        key=str(ModuleStoreCourseFactory().id),
                        status='published'
                    )
                ])
            ],
            is_program_eligible_for_one_click_purchase=True,
            applicable_seat_types=['verified']
        )
        data = ProgramDataExtender(program, self.user).extend()

        self.assertFalse(data['is_learner_eligible_for_one_click_purchase'])

        course_run_2['status'] = 'unpublished'
        data = ProgramDataExtender(program, self.user).extend()

        self.assertTrue(data['is_learner_eligible_for_one_click_purchase'])
Exemplo n.º 13
0
    def test_shared_enrollment_engagement(self, mock_get_programs):
        """
        Verify that correct programs are returned when the user is enrolled in a
        single course run appearing in multiple programs.
        """
        shared_course_run_key, solo_course_run_key = (
            generate_course_run_key() for __ in range(2))

        batch = [
            ProgramFactory(courses=[
                CourseFactory(course_runs=[
                    CourseRunFactory(key=shared_course_run_key),
                ]),
            ]) for __ in range(2)
        ]

        joint_programs = sorted(batch, key=lambda program: program['title'])
        data = joint_programs + [
            ProgramFactory(courses=[
                CourseFactory(course_runs=[
                    CourseRunFactory(key=solo_course_run_key),
                ]),
            ]),
            ProgramFactory(),
        ]

        mock_get_programs.return_value = data

        # Enrollment for the shared course run created last (most recently).
        self._create_enrollments(solo_course_run_key, shared_course_run_key)
        meter = ProgramProgressMeter(self.user)

        self._attach_detail_url(data)
        programs = data[:3]
        self.assertEqual(meter.engaged_programs, programs)
        self._assert_progress(
            meter,
            *(ProgressFactory(uuid=program['uuid'],
                              in_progress=self._extract_titles(program, 0))
              for program in programs))
        self.assertEqual(meter.completed_programs, [])
 def setUp(self):
     super(TestSyncCourseRunsCommand, self).setUp()
     # create mongo course
     self.course = CourseFactory.create()
     # load this course into course overview
     CourseOverview.get_from_id(self.course.id)
     # create a catalog course run with the same course id.
     self.catalog_course_run = CourseRunFactory(
         key=unicode(self.course.id),
         marketing_url='test_marketing_url',
         eligible_for_financial_aid=False
     )
Exemplo n.º 15
0
    def setUpClass(cls):
        super(TestProgramDetails, cls).setUpClass()

        modulestore_course = ModuleStoreCourseFactory()
        course_run = CourseRunFactory(key=six.text_type(modulestore_course.id))
        course = CourseFactory(course_runs=[course_run])

        cls.program_data = ProgramFactory(uuid=cls.program_uuid, courses=[course])
        cls.pathway_data = PathwayFactory()
        cls.program_data['pathway_ids'] = [cls.pathway_data['id']]
        cls.pathway_data['program_uuids'] = [cls.program_data['uuid']]
        del cls.pathway_data['programs']
Exemplo n.º 16
0
    def setUpClass(cls):
        super(TestProgramDetails, cls).setUpClass()

        modulestore_course = ModuleStoreCourseFactory()
        course_run = CourseRunFactory(key=unicode(modulestore_course.id))  # pylint: disable=no-member
        course = CourseFactory(course_runs=[course_run])

        cls.program_data = ProgramFactory(uuid=cls.program_uuid, courses=[course])
        cls.pathway_data = CreditPathwayFactory()
        cls.program_data['pathway_ids'] = [cls.pathway_data['id']]
        cls.pathway_data['program_uuids'] = [cls.program_data['uuid']]
        del cls.pathway_data['programs']
Exemplo n.º 17
0
    def setUpClass(cls):
        super().setUpClass()

        modulestore_course = ModuleStoreCourseFactory()
        course_run = CourseRunFactory(key=str(modulestore_course.id))  # lint-amnesty, pylint: disable=no-member
        course = CourseFactory(course_runs=[course_run])

        cls.program_data = ProgramFactory(uuid=cls.program_uuid, courses=[course])
        cls.pathway_data = PathwayFactory()
        cls.program_data['pathway_ids'] = [cls.pathway_data['id']]
        cls.pathway_data['program_uuids'] = [cls.program_data['uuid']]
        del cls.pathway_data['programs']  # lint-amnesty, pylint: disable=unsupported-delete-operation
 def setUp(self):
     super(TestSyncCourseRunsCommand, self).setUp()
     # create mongo course
     self.course = CourseFactory.create()
     # load this course into course overview
     self.course_overview = CourseOverview.get_from_id(self.course.id)
     # create a catalog course run with the same course id.
     self.catalog_course_run = CourseRunFactory(
         key=unicode(self.course.id),
         marketing_url='test_marketing_url',
         eligible_for_financial_aid=False
     )
Exemplo n.º 19
0
    def test_unrelated_program_not_listed(self, mock_get_programs):
        """Verify that unrelated programs don't appear in the listing."""
        nonexistent_course_run_id = generate_course_run_key()

        course_run = CourseRunFactory(key=nonexistent_course_run_id)
        course = CatalogCourseFactory(course_runs=[course_run])
        unrelated_program = ProgramFactory(courses=[course])

        mock_get_programs.return_value = self.programs + [unrelated_program]

        response = self.client.get(self.url)
        self.assert_related_programs(response)
        self.assertNotContains(response, unrelated_program['title'])
Exemplo n.º 20
0
    def test_get_course_run_details(self, mock_get_course_run_details):
        """
        Test for Python API `get_course_run_details` function.
        """
        course_run = CourseRunFactory()

        mock_get_course_run_details.return_value = {
            'title': course_run['title'],
        }

        results = get_course_run_details(course_run['key'], ['title'])

        assert results['title'] == course_run['title']
Exemplo n.º 21
0
    def test_mutiple_program_engagement(self, mock_get_programs):
        """
        Verify that correct programs are returned in the correct order when the
        user is enrolled in course runs appearing in programs.
        """
        newer_course_run_key, older_course_run_key = (generate_course_run_key() for __ in range(2))
        data = [
            ProgramFactory(
                courses=[
                    CourseFactory(course_runs=[
                        CourseRunFactory(key=newer_course_run_key),
                    ]),
                ]
            ),
            ProgramFactory(
                courses=[
                    CourseFactory(course_runs=[
                        CourseRunFactory(key=older_course_run_key),
                    ]),
                ]
            ),
            ProgramFactory(),
        ]
        mock_get_programs.return_value = data

        # The creation time of the enrollments matters to the test. We want
        # the first_course_run_key to represent the newest enrollment.
        self._create_enrollments(older_course_run_key, newer_course_run_key)
        meter = ProgramProgressMeter(self.user)

        self._attach_detail_url(data)
        programs = data[:2]
        self.assertEqual(meter.engaged_programs, programs)
        self._assert_progress(
            meter,
            *(ProgressFactory(uuid=program['uuid'], in_progress=1) for program in programs)
        )
        self.assertEqual(meter.completed_programs, [])
Exemplo n.º 22
0
 def test_get_course_run_details(self, mock_get_edx_api_data):
     """
     Test retrieval of details about a specific course run
     """
     course_run = CourseRunFactory()
     course_run_details = {
         'content_language': course_run['content_language'],
         'weeks_to_complete': course_run['weeks_to_complete'],
         'max_effort': course_run['max_effort']
     }
     mock_get_edx_api_data.return_value = course_run_details
     data = get_course_run_details(course_run['key'], ['content_language', 'weeks_to_complete', 'max_effort'])
     self.assertTrue(mock_get_edx_api_data.called)
     self.assertEqual(data, course_run_details)
Exemplo n.º 23
0
    def test_course_pricing_when_all_course_runs_have_no_seats(self):
        # Create three seatless course runs and add them to the program
        course_runs = []
        for __ in range(3):
            course = ModuleStoreCourseFactory()
            course = self.update_course(course, self.user.id)
            course_runs.append(CourseRunFactory(key=unicode(course.id), seats=[]))
        program = ProgramFactory(courses=[CourseFactory(course_runs=course_runs)])

        data = ProgramMarketingDataExtender(program, self.user).extend()

        self.assertEqual(data['number_of_courses'], len(program['courses']))
        self.assertEqual(data['full_program_price'], 0.0)
        self.assertEqual(data['avg_price_per_course'], 0.0)
Exemplo n.º 24
0
    def test_get_pseudo_course_overview(self, mock_get_course_run_details):
        """
        Test for the `get_pseudo_course_overview` function that creates a temporary course overview for courses that
        have been deleted.
        """
        course_run = CourseRunFactory()
        mock_get_course_run_details.return_value = {
            'title': course_run['title'],
        }
        course_key = CourseKey.from_string(course_run['key'])

        result = get_pseudo_course_overview(course_key)
        assert result.display_name == course_run['title']
        assert result.display_org_with_default == course_key.org
        assert result.certificates_show_before_end
    def test_course_overview_does_not_exist(self, mock_log_info, mock_catalog_course_runs):
        """
        Verify no error in case if a course run is not found in course overview.
        """
        nonexistent_course_run = CourseRunFactory()
        mock_catalog_course_runs.return_value = [self.catalog_course_run, nonexistent_course_run]

        call_command('sync_course_runs')

        mock_log_info.assert_any_call(
            '[sync_course_runs] course overview record not found for course run: %s',
            nonexistent_course_run['key'],
        )
        updated_marketing_url = CourseOverview.objects.get(id=self.course.id).marketing_url
        self.assertEqual(updated_marketing_url, 'test_marketing_url')
Exemplo n.º 26
0
 def test_credit_course_counted_complete_for_verified(self, mock_completed_course_runs, mock_get_programs):
     """
     Verify that 'credit' course certificate type are treated as if they were
     "verified" when checking for course completion status.
     """
     course_run_key = generate_course_run_key()
     course = CourseFactory(course_runs=[
         CourseRunFactory(key=course_run_key, type='credit'),
     ])
     program = ProgramFactory(courses=[course])
     mock_get_programs.return_value = [program]
     self._create_enrollments(course_run_key)
     meter = ProgramProgressMeter(self.user)
     mock_completed_course_runs.return_value = [{'course_run_id': course_run_key, 'type': 'verified'}]
     self.assertEqual(meter._is_course_complete(course), True)
Exemplo n.º 27
0
    def _create_course(self, course_price):
        """
        Creates the course in mongo and update it with the instructor data.
        Also creates catalog course with respect to course run.

        Returns:
            Catalog course dict.
        """
        course = ModuleStoreCourseFactory()
        course.start = datetime.datetime.now(utc) - datetime.timedelta(days=1)
        course.end = datetime.datetime.now(utc) + datetime.timedelta(days=1)
        course.instructor_info = self.instructors
        course = self.update_course(course, self.user.id)

        course_run = CourseRunFactory(key=unicode(course.id),
                                      seats=[SeatFactory(price=course_price)])
        return CourseFactory(course_runs=[course_run])
Exemplo n.º 28
0
    def test_unpublished_sessions_for_entitlement(self, mock_get_edx_api_data):
        """
        Test unpublished course runs are not part of visible session entitlements when the user
        is not enrolled.
        """
        catalog_course_run = CourseRunFactory.create(status=COURSE_UNPUBLISHED)
        catalog_course = CourseFactory(course_runs=[catalog_course_run])
        mock_get_edx_api_data.return_value = catalog_course
        course_key = CourseKey.from_string(catalog_course_run.get('key'))
        course_overview = CourseOverviewFactory.create(id=course_key, start=self.tomorrow)
        CourseModeFactory.create(mode_slug=CourseMode.VERIFIED, min_price=100, course_id=course_overview.id)
        entitlement = CourseEntitlementFactory(
            user=self.user, mode=CourseMode.VERIFIED
        )

        session_entitlements = get_visible_sessions_for_entitlement(entitlement)
        self.assertEqual(session_entitlements, [])
    def test_starting_and_ending_logs(self, mock_log_info, mock_catalog_course_runs):
        """
        Verify logging at start and end of the command.
        """
        mock_catalog_course_runs.return_value = [self.catalog_course_run, CourseRunFactory(), CourseRunFactory()]

        call_command('sync_course_runs')
        # Assert the logs at the start of the command.
        mock_log_info.assert_any_call('[sync_course_runs] Fetching course runs from catalog service.')
        # Assert the log metrics at it's completion.
        mock_log_info.assert_any_call(
            ('[sync_course_runs] course runs retrieved: %d, course runs found in course overview: %d,'
             ' course runs not found in course overview: %d, course overviews metadata updated: %d,'),
            3,
            1,
            2,
            1,
        )
    def test_handle_enqueue_failure(self, mock_log, mock_task, mock_get_programs):
        """Verify that failure to enqueue a task doesn't halt execution."""

        def side_effect(username):
            """Simulate failure to enqueue a task."""
            if username == self.alice.username:
                raise Exception

        mock_task.side_effect = side_effect

        data = [
            ProgramFactory(
                courses=[
                    CourseFactory(course_runs=[
                        CourseRunFactory(key=self.course_run_key),
                    ]),
                ]
            ),
        ]
        mock_get_programs.return_value = data

        GeneratedCertificateFactory(
            user=self.alice,
            course_id=self.course_run_key,
            mode=MODES.verified,
            status=CertificateStatuses.downloadable,
        )

        GeneratedCertificateFactory(
            user=self.bob,
            course_id=self.course_run_key,
            mode=MODES.verified,
            status=CertificateStatuses.downloadable,
        )

        call_command('backpopulate_program_credentials', commit=True)

        self.assertTrue(mock_log.called)

        calls = [
            mock.call(self.alice.username),
            mock.call(self.bob.username)
        ]
        mock_task.assert_has_calls(calls, any_order=True)
Exemplo n.º 31
0
    def test_get_visible_sessions_for_entitlement(self, mock_get_edx_api_data):
        """
        Test retrieval of visible session entitlements.
        """
        catalog_course_run = CourseRunFactory.create()
        catalog_course = CourseFactory(course_runs=[catalog_course_run])
        mock_get_edx_api_data.return_value = catalog_course
        course_key = CourseKey.from_string(catalog_course_run.get('key'))
        course_overview = CourseOverviewFactory.create(id=course_key, start=self.tomorrow)
        CourseModeFactory.create(mode_slug=CourseMode.VERIFIED, min_price=100, course_id=course_overview.id)
        course_enrollment = CourseEnrollmentFactory(
            user=self.user, course_id=unicode(course_overview.id), mode=CourseMode.VERIFIED
        )
        entitlement = CourseEntitlementFactory(
            user=self.user, enrollment_course_run=course_enrollment, mode=CourseMode.VERIFIED
        )

        session_entitlements = get_visible_sessions_for_entitlement(entitlement)
        self.assertEqual(session_entitlements, [catalog_course_run])
Exemplo n.º 32
0
    def test_in_progress_course_upgrade_deadline_check(self, offset,
                                                       mock_get_programs):
        """
        Verify that if the user's enrollment is not of the same type as the course run,
        the course will only count as in progress if there is another available seat with
        the right type for which the upgrade deadline has not passed.
        """
        course_run_key = generate_course_run_key()
        now = datetime.datetime.now(utc)
        upgrade_deadline = None if not offset else str(now +
                                                       datetime.timedelta(
                                                           days=offset))
        required_seat = SeatFactory(type='verified',
                                    upgrade_deadline=upgrade_deadline)
        enrolled_seat = SeatFactory(type='audit')
        seats = [required_seat, enrolled_seat]

        data = [
            ProgramFactory(courses=[
                CourseFactory(course_runs=[
                    CourseRunFactory(
                        key=course_run_key, type='verified', seats=seats),
                ]),
            ])
        ]
        mock_get_programs.return_value = data

        CourseEnrollmentFactory(user=self.user,
                                course_id=course_run_key,
                                mode='audit')

        meter = ProgramProgressMeter(self.site, self.user)

        program = data[0]
        expected = [
            ProgressFactory(uuid=program['uuid'],
                            completed=0,
                            in_progress=1 if offset in [None, 1] else 0,
                            not_started=1 if offset in [-1] else 0)
        ]

        self.assertEqual(meter.progress(count_only=True), expected)
 def _get_programs_data(self, hierarchy_type):
     """
     Generate a mock response for get_programs() with the given type of
     course hierarchy.  Dramatically simplifies (and makes consistent
     between test runs) the ddt-generated test_flatten methods.
     """
     if hierarchy_type == self.SEPARATE_PROGRAMS:
         return [
             ProgramFactory(
                 courses=[
                     CourseFactory(course_runs=[
                         CourseRunFactory(key=self.course_run_key),
                     ]),
                 ]
             ),
             ProgramFactory(
                 courses=[
                     CourseFactory(course_runs=[
                         CourseRunFactory(key=self.alternate_course_run_key),
                     ]),
                 ]
             ),
         ]
     elif hierarchy_type == self.SEPARATE_COURSES:
         return [
             ProgramFactory(
                 courses=[
                     CourseFactory(course_runs=[
                         CourseRunFactory(key=self.course_run_key),
                     ]),
                     CourseFactory(course_runs=[
                         CourseRunFactory(key=self.alternate_course_run_key),
                     ]),
                 ]
             ),
         ]
     else:  # SAME_COURSE
         return [
             ProgramFactory(
                 courses=[
                     CourseFactory(course_runs=[
                         CourseRunFactory(key=self.course_run_key),
                         CourseRunFactory(key=self.alternate_course_run_key),
                     ]),
                 ]
             ),
         ]
Exemplo n.º 34
0
    def setUpClass(cls):
        """
        Set up test data
        """
        super().setUpClass()
        catalog_org = CatalogOrganizationFactory.create(
            key=cls.organization_key)
        cls.program = ProgramFactory.create(
            uuid=cls.program_uuid, authoring_organizations=[catalog_org])
        organization = OrganizationFactory.create(
            short_name=cls.organization_key)
        SAMLProviderConfigFactory.create(organization=organization)

        catalog_course_id_str = 'course-v1:edX+ToyX'
        course_run_id_str = f'{catalog_course_id_str}+Toy_Course'
        cls.course_id = CourseKey.from_string(course_run_id_str)
        CourseOverviewFactory(id=cls.course_id)
        course_run = CourseRunFactory(key=course_run_id_str)
        cls.course = CourseFactory(key=catalog_course_id_str,
                                   course_runs=[course_run])
        cls.student_1 = UserFactory(username='******')
        cls.student_2 = UserFactory(username='******')
Exemplo n.º 35
0
    def setUp(self):
        super(TestProgramDataExtender, self).setUp()

        self.user = UserFactory()
        self.client.login(username=self.user.username, password=self.password)

        self.course = ModuleStoreCourseFactory()
        self.course.start = datetime.datetime.now(utc) - datetime.timedelta(
            days=1)
        self.course.end = datetime.datetime.now(utc) + datetime.timedelta(
            days=1)
        self.course = self.update_course(self.course, self.user.id)  # pylint: disable=no-member

        organization = OrganizationFactory()
        course_run = CourseRunFactory(key=unicode(self.course.id))  # pylint: disable=no-member
        course = CourseFactory(course_runs=[course_run])
        program = ProgramFactory(authoring_organizations=[organization],
                                 courses=[course])

        self.program = munge_catalog_program(program)
        self.course_code = self.program['course_codes'][0]
        self.run_mode = self.course_code['run_modes'][0]
Exemplo n.º 36
0
    def test_program_completion_with_no_id_professional(self, mock_get_certificates_for_user, mock_get_programs):
        """
        Verify that 'no-id-professional' certificates are treated as if they were
        'professional' certificates when determining program completion.
        """
        # Create serialized course runs like the ones we expect to receive from
        # the discovery service's API. These runs are of type 'professional'.
        course_runs = CourseRunFactory.create_batch(2, type='professional')
        program = ProgramFactory(courses=[CourseFactory(course_runs=course_runs)])
        mock_get_programs.return_value = [program]

        # Verify that the test program is not complete.
        meter = ProgramProgressMeter(self.site, self.user)
        self.assertEqual(meter.completed_programs, [])

        # Grant a 'no-id-professional' certificate for one of the course runs,
        # thereby completing the program.
        mock_get_certificates_for_user.return_value = [
            self._make_certificate_result(status='downloadable', type='no-id-professional', course_key=course_runs[0]['key'])
        ]

        # Verify that the program is complete.
        meter = ProgramProgressMeter(self.site, self.user)
        self.assertEqual(meter.completed_programs, [program['uuid']])
class TestSyncCourseRunsCommand(ModuleStoreTestCase):
    """
    Test for the sync course runs management command.
    """
    def setUp(self):
        super(TestSyncCourseRunsCommand, self).setUp()
        # create mongo course
        self.course = CourseFactory.create()
        # load this course into course overview
        self.course_overview = CourseOverview.get_from_id(self.course.id)
        # create a catalog course run with the same course id.
        self.catalog_course_run = CourseRunFactory(
            key=unicode(self.course.id),
            marketing_url='test_marketing_url',
            eligible_for_financial_aid=False
        )

    def test_course_run_sync(self, mock_catalog_course_runs):
        """
        Verify on executing management command course overview data is updated
        with course run data from course discovery.
        """
        mock_catalog_course_runs.return_value = [self.catalog_course_run]

        call_command('sync_course_runs')
        updated_course_overview = CourseOverview.objects.get(id=self.course.id)

        # assert fields have updated
        for field in sync_command.course_run_fields:
            course_overview_field_name = field.course_overview_name
            catalog_field_name = field.catalog_name

            previous_course_overview_value = getattr(self.course_overview, course_overview_field_name),
            updated_course_overview_value = getattr(updated_course_overview, course_overview_field_name)

            # course overview value matches catalog value
            self.assertEqual(
                updated_course_overview_value,
                self.catalog_course_run.get(catalog_field_name),
            )
            # new value doesn't match old value
            self.assertNotEqual(
                updated_course_overview_value,
                previous_course_overview_value,
            )

    @mock.patch(COMMAND_MODULE + '.log.info')
    def test_course_overview_does_not_exist(self, mock_log_info, mock_catalog_course_runs):
        """
        Verify no error in case if a course run is not found in course overview.
        """
        nonexistent_course_run = CourseRunFactory()
        mock_catalog_course_runs.return_value = [self.catalog_course_run, nonexistent_course_run]

        call_command('sync_course_runs')

        mock_log_info.assert_any_call(
            '[sync_course_runs] course overview record not found for course run: %s',
            nonexistent_course_run['key'],
        )
        updated_marketing_url = CourseOverview.objects.get(id=self.course.id).marketing_url
        self.assertEqual(updated_marketing_url, 'test_marketing_url')

    @mock.patch(COMMAND_MODULE + '.log.info')
    def test_starting_and_ending_logs(self, mock_log_info, mock_catalog_course_runs):
        """
        Verify logging at start and end of the command.
        """
        def _assert_logs(num_updates):
            mock_log_info.assert_any_call('[sync_course_runs] Fetching course runs from catalog service.')
            mock_log_info.assert_any_call(
                '[sync_course_runs] course runs found in catalog: %d, course runs found in course overview: %d,'
                ' course runs not found in course overview: %d, course overviews updated: %d',
                3,
                1,
                2,
                num_updates,
            )
            mock_log_info.reset_mock()

        mock_catalog_course_runs.return_value = [self.catalog_course_run, CourseRunFactory(), CourseRunFactory()]

        call_command('sync_course_runs')
        _assert_logs(num_updates=1)

        call_command('sync_course_runs')
        _assert_logs(num_updates=0)
Exemplo n.º 38
0
class TestProgramDataExtender(ModuleStoreTestCase):
    """Tests of the program data extender utility class."""
    maxDiff = None
    sku = 'abc123'
    checkout_path = '/basket'
    instructors = {
        'instructors': [
            {
                'name': 'test-instructor1',
                'organization': 'TextX',
            },
            {
                'name': 'test-instructor2',
                'organization': 'TextX',
            }
        ]
    }

    def setUp(self):
        super(TestProgramDataExtender, self).setUp()

        self.course = ModuleStoreCourseFactory()
        self.course.start = datetime.datetime.now(utc) - datetime.timedelta(days=1)
        self.course.end = datetime.datetime.now(utc) + datetime.timedelta(days=1)
        self.course = self.update_course(self.course, self.user.id)

        self.course_run = CourseRunFactory(key=unicode(self.course.id))
        self.catalog_course = CourseFactory(course_runs=[self.course_run])
        self.program = ProgramFactory(courses=[self.catalog_course])
        self.course_price = 100

    def _assert_supplemented(self, actual, **kwargs):
        """DRY helper used to verify that program data is extended correctly."""
        self.course_run.update(
            dict(
                {
                    'certificate_url': None,
                    'course_url': reverse('course_root', args=[self.course.id]),
                    'enrollment_open_date': strftime_localized(DEFAULT_ENROLLMENT_START_DATE, 'SHORT_DATE'),
                    'is_course_ended': self.course.end < datetime.datetime.now(utc),
                    'is_enrolled': False,
                    'is_enrollment_open': True,
                    'upgrade_url': None,
                    'advertised_start': None,
                },
                **kwargs
            )
        )

        self.catalog_course['course_runs'] = [self.course_run]
        self.program['courses'] = [self.catalog_course]

        self.assertEqual(actual, self.program)

    @ddt.data(-1, 0, 1)
    def test_is_enrollment_open(self, days_offset):
        """
        Verify that changes to the course run end date do not affect our
        assessment of the course run being open for enrollment.
        """
        self.course.end = datetime.datetime.now(utc) + datetime.timedelta(days=days_offset)
        self.course = self.update_course(self.course, self.user.id)

        data = ProgramDataExtender(self.program, self.user).extend()

        self._assert_supplemented(data)

    @ddt.data(
        (False, None, False),
        (True, MODES.audit, True),
        (True, MODES.verified, False),
    )
    @ddt.unpack
    @mock.patch(UTILS_MODULE + '.CourseMode.mode_for_course')
    def test_student_enrollment_status(self, is_enrolled, enrolled_mode, is_upgrade_required, mock_get_mode):
        """Verify that program data is supplemented with the student's enrollment status."""
        expected_upgrade_url = '{root}/{path}?sku={sku}'.format(
            root=ECOMMERCE_URL_ROOT,
            path=self.checkout_path.strip('/'),
            sku=self.sku,
        )

        update_commerce_config(enabled=True, checkout_page=self.checkout_path)

        mock_mode = mock.Mock()
        mock_mode.sku = self.sku
        mock_get_mode.return_value = mock_mode

        if is_enrolled:
            CourseEnrollmentFactory(user=self.user, course_id=self.course.id, mode=enrolled_mode)

        data = ProgramDataExtender(self.program, self.user).extend()

        self._assert_supplemented(
            data,
            is_enrolled=is_enrolled,
            upgrade_url=expected_upgrade_url if is_upgrade_required else None
        )

    @ddt.data(MODES.audit, MODES.verified)
    def test_inactive_enrollment_no_upgrade(self, enrolled_mode):
        """
        Verify that a student with an inactive enrollment isn't encouraged to upgrade.
        """
        update_commerce_config(enabled=True, checkout_page=self.checkout_path)

        CourseEnrollmentFactory(
            user=self.user,
            course_id=self.course.id,
            mode=enrolled_mode,
            is_active=False,
        )

        data = ProgramDataExtender(self.program, self.user).extend()

        self._assert_supplemented(data)

    @mock.patch(UTILS_MODULE + '.CourseMode.mode_for_course')
    def test_ecommerce_disabled(self, mock_get_mode):
        """
        Verify that the utility can operate when the ecommerce service is disabled.
        """
        update_commerce_config(enabled=False, checkout_page=self.checkout_path)

        mock_mode = mock.Mock()
        mock_mode.sku = self.sku
        mock_get_mode.return_value = mock_mode

        CourseEnrollmentFactory(user=self.user, course_id=self.course.id, mode=MODES.audit)

        data = ProgramDataExtender(self.program, self.user).extend()

        self._assert_supplemented(data, is_enrolled=True, upgrade_url=None)

    @ddt.data(
        (1, 1, False),
        (1, -1, True),
    )
    @ddt.unpack
    def test_course_run_enrollment_status(self, start_offset, end_offset, is_enrollment_open):
        """
        Verify that course run enrollment status is reflected correctly.
        """
        self.course.enrollment_start = datetime.datetime.now(utc) - datetime.timedelta(days=start_offset)
        self.course.enrollment_end = datetime.datetime.now(utc) - datetime.timedelta(days=end_offset)

        self.course = self.update_course(self.course, self.user.id)

        data = ProgramDataExtender(self.program, self.user).extend()

        self._assert_supplemented(
            data,
            is_enrollment_open=is_enrollment_open,
            enrollment_open_date=strftime_localized(self.course.enrollment_start, 'SHORT_DATE'),
        )

    def test_no_enrollment_start_date(self):
        """
        Verify that a closed course run with no explicit enrollment start date
        doesn't cause an error. Regression test for ECOM-4973.
        """
        self.course.enrollment_end = datetime.datetime.now(utc) - datetime.timedelta(days=1)
        self.course = self.update_course(self.course, self.user.id)

        data = ProgramDataExtender(self.program, self.user).extend()

        self._assert_supplemented(
            data,
            is_enrollment_open=False,
        )

    @ddt.data(True, False)
    @mock.patch(UTILS_MODULE + '.certificate_api.certificate_downloadable_status')
    @mock.patch(CERTIFICATES_API_MODULE + '.has_html_certificates_enabled')
    def test_certificate_url_retrieval(self, is_uuid_available, mock_html_certs_enabled, mock_get_cert_data):
        """
        Verify that the student's run mode certificate is included,
        when available.
        """
        test_uuid = uuid.uuid4().hex
        mock_get_cert_data.return_value = {'uuid': test_uuid} if is_uuid_available else {}
        mock_html_certs_enabled.return_value = True

        data = ProgramDataExtender(self.program, self.user).extend()

        expected_url = reverse(
            'certificates:render_cert_by_uuid',
            kwargs={'certificate_uuid': test_uuid}
        ) if is_uuid_available else None

        self._assert_supplemented(data, certificate_url=expected_url)

    @ddt.data(True, False)
    def test_may_certify_attached(self, may_certify):
        """
        Verify that the `may_certify` is included during data extension.
        """
        self.course.certificates_show_before_end = may_certify
        self.course = self.update_course(self.course, self.user.id)

        data = ProgramDataExtender(self.program, self.user).extend()

        self.assertEqual(may_certify, data['courses'][0]['course_runs'][0]['may_certify'])

        self._assert_supplemented(data)

    def test_learner_eligibility_for_one_click_purchase(self):
        """
        Learner should be eligible for one click purchase if:
            - program is eligible for one click purchase
            - There are courses remaining that have not been purchased and enrolled in.
        """
        data = ProgramDataExtender(self.program, self.user).extend()
        self.assertFalse(data['is_learner_eligible_for_one_click_purchase'])

        courses = [_create_course(self, self.course_price)]

        program = ProgramFactory(
            courses=courses,
            is_program_eligible_for_one_click_purchase=False
        )
        data = ProgramDataExtender(program, self.user).extend()
        self.assertFalse(data['is_learner_eligible_for_one_click_purchase'])

        course1 = _create_course(self, self.course_price)
        course2 = _create_course(self, self.course_price)
        CourseEnrollmentFactory(user=self.user, course_id=course1['course_runs'][0]['key'], mode='verified')
        CourseEnrollmentFactory(user=self.user, course_id=course2['course_runs'][0]['key'], mode='audit')
        program2 = ProgramFactory(
            courses=[course1, course2],
            is_program_eligible_for_one_click_purchase=True,
            applicable_seat_types=['verified'],
        )
        data = ProgramDataExtender(program2, self.user).extend()
        self.assertTrue(data['is_learner_eligible_for_one_click_purchase'])

    def test_learner_eligibility_for_one_click_purchase_with_unpublished(self):
        """
        Learner should be eligible for one click purchase if:
            - program is eligible for one click purchase
            - There are courses remaining that have not been purchased and enrolled in.
        """
        course1 = _create_course(self, self.course_price, course_run_count=2)
        course2 = _create_course(self, self.course_price)
        CourseEnrollmentFactory(user=self.user, course_id=course1['course_runs'][0]['key'], mode='verified')
        course1['course_runs'][0]['status'] = 'unpublished'
        program2 = ProgramFactory(
            courses=[course1, course2],
            is_program_eligible_for_one_click_purchase=True,
            applicable_seat_types=['verified'],
        )
        data = ProgramDataExtender(program2, self.user).extend()
        self.assertEqual(len(data['skus']), 1)
        self.assertTrue(data['is_learner_eligible_for_one_click_purchase'])

    def test_learner_eligibility_for_one_click_purchase_professional_no_id(self):
        """
        Learner should not be eligible for one click purchase if:
            - There are no courses remaining that have not been purchased and enrolled in.
        This test is primarily for the case of no-id-professional enrollment modes
        """
        course1 = _create_course(self, self.course_price)
        CourseEnrollmentFactory(user=self.user, course_id=course1['course_runs'][0]['key'], mode='no-id-professional')
        program2 = ProgramFactory(
            courses=[course1],
            is_program_eligible_for_one_click_purchase=True,
            applicable_seat_types=['professional'],  # There is no seat type for no-id-professional, it
                                                     # instead uses professional
        )
        data = ProgramDataExtender(program2, self.user).extend()
        self.assertFalse(data['is_learner_eligible_for_one_click_purchase'])

    def test_multiple_published_course_runs(self):
        """
        Learner should not be eligible for one click purchase if:
            - program has a course with more than one published course run
        """
        course_run_1 = CourseRunFactory(
            key=str(ModuleStoreCourseFactory().id),
            status='published'
        )
        course_run_2 = CourseRunFactory(
            key=str(ModuleStoreCourseFactory().id),
            status='published'
        )
        course = CourseFactory(course_runs=[course_run_1, course_run_2])
        program = ProgramFactory(
            courses=[
                CourseFactory(course_runs=[
                    CourseRunFactory(
                        key=str(ModuleStoreCourseFactory().id),
                        status='published'
                    )
                ]),
                course,
                CourseFactory(course_runs=[
                    CourseRunFactory(
                        key=str(ModuleStoreCourseFactory().id),
                        status='published'
                    )
                ])
            ],
            is_program_eligible_for_one_click_purchase=True,
            applicable_seat_types=['verified']
        )
        data = ProgramDataExtender(program, self.user).extend()

        self.assertFalse(data['is_learner_eligible_for_one_click_purchase'])

        course_run_2['status'] = 'unpublished'
        data = ProgramDataExtender(program, self.user).extend()

        self.assertTrue(data['is_learner_eligible_for_one_click_purchase'])
Exemplo n.º 39
0
class TestProgramDataExtender(ModuleStoreTestCase):
    """Tests of the program data extender utility class."""
    maxDiff = None
    sku = 'abc123'
    checkout_path = '/basket'

    def setUp(self):
        super(TestProgramDataExtender, self).setUp()

        self.course = ModuleStoreCourseFactory()
        self.course.start = datetime.datetime.now(utc) - datetime.timedelta(days=1)
        self.course.end = datetime.datetime.now(utc) + datetime.timedelta(days=1)
        self.course = self.update_course(self.course, self.user.id)

        self.course_run = CourseRunFactory(key=unicode(self.course.id))
        self.catalog_course = CourseFactory(course_runs=[self.course_run])
        self.program = ProgramFactory(courses=[self.catalog_course])

    def _assert_supplemented(self, actual, **kwargs):
        """DRY helper used to verify that program data is extended correctly."""
        self.course_run.update(
            dict(
                {
                    'certificate_url': None,
                    'course_url': reverse('course_root', args=[self.course.id]),
                    'enrollment_open_date': strftime_localized(DEFAULT_ENROLLMENT_START_DATE, 'SHORT_DATE'),
                    'is_course_ended': self.course.end < datetime.datetime.now(utc),
                    'is_enrolled': False,
                    'is_enrollment_open': True,
                    'upgrade_url': None,
                    'advertised_start': None,
                },
                **kwargs
            )
        )

        self.catalog_course['course_runs'] = [self.course_run]
        self.program['courses'] = [self.catalog_course]

        self.assertEqual(actual, self.program)

    @ddt.data(-1, 0, 1)
    def test_is_enrollment_open(self, days_offset):
        """
        Verify that changes to the course run end date do not affect our
        assessment of the course run being open for enrollment.
        """
        self.course.end = datetime.datetime.now(utc) + datetime.timedelta(days=days_offset)
        self.course = self.update_course(self.course, self.user.id)

        data = ProgramDataExtender(self.program, self.user).extend()

        self._assert_supplemented(data)

    @ddt.data(
        (False, None, False),
        (True, MODES.audit, True),
        (True, MODES.verified, False),
    )
    @ddt.unpack
    @mock.patch(UTILS_MODULE + '.CourseMode.mode_for_course')
    def test_student_enrollment_status(self, is_enrolled, enrolled_mode, is_upgrade_required, mock_get_mode):
        """Verify that program data is supplemented with the student's enrollment status."""
        expected_upgrade_url = '{root}/{path}?sku={sku}'.format(
            root=ECOMMERCE_URL_ROOT,
            path=self.checkout_path.strip('/'),
            sku=self.sku,
        )

        update_commerce_config(enabled=True, checkout_page=self.checkout_path)

        mock_mode = mock.Mock()
        mock_mode.sku = self.sku
        mock_get_mode.return_value = mock_mode

        if is_enrolled:
            CourseEnrollmentFactory(user=self.user, course_id=self.course.id, mode=enrolled_mode)

        data = ProgramDataExtender(self.program, self.user).extend()

        self._assert_supplemented(
            data,
            is_enrolled=is_enrolled,
            upgrade_url=expected_upgrade_url if is_upgrade_required else None
        )

    @ddt.data(MODES.audit, MODES.verified)
    def test_inactive_enrollment_no_upgrade(self, enrolled_mode):
        """
        Verify that a student with an inactive enrollment isn't encouraged to upgrade.
        """
        update_commerce_config(enabled=True, checkout_page=self.checkout_path)

        CourseEnrollmentFactory(
            user=self.user,
            course_id=self.course.id,
            mode=enrolled_mode,
            is_active=False,
        )

        data = ProgramDataExtender(self.program, self.user).extend()

        self._assert_supplemented(data)

    @mock.patch(UTILS_MODULE + '.CourseMode.mode_for_course')
    def test_ecommerce_disabled(self, mock_get_mode):
        """
        Verify that the utility can operate when the ecommerce service is disabled.
        """
        update_commerce_config(enabled=False, checkout_page=self.checkout_path)

        mock_mode = mock.Mock()
        mock_mode.sku = self.sku
        mock_get_mode.return_value = mock_mode

        CourseEnrollmentFactory(user=self.user, course_id=self.course.id, mode=MODES.audit)

        data = ProgramDataExtender(self.program, self.user).extend()

        self._assert_supplemented(data, is_enrolled=True, upgrade_url=None)

    @ddt.data(
        (1, 1, False),
        (1, -1, True),
    )
    @ddt.unpack
    def test_course_run_enrollment_status(self, start_offset, end_offset, is_enrollment_open):
        """
        Verify that course run enrollment status is reflected correctly.
        """
        self.course.enrollment_start = datetime.datetime.now(utc) - datetime.timedelta(days=start_offset)
        self.course.enrollment_end = datetime.datetime.now(utc) - datetime.timedelta(days=end_offset)

        self.course = self.update_course(self.course, self.user.id)

        data = ProgramDataExtender(self.program, self.user).extend()

        self._assert_supplemented(
            data,
            is_enrollment_open=is_enrollment_open,
            enrollment_open_date=strftime_localized(self.course.enrollment_start, 'SHORT_DATE'),
        )

    def test_no_enrollment_start_date(self):
        """
        Verify that a closed course run with no explicit enrollment start date
        doesn't cause an error. Regression test for ECOM-4973.
        """
        self.course.enrollment_end = datetime.datetime.now(utc) - datetime.timedelta(days=1)
        self.course = self.update_course(self.course, self.user.id)

        data = ProgramDataExtender(self.program, self.user).extend()

        self._assert_supplemented(
            data,
            is_enrollment_open=False,
        )

    @ddt.data(True, False)
    @mock.patch(UTILS_MODULE + '.certificate_api.certificate_downloadable_status')
    @mock.patch(CERTIFICATES_API_MODULE + '.has_html_certificates_enabled')
    def test_certificate_url_retrieval(self, is_uuid_available, mock_html_certs_enabled, mock_get_cert_data):
        """
        Verify that the student's run mode certificate is included,
        when available.
        """
        test_uuid = uuid.uuid4().hex
        mock_get_cert_data.return_value = {'uuid': test_uuid} if is_uuid_available else {}
        mock_html_certs_enabled.return_value = True

        data = ProgramDataExtender(self.program, self.user).extend()

        expected_url = reverse(
            'certificates:render_cert_by_uuid',
            kwargs={'certificate_uuid': test_uuid}
        ) if is_uuid_available else None

        self._assert_supplemented(data, certificate_url=expected_url)