Esempio n. 1
0
    def mock_courses_api(self):
        # Create existing seats to be removed by ingest
        audit_run_type = CourseRunType.objects.get(slug=CourseRunType.AUDIT)
        credit_run_type = CourseRunType.objects.get(slug=CourseRunType.CREDIT_VERIFIED_AUDIT)
        verified_run_type = CourseRunType.objects.get(slug=CourseRunType.VERIFIED_AUDIT)
        audit_run = CourseRunFactory(title_override='audit', key='audit/course/run', type=audit_run_type)
        verified_run = CourseRunFactory(title_override='verified', key='verified/course/run', type=verified_run_type)
        credit_run = CourseRunFactory(title_override='credit', key='credit/course/run', type=credit_run_type)
        no_currency_run = CourseRunFactory(title_override='no currency', key='nocurrency/course/run',
                                           type=verified_run_type)

        professional_type = SeatTypeFactory.professional()
        SeatFactory(course_run=audit_run, type=professional_type)
        SeatFactory(course_run=verified_run, type=professional_type)
        SeatFactory(course_run=credit_run, type=professional_type)
        SeatFactory(course_run=no_currency_run, type=professional_type)

        bodies = mock_data.ECOMMERCE_API_BODIES
        url = self.api_url + 'courses/'
        responses.add_callback(
            responses.GET,
            url,
            callback=mock_api_callback(url, bodies),
            content_type=JSON
        )
        return bodies
Esempio n. 2
0
    def test_ingest_verified_deadline(self, mock_push_to_ecomm):
        """ Verify the method ingests data from the Courses API. """
        api_data = self.mock_api()

        self.assertEqual(Course.objects.count(), 0)
        self.assertEqual(CourseRun.objects.count(), 0)

        # Assume that while we are relying on ORGS_ON_OLD_PUBLISHER it will never be empty
        with self.settings(ORGS_ON_OLD_PUBLISHER='OTHER'):
            self.loader.ingest()

        # Verify the API was called with the correct authorization header
        self.assert_api_called(4)

        runs = CourseRun.objects.all()
        # Run with a verified entitlement, but no change in end date
        run1 = runs[0]
        run1.seats.add(
            SeatFactory(course_run=run1, type=SeatTypeFactory.verified()))
        run1.save()

        # Run with a verified entitlement, and the end date has changed
        run2 = runs[1]
        run2.seats.add(
            SeatFactory(
                course_run=run2,
                type=SeatTypeFactory.verified(),
                upgrade_deadline=datetime.datetime.now(pytz.UTC),
            ))
        original_run2_deadline = run2.seats.first().upgrade_deadline
        run2.end = datetime.datetime.now(pytz.UTC)
        run2.save()

        # Run with a credit entitlement, and the end date has changed should not
        run3 = runs[2]
        run3.seats.add(
            SeatFactory(
                course_run=run3,
                type=SeatTypeFactory.credit(),
                upgrade_deadline=None,
            ))
        run3.end = datetime.datetime.now(pytz.UTC)
        run3.save()

        # Verify the CourseRuns were created correctly
        expected_num_course_runs = len(api_data)
        self.assertEqual(CourseRun.objects.count(), expected_num_course_runs)

        # Verify multiple calls to ingest data do NOT result in data integrity errors.
        self.loader.ingest()
        calls = [
            mock.call(run2),
            mock.call(run3),
        ]
        mock_push_to_ecomm.assert_has_calls(calls)
        # Make sure the verified seat with a course run end date is changed
        self.assertNotEqual(original_run2_deadline,
                            run2.seats.first().upgrade_deadline)
        # Make sure the credit seat with a course run end date is unchanged
        self.assertIsNone(run3.seats.first().upgrade_deadline)
Esempio n. 3
0
    def test_courses_with_include_archived(self):
        """
        Verify the endpoint returns the list of available and archived courses if include archived
        is True in catalog.
        """
        url = reverse('api:v1:catalog-courses', kwargs={'id': self.catalog.id})
        Course.objects.all().delete()

        now = datetime.datetime.now(pytz.UTC)
        future = now + datetime.timedelta(days=30)
        past = now - datetime.timedelta(days=30)

        course_run = CourseRunFactory.create(
            course__title='ABC Test Course With Archived', end=future, enrollment_end=future
        )
        SeatFactory.create(course_run=course_run)
        # Create an archived course run
        CourseRunFactory.create(course=course_run.course, end=past)

        response = self.client.get(url)

        assert response.status_code == 200
        # The course appearing in response should have on 1 course run
        assert len(response.data['results'][0]['course_runs']) == 1

        # Mark include archived True in catalog
        self.catalog.include_archived = True
        self.catalog.save()
        response = self.client.get(url)

        assert response.status_code == 200
        # The course appearing in response should include archived course run
        assert len(response.data['results'][0]['course_runs']) == 2
Esempio n. 4
0
    def test_ensure_draft_world_not_draft_course_run_given(self):
        course = CourseFactory()
        course_run = CourseRunFactory(course=course)
        verified_seat = SeatFactory(type='verified', course_run=course_run)
        audit_seat = SeatFactory(type='audit', course_run=course_run)
        course_run.seats.add(verified_seat, audit_seat)

        ensured_draft_course_run = utils.ensure_draft_world(course_run)
        not_draft_course_run = CourseRun.objects.get(uuid=course_run.uuid)

        self.assertNotEqual(ensured_draft_course_run, not_draft_course_run)
        self.assertEqual(ensured_draft_course_run.uuid,
                         not_draft_course_run.uuid)
        self.assertTrue(ensured_draft_course_run.draft)
        self.assertNotEqual(ensured_draft_course_run.course,
                            not_draft_course_run.course)
        self.assertEqual(ensured_draft_course_run.course.uuid,
                         not_draft_course_run.course.uuid)

        # Check slugs are equal
        self.assertEqual(ensured_draft_course_run.slug,
                         not_draft_course_run.slug)

        # Seat checks
        draft_seats = ensured_draft_course_run.seats.all()
        not_draft_seats = not_draft_course_run.seats.all()
        self.assertNotEqual(draft_seats, not_draft_seats)
        self.assertEqual(len(draft_seats), len(not_draft_seats))
        for i, __ in enumerate(draft_seats):
            self.assertEqual(draft_seats[i].price, not_draft_seats[i].price)
            self.assertEqual(draft_seats[i].sku, not_draft_seats[i].sku)
            self.assertNotEqual(draft_seats[i].course_run,
                                not_draft_seats[i].course_run)
            self.assertEqual(draft_seats[i].course_run.uuid,
                             not_draft_seats[i].course_run.uuid)
            self.assertEqual(draft_seats[i].official_version,
                             not_draft_seats[i])
            self.assertEqual(not_draft_seats[i].draft_version, draft_seats[i])

        # Check draft course is also created
        draft_course = ensured_draft_course_run.course
        not_draft_course = Course.objects.get(uuid=course.uuid)
        self.assertNotEqual(draft_course, not_draft_course)
        self.assertEqual(draft_course.uuid, not_draft_course.uuid)
        self.assertTrue(draft_course.draft)

        # Check official and draft versions match up
        self.assertEqual(ensured_draft_course_run.official_version,
                         not_draft_course_run)
        self.assertEqual(not_draft_course_run.draft_version,
                         ensured_draft_course_run)
    def setUp(self):
        super(AffiliateWindowViewSetTests, self).setUp()
        self.user = UserFactory()
        self.client.force_authenticate(self.user)
        self.catalog = CatalogFactory(query='*:*', viewers=[self.user])

        self.enrollment_end = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=30)
        self.course_end = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=60)
        self.course_run = CourseRunFactory(enrollment_end=self.enrollment_end, end=self.course_end)

        self.seat_verified = SeatFactory(course_run=self.course_run, type=Seat.VERIFIED)
        self.course = self.course_run.course
        self.affiliate_url = reverse('api:v1:partners:affiliate_window-detail', kwargs={'pk': self.catalog.id})
        self.refresh_index()
Esempio n. 6
0
    def setUp(self):
        super(CreateCoursesTests, self).setUp()

        transcript_languages = LanguageTag.objects.all()[:2]
        self.subjects = SubjectFactory.create_batch(3)
        self.course = CourseFactory(subjects=self.subjects)

        self.command_name = 'import_metadata_courses'
        self.command_args = ['--start_id={}'.format(self.course.id), '--end_id={}'.format(self.course.id)]

        # create multiple course-runs against course.
        course_runs = CourseRunFactory.create_batch(
            3, course=self.course, transcript_languages=transcript_languages,
            language=transcript_languages[0],
            short_description_override='Testing description'
        )

        canonical_course_run = course_runs[0]
        for seat_type in ['honor', 'credit', 'verified']:  # to avoid same type seat creation.
            SeatFactory(course_run=canonical_course_run, type=seat_type)

        staff = PersonFactory.create_batch(2)
        canonical_course_run.staff.add(*staff)

        self.course.canonical_course_run = canonical_course_run
        self.course.save()

        # create org and assign to the course-metadata
        self.forganization_extension = factories.OrganizationExtensionFactory()
        self.organization = self.forganization_extension.organization
        self.course.authoring_organizations.add(self.organization)
    def test_data(self):
        user = UserFactory()
        CatalogFactory(query='*:*', viewers=[user])
        course_run = CourseRunFactory()
        seat = SeatFactory(course_run=course_run)
        serializer = AffiliateWindowSerializer(seat)

        # Verify none of the course run attributes are empty; otherwise, Affiliate Window will report errors.
        # pylint: disable=no-member
        self.assertTrue(
            all((course_run.title, course_run.short_description,
                 course_run.marketing_url)))

        expected = {
            'pid': '{}-{}'.format(course_run.key, seat.type),
            'name': course_run.title,
            'desc': course_run.short_description,
            'purl': course_run.marketing_url,
            'price': {
                'actualp': seat.price
            },
            'currency': seat.currency.code,
            'imgurl': course_run.card_image_url,
            'category': 'Other Experiences'
        }
        self.assertDictEqual(serializer.data, expected)
Esempio n. 8
0
    def test_marketable_enrollable_course_runs_with_archived(self, marketable_enrollable_course_runs_with_archived):
        """ Verify the endpoint filters course runs to those that are marketable and
        enrollable, including archived course runs (with an end date in the past). """

        past = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=2)
        future = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=2)

        course_run = CourseRunFactory(enrollment_start=None, enrollment_end=future, course=self.course)
        SeatFactory(course_run=course_run)

        filtered_course_runs = [
            CourseRunFactory(enrollment_start=None, enrollment_end=None, course=self.course),
            CourseRunFactory(
                enrollment_start=past, enrollment_end=future, course=self.course
            ),
            CourseRunFactory(enrollment_start=future, course=self.course),
            CourseRunFactory(enrollment_end=past, course=self.course),
        ]

        url = reverse('api:v1:course-detail', kwargs={'key': self.course.key})
        url = '{}?marketable_enrollable_course_runs_with_archived={}'.format(
            url, marketable_enrollable_course_runs_with_archived
        )
        response = self.client.get(url)

        assert response.status_code == 200

        if marketable_enrollable_course_runs_with_archived:
            # Emulate prefetching behavior.
            for course_run in filtered_course_runs:
                course_run.delete()

        assert response.data == self.serialize_course(self.course)
Esempio n. 9
0
    def test_affiliate_with_supported_seats(self):
        """ Verify that endpoint returns course runs for verified and professional seats only. """
        response = self.client.get(self.affiliate_url)

        self.assertEqual(response.status_code, 200)
        root = ET.fromstring(response.content)
        self.assertEqual(1, len(root.findall('product')))
        self.assert_product_xml(
            root.findall('product/[pid="{}-{}"]'.format(
                self.course_run.key, self.seat_verified.type.slug))[0],
            self.seat_verified)

        # Add professional seat
        seat_professional = SeatFactory(course_run=self.course_run,
                                        type=SeatTypeFactory.professional())

        response = self.client.get(self.affiliate_url)
        root = ET.fromstring(response.content)
        self.assertEqual(2, len(root.findall('product')))

        self.assert_product_xml(
            root.findall('product/[pid="{}-{}"]'.format(
                self.course_run.key, self.seat_verified.type.slug))[0],
            self.seat_verified)
        self.assert_product_xml(
            root.findall('product/[pid="{}-{}"]'.format(
                self.course_run.key, seat_professional.type.slug))[0],
            seat_professional)
Esempio n. 10
0
    def test_marketable_seats_exclusions(self, has_seats):
        """ Verify that the method excludes CourseRuns without seats. """
        course_run = CourseRunFactory()

        if has_seats:
            SeatFactory(course_run=course_run)

        assert CourseRun.objects.marketable().exists() == has_seats
Esempio n. 11
0
 def test_data(self):
     request = make_request()
     course_run = CourseRunFactory()
     SeatFactory(course_run=course_run)
     serializer_context = {'request': request}
     serializer = FlattenedCourseRunWithCourseSerializer(
         course_run, context=serializer_context)
     expected = self.get_expected_data(request, course_run)
     self.assertDictEqual(serializer.data, expected)
Esempio n. 12
0
    def test_marketable_exclusions(self):
        """ Verify the method excludes CourseRuns without a slug. """
        course_run = CourseRunFactory()
        SeatFactory(course_run=course_run)

        course_run.slug = ''  # blank out auto-generated slug
        course_run.save()

        self.assertEqual(CourseRun.objects.marketable().exists(), False)
    def test_marketable_course_runs_only(self, marketable_course_runs_only):
        """
        Verify that a client requesting marketable_course_runs_only only receives
        course runs that are published, have seats, and can still be enrolled in.
        """
        # Published course run with a seat, no enrollment start or end, and an end date in the future.
        enrollable_course_run = CourseRunFactory(
            status=CourseRunStatus.Published,
            end=datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=10),
            enrollment_start=None,
            enrollment_end=None,
            course=self.course)
        SeatFactory(course_run=enrollable_course_run)

        # Unpublished course run with a seat.
        unpublished_course_run = CourseRunFactory(
            status=CourseRunStatus.Unpublished, course=self.course)
        SeatFactory(course_run=unpublished_course_run)

        # Published course run with no seats.
        no_seats_course_run = CourseRunFactory(
            status=CourseRunStatus.Published, course=self.course)

        # Published course run with a seat and an end date in the past.
        closed_course_run = CourseRunFactory(
            status=CourseRunStatus.Published,
            end=datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=10),
            course=self.course)
        SeatFactory(course_run=closed_course_run)

        url = reverse('api:v1:course-detail', kwargs={'key': self.course.key})
        url = '{}?marketable_course_runs_only={}'.format(
            url, marketable_course_runs_only)
        response = self.client.get(url)

        assert response.status_code == 200

        if marketable_course_runs_only:
            # Emulate prefetching behavior.
            for course_run in (unpublished_course_run, no_seats_course_run,
                               closed_course_run):
                course_run.delete()

        assert response.data == self.serialize_course(self.course)
Esempio n. 14
0
    def test_marketable_unpublished_exclusions(self, is_published):
        """ Verify the method excludes CourseRuns with Unpublished status. """
        course_run = CourseRunFactory(status=CourseRunStatus.Unpublished)
        SeatFactory(course_run=course_run)

        if is_published:
            course_run.status = CourseRunStatus.Published
            course_run.save()

        assert CourseRun.objects.marketable().exists() == is_published
Esempio n. 15
0
 def test_data_without_level_type(self):
     """ Verify the serializer handles courses with no level type set. """
     request = make_request()
     course_run = CourseRunFactory(course__level_type=None)
     SeatFactory(course_run=course_run)
     serializer_context = {'request': request}
     serializer = FlattenedCourseRunWithCourseSerializer(
         course_run, context=serializer_context)
     expected = self.get_expected_data(request, course_run)
     self.assertDictEqual(serializer.data, expected)
Esempio n. 16
0
    def test_courses(self):
        """ Verify the endpoint returns the list of courses contained in the catalog. """
        url = reverse('api:v1:catalog-courses', kwargs={'id': self.catalog.id})

        SeatFactory(course_run=self.course_run)
        courses = [self.course]

        # These courses/course runs should not be returned because they are no longer open for enrollment.
        enrollment_end = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=30)
        excluded_runs = [
            CourseRunFactory(enrollment_end=enrollment_end, course__title='ABC Test Course 2'),
            CourseRunFactory(enrollment_end=enrollment_end, course=self.course),
        ]
        for course_run in excluded_runs:
            SeatFactory(course_run=course_run)

        with self.assertNumQueries(27):
            response = self.client.get(url)
        assert response.status_code == 200
        assert response.data['results'] == self.serialize_catalog_course(courses, many=True)
Esempio n. 17
0
    def mock_courses_api(self):
        # Create existing seats to be removed by ingest
        audit_run = CourseRunFactory(title_override='audit', key='audit/course/run')
        verified_run = CourseRunFactory(title_override='verified', key='verified/course/run')
        credit_run = CourseRunFactory(title_override='credit', key='credit/course/run')
        no_currency_run = CourseRunFactory(title_override='no currency', key='nocurrency/course/run')

        SeatFactory(course_run=audit_run, type=Seat.PROFESSIONAL)
        SeatFactory(course_run=verified_run, type=Seat.PROFESSIONAL)
        SeatFactory(course_run=credit_run, type=Seat.PROFESSIONAL)
        SeatFactory(course_run=no_currency_run, type=Seat.PROFESSIONAL)

        bodies = mock_data.ECOMMERCE_API_BODIES
        url = self.api_url + 'courses/'
        responses.add_callback(
            responses.GET,
            url,
            callback=mock_api_callback(url, bodies),
            content_type=JSON
        )
        return bodies
    def test_filter_by_marketable(self):
        """ Verify the endpoint filters course runs to those that are marketable. """
        CourseRun.objects.all().delete()
        expected = CourseRunFactory.create_batch(3, course__partner=self.partner)
        for course_run in expected:
            SeatFactory(course_run=course_run)

        CourseRunFactory.create_batch(3, slug=None, course__partner=self.partner)
        CourseRunFactory.create_batch(3, slug='', course__partner=self.partner)

        url = reverse('api:v1:course_run-list') + '?marketable=1'
        self.assert_list_results(url, expected)
Esempio n. 19
0
    def create_course_with_basic_active_course_run(self):
        course = AlgoliaProxyCourseFactory(partner=self.__class__.edxPartner)

        course_run = CourseRunFactory(course=course,
                                      start=self.YESTERDAY,
                                      end=self.YESTERDAY,
                                      status=CourseRunStatus.Published)
        SeatFactory(
            course_run=course_run,
            type=SeatTypeFactory.audit(),
        )
        return course
Esempio n. 20
0
    def test_courses_with_time_range_query(self):
        catalog = CatalogFactory(query='start:[2015-01-01 TO 2015-12-01]')
        course_run_1 = CourseRunFactory(
            start=datetime.datetime(2015, 9, 1, tzinfo=pytz.UTC),
            status=CourseRunStatus.Published,
            type__is_marketable=True,
        )
        course_run_2 = CourseRunFactory(
            start=datetime.datetime(2015, 10, 13, tzinfo=pytz.UTC),
            status=CourseRunStatus.Published,
            type__is_marketable=True,
        )
        SeatFactory.create(course_run=course_run_1)
        SeatFactory.create(course_run=course_run_2)
        call_command('search_index', '--rebuild', '-f')
        url = reverse('api:v1:catalog-courses', kwargs={'id': catalog.id})
        response = self.client.get(url)

        assert response.status_code == 200
        assert response.data['results'] == self.serialize_catalog_course(
            [course_run_1.course, course_run_2.course], many=True)
Esempio n. 21
0
    def test_courses_with_different_catalog_queries_but_the_same_meaning(
            self, query):
        catalog = CatalogFactory(query=query)
        course_run_1 = CourseRunFactory(
            start=datetime.datetime(2015, 9, 1, tzinfo=pytz.UTC),
            course__title='Science at the Polls: Biology for Voters, Part 1',
            status=CourseRunStatus.Published,
            type__is_marketable=True,
        )
        course_run_2 = CourseRunFactory(
            start=datetime.datetime(2015, 10, 13, tzinfo=pytz.UTC),
            course__title="DNA: Biology's Genetic Code",
            status=CourseRunStatus.Published,
            type__is_marketable=True,
        )
        course_run_3 = CourseRunFactory(
            status=CourseRunStatus.Published,
            start=datetime.datetime(2015, 1, 1, tzinfo=pytz.UTC),
            course__title="AP Biology",
            type__is_marketable=True,
        )
        SeatFactory.create(course_run=course_run_1)
        SeatFactory.create(course_run=course_run_2)
        SeatFactory.create(course_run=course_run_3)
        url = reverse('api:v1:catalog-courses', kwargs={'id': catalog.id})
        response = self.client.get(url)

        assert response.status_code == 200
        assert response.data['results'] == self.serialize_catalog_course(
            [course_run_1.course, course_run_2.course, course_run_3.course],
            many=True)
    def setUp(self):
        super(AffiliateWindowViewSetTests, self).setUp()
        self.user = UserFactory()
        self.client.force_authenticate(self.user)
        self.catalog = CatalogFactory(query='*:*', viewers=[self.user])

        self.enrollment_end = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=30)
        self.course_end = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=60)
        self.course_run = CourseRunFactory(enrollment_end=self.enrollment_end, end=self.course_end)

        self.seat_verified = SeatFactory(course_run=self.course_run, type=Seat.VERIFIED)
        self.course = self.course_run.course
        self.affiliate_url = reverse('api:v1:partners:affiliate_window-detail', kwargs={'pk': self.catalog.id})
        self.refresh_index()
Esempio n. 23
0
    def create_upgradeable_course_starting_soon(self):
        course = AlgoliaProxyCourseFactory(partner=self.__class__.edxPartner)
        upgradeable_course_run = CourseRunFactory(
            course=course,
            start=self.TOMORROW,
            end=self.IN_FIFTEEN_DAYS,
            enrollment_end=self.IN_FIFTEEN_DAYS,
            status=CourseRunStatus.Published)

        SeatFactory(course_run=upgradeable_course_run,
                    type=SeatTypeFactory.verified(),
                    upgrade_deadline=self.IN_FIFTEEN_DAYS,
                    price=10)
        return course
 def create_current_upgradeable_course(self, **kwargs):
     course = AlgoliaProxyCourseFactory(partner=self.__class__.edxPartner)
     current_upgradeable_course_run = CourseRunFactory(
         course=course,
         start=self.YESTERDAY,
         end=self.IN_FIFTEEN_DAYS,
         enrollment_end=self.IN_FIFTEEN_DAYS,
         status=CourseRunStatus.Published,
         **kwargs)
     SeatFactory(course_run=current_upgradeable_course_run,
                 type=SeatTypeFactory.verified(),
                 upgrade_deadline=self.TOMORROW,
                 price=10)
     return course
Esempio n. 25
0
    def create_current_non_upgradeable_course(self):
        course = AlgoliaProxyCourseFactory(partner=self.__class__.edxPartner)

        non_upgradeable_course_run = CourseRunFactory(
            course=course,
            start=self.YESTERDAY,
            end=self.IN_FIFTEEN_DAYS,
            enrollment_end=self.IN_FIFTEEN_DAYS,
            status=CourseRunStatus.Published)
        # not upgradeable because upgrade_deadline has passed
        SeatFactory(course_run=non_upgradeable_course_run,
                    type=SeatTypeFactory.verified(),
                    upgrade_deadline=self.YESTERDAY,
                    price=10)
        return course
Esempio n. 26
0
    def test_data(self):
        course_run = CourseRunFactory()
        seat = SeatFactory(course_run=course_run)
        serializer = SeatSerializer(seat)

        expected = {
            'type': seat.type,
            'price': str(seat.price),
            'currency': seat.currency.code,
            'upgrade_deadline': json_date_format(seat.upgrade_deadline),
            'credit_provider': seat.credit_provider,  # pylint: disable=no-member
            'credit_hours': seat.credit_hours  # pylint: disable=no-member
        }

        self.assertDictEqual(serializer.data, expected)
Esempio n. 27
0
 def create_upcoming_non_upgradeable_course(self, additional_days=0):
     course = AlgoliaProxyCourseFactory(partner=self.__class__.edxPartner)
     future_course_run = CourseRunFactory(
         course=course,
         start=self.IN_THREE_DAYS + datetime.timedelta(days=additional_days),
         end=self.IN_FIFTEEN_DAYS + datetime.timedelta(days=additional_days),
         enrollment_end=self.IN_THREE_DAYS + datetime.timedelta(days=additional_days),
         status=CourseRunStatus.Published
     )
     SeatFactory(
         course_run=future_course_run,
         type=SeatTypeFactory.verified(),
         upgrade_deadline=self.YESTERDAY,
         price=10
     )
     return course
 def test_index_if_non_active_course_run_is_hidden(self):
     course = self.create_course_with_basic_active_course_run()
     course.authoring_organizations.add(OrganizationFactory())
     non_upgradeable_course_run = CourseRunFactory(
         course=course,
         start=self.YESTERDAY,
         end=self.IN_FIFTEEN_DAYS,
         enrollment_end=self.IN_FIFTEEN_DAYS,
         status=CourseRunStatus.Published,
         hidden=True)
     # not upgradeable because upgrade_deadline has passed
     SeatFactory(course_run=non_upgradeable_course_run,
                 type=SeatTypeFactory.verified(),
                 upgrade_deadline=self.YESTERDAY,
                 price=10)
     assert course.should_index
class AffiliateWindowViewSetTests(ElasticsearchTestMixin, SerializationMixin, APITestCase):
    """ Tests for the AffiliateWindowViewSet. """

    def setUp(self):
        super(AffiliateWindowViewSetTests, self).setUp()
        self.user = UserFactory()
        self.client.force_authenticate(self.user)
        self.catalog = CatalogFactory(query='*:*', viewers=[self.user])

        self.enrollment_end = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=30)
        self.course_end = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=60)
        self.course_run = CourseRunFactory(enrollment_end=self.enrollment_end, end=self.course_end)

        self.seat_verified = SeatFactory(course_run=self.course_run, type=Seat.VERIFIED)
        self.course = self.course_run.course
        self.affiliate_url = reverse('api:v1:partners:affiliate_window-detail', kwargs={'pk': self.catalog.id})
        self.refresh_index()

    def test_without_authentication(self):
        """ Verify authentication is required when accessing the endpoint. """
        self.client.logout()
        response = self.client.get(self.affiliate_url)
        self.assertEqual(response.status_code, 403)

    def test_affiliate_with_supported_seats(self):
        """ Verify that endpoint returns course runs for verified and professional seats only. """
        response = self.client.get(self.affiliate_url)
        self.assertEqual(response.status_code, 200)
        root = ET.fromstring(response.content)
        self.assertEqual(1, len(root.findall('product')))
        self.assert_product_xml(
            root.findall('product/[pid="{}-{}"]'.format(self.course_run.key, self.seat_verified.type))[0],
            self.seat_verified
        )

        # Add professional seat.
        seat_professional = SeatFactory(course_run=self.course_run, type=Seat.PROFESSIONAL)

        response = self.client.get(self.affiliate_url)
        root = ET.fromstring(response.content)
        self.assertEqual(2, len(root.findall('product')))

        self.assert_product_xml(
            root.findall('product/[pid="{}-{}"]'.format(self.course_run.key, self.seat_verified.type))[0],
            self.seat_verified
        )
        self.assert_product_xml(
            root.findall('product/[pid="{}-{}"]'.format(self.course_run.key, seat_professional.type))[0],
            seat_professional
        )

    @ddt.data(Seat.CREDIT, Seat.HONOR, Seat.AUDIT)
    def test_with_non_supported_seats(self, non_supporting_seat):
        """ Verify that endpoint returns no data for honor, credit and audit seats. """

        self.seat_verified.type = non_supporting_seat
        self.seat_verified.save()

        response = self.client.get(self.affiliate_url)
        self.assertEqual(response.status_code, 200)
        root = ET.fromstring(response.content)
        self.assertEqual(0, len(root.findall('product')))

    def test_with_closed_enrollment(self):
        """ Verify that endpoint returns no data if enrollment is close. """
        self.course_run.enrollment_end = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=100)
        self.course_run.end = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=100)
        self.course_run.save()

        # new course run with future end date and no enrollment_date.
        CourseRunFactory(end=self.course_end, course=self.course, enrollment_end=None)

        response = self.client.get(self.affiliate_url)

        self.assertEqual(response.status_code, 200)
        root = ET.fromstring(response.content)
        self.assertEqual(0, len(root.findall('product')))

    def assert_product_xml(self, content, seat):
        """ Helper method to verify product data in xml format. """
        self.assertEqual(content.find('pid').text, '{}-{}'.format(self.course_run.key, seat.type))
        self.assertEqual(content.find('name').text, self.course_run.title)
        self.assertEqual(content.find('desc').text, self.course_run.short_description)
        self.assertEqual(content.find('purl').text, self.course_run.marketing_url)
        self.assertEqual(content.find('imgurl').text, self.course_run.image.src)
        self.assertEqual(content.find('price/actualp').text, str(seat.price))
        self.assertEqual(content.find('currency').text, seat.currency.code)
        self.assertEqual(content.find('category').text, AffiliateWindowSerializer.CATEGORY)

    def test_dtd_with_valid_data(self):
        """ Verify the XML data produced by the endpoint conforms to the DTD file. """
        response = self.client.get(self.affiliate_url)
        self.assertEqual(response.status_code, 200)
        filename = abspath(join(dirname(dirname(__file__)), 'affiliate_window_product_feed.1.4.dtd'))
        dtd = etree.DTD(open(filename))

        root = etree.XML(response.content)
        self.assertTrue(dtd.validate(root))

    def test_permissions(self):
        """ Verify only users with the appropriate permissions can access the endpoint. """
        catalog = CatalogFactory()
        superuser = UserFactory(is_superuser=True)
        url = reverse('api:v1:partners:affiliate_window-detail', kwargs={'pk': catalog.id})

        # Superusers can view all catalogs
        self.client.force_authenticate(superuser)
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)

        # Regular users can only view catalogs belonging to them
        self.client.force_authenticate(self.user)
        response = self.client.get(url)
        self.assertEqual(response.status_code, 403)

        catalog.viewers = [self.user]
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
Esempio n. 30
0
    def test_marketable(self):
        """ Verify the method filters CourseRuns to those with slugs. """
        course_run = CourseRunFactory()
        SeatFactory(course_run=course_run)

        assert list(CourseRun.objects.marketable()) == [course_run]
Esempio n. 31
0
    def test_csv(self):
        SeatFactory(type='audit', course_run=self.course_run)
        SeatFactory(type='verified', course_run=self.course_run)
        SeatFactory(type='credit', course_run=self.course_run, credit_provider='ASU', credit_hours=9)
        SeatFactory(type='credit', course_run=self.course_run, credit_provider='Hogwarts', credit_hours=4)

        url = reverse('api:v1:catalog-csv', kwargs={'id': self.catalog.id})

        with self.assertNumQueries(20):
            response = self.client.get(url)

        course_run = self.serialize_catalog_flat_course_run(self.course_run)
        expected = [
            course_run['key'],
            course_run['title'],
            course_run['pacing_type'],
            course_run['start'],
            course_run['end'],
            course_run['enrollment_start'],
            course_run['enrollment_end'],
            course_run['announcement'],
            course_run['full_description'],
            course_run['short_description'],
            course_run['marketing_url'],
            course_run['image']['src'],
            '',
            '',
            '',
            course_run['video']['src'],
            course_run['video']['description'],
            course_run['video']['image']['src'],
            course_run['video']['image']['description'],
            str(course_run['video']['image']['height']),
            str(course_run['video']['image']['width']),
            course_run['content_language'],
            str(course_run['level_type']),
            str(course_run['max_effort']),
            str(course_run['min_effort']),
            course_run['subjects'],
            course_run['expected_learning_items'],
            course_run['prerequisites'],
            course_run['owners'],
            course_run['sponsors'],
            course_run['seats']['audit']['type'],
            course_run['seats']['honor']['type'],
            course_run['seats']['professional']['type'],
            str(course_run['seats']['professional']['price']),
            course_run['seats']['professional']['currency'],
            course_run['seats']['professional']['upgrade_deadline'],
            course_run['seats']['verified']['type'],
            str(course_run['seats']['verified']['price']),
            course_run['seats']['verified']['currency'],
            course_run['seats']['verified']['upgrade_deadline'],
            '{}'.format(course_run['seats']['credit']['type']),
            '{}'.format(str(course_run['seats']['credit']['price'])),
            '{}'.format(course_run['seats']['credit']['currency']),
            '{}'.format(course_run['seats']['credit']['upgrade_deadline']),
            '{}'.format(course_run['seats']['credit']['credit_provider']),
            '{}'.format(course_run['seats']['credit']['credit_hours']),
            course_run['modified'],
            course_run['course_key'],
        ]

        # collect streamed content
        received_content = b''
        for item in response.streaming_content:
            received_content += item

        # convert received content to csv for comparison
        f = StringIO(received_content.decode('utf-8'))
        reader = csv.reader(f)
        content = list(reader)

        self.assertEqual(response.status_code, 200)
        self.assertEqual(set(expected), set(content[1]))
Esempio n. 32
0
    def test_marketable_exclusions(self, slug):
        """ Verify the method excludes CourseRuns without a slug. """
        course_run = CourseRunFactory(slug=slug)
        SeatFactory(course_run=course_run)

        self.assertEqual(CourseRun.objects.marketable().exists(), False)